@@ -28,6 +28,7 @@ public abstract partial class Frame : IFrameControl
28
28
private const byte ByteCR = ( byte ) '\r ' ;
29
29
private const byte ByteLF = ( byte ) '\n ' ;
30
30
private const byte ByteColon = ( byte ) ':' ;
31
+ private const byte ByteForwardSlash = ( byte ) '/' ;
31
32
private const byte ByteSpace = ( byte ) ' ' ;
32
33
private const byte ByteTab = ( byte ) '\t ' ;
33
34
private const byte ByteQuestionMark = ( byte ) '?' ;
@@ -973,6 +974,8 @@ private void CreateResponseHeader(
973
974
974
975
public RequestLineStatus TakeStartLine ( SocketInput input )
975
976
{
977
+ // expected start line format: https://tools.ietf.org/html/rfc7230#section-3.1.1
978
+
976
979
const int MaxInvalidRequestLineChars = 32 ;
977
980
978
981
var scan = input . ConsumingStart ( ) ;
@@ -1011,6 +1014,7 @@ public RequestLineStatus TakeStartLine(SocketInput input)
1011
1014
}
1012
1015
end . Take ( ) ;
1013
1016
1017
+ // begin consuming method
1014
1018
string method ;
1015
1019
var begin = scan ;
1016
1020
if ( ! begin . GetKnownMethod ( out method ) )
@@ -1045,8 +1049,38 @@ public RequestLineStatus TakeStartLine(SocketInput input)
1045
1049
scan . Skip ( method . Length ) ;
1046
1050
}
1047
1051
1048
- scan . Take ( ) ;
1052
+ scan . Take ( ) ; // consume space
1053
+
1054
+ // begin consuming request-target
1049
1055
begin = scan ;
1056
+
1057
+ string requestUriScheme ;
1058
+ string requestAuthority = null ;
1059
+ if ( scan . GetKnownUriScheme ( out requestUriScheme ) )
1060
+ {
1061
+ // Request URIs can be in absolute form
1062
+ // See https://tools.ietf.org/html/rfc7230#section-5.3.2
1063
+ // This will skip over scheme and authority for determinie path, but preserving the vlues for rawTarget.
1064
+ // We rely on the Host header and server configuration to determine the effective host, port, and scheme
1065
+ // for this request.
1066
+ scan . Skip ( requestUriScheme . Length ) ;
1067
+ begin = scan ;
1068
+
1069
+ // an absolute URI is not required to end in a slash but must not be empty
1070
+ // see https://tools.ietf.org/html/rfc3986#section-4.3
1071
+
1072
+ var pathIndex = scan . Seek ( ByteForwardSlash , ByteSpace , ref end ) ;
1073
+ if ( pathIndex == - 1 )
1074
+ {
1075
+ RejectRequest ( RequestRejectionReason . InvalidRequestLine ,
1076
+ Log . IsEnabled ( LogLevel . Information ) ? start . GetAsciiStringEscaped ( end , MaxInvalidRequestLineChars ) : string . Empty ) ;
1077
+ }
1078
+
1079
+ // TODO consider handling unicode host names
1080
+ requestAuthority = begin . GetAsciiString ( ref scan ) ;
1081
+ begin = scan ;
1082
+ }
1083
+
1050
1084
var needDecode = false ;
1051
1085
var chFound = scan . Seek ( ByteSpace , ByteQuestionMark , BytePercentage , ref end ) ;
1052
1086
if ( chFound == - 1 )
@@ -1082,13 +1116,15 @@ public RequestLineStatus TakeStartLine(SocketInput input)
1082
1116
1083
1117
var queryEnd = scan ;
1084
1118
1085
- if ( pathBegin . Peek ( ) == ByteSpace )
1119
+ if ( pathBegin . Peek ( ) == ByteSpace && requestAuthority == null )
1086
1120
{
1087
1121
RejectRequest ( RequestRejectionReason . InvalidRequestLine ,
1088
1122
Log . IsEnabled ( LogLevel . Information ) ? start . GetAsciiStringEscaped ( end , MaxInvalidRequestLineChars ) : string . Empty ) ;
1089
1123
}
1090
1124
1091
- scan . Take ( ) ;
1125
+ scan . Take ( ) ; // consume space
1126
+
1127
+ // begin consuming HTTP-version
1092
1128
begin = scan ;
1093
1129
if ( scan . Seek ( ByteCR , ref end ) == - 1 )
1094
1130
{
@@ -1123,11 +1159,11 @@ public RequestLineStatus TakeStartLine(SocketInput input)
1123
1159
// Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8;
1124
1160
// then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs"
1125
1161
string requestUrlPath ;
1126
- string rawTarget ;
1162
+ string rawUrlPath ;
1127
1163
if ( needDecode )
1128
1164
{
1129
1165
// Read raw target before mutating memory.
1130
- rawTarget = pathBegin . GetAsciiString ( ref queryEnd ) ;
1166
+ rawUrlPath = pathBegin . GetAsciiString ( ref queryEnd ) ;
1131
1167
1132
1168
// URI was encoded, unescape and then parse as utf8
1133
1169
pathEnd = UrlPathDecoder . Unescape ( pathBegin , pathEnd ) ;
@@ -1142,31 +1178,33 @@ public RequestLineStatus TakeStartLine(SocketInput input)
1142
1178
{
1143
1179
// No need to allocate an extra string if the path didn't need
1144
1180
// decoding and there's no query string following it.
1145
- rawTarget = requestUrlPath ;
1181
+ rawUrlPath = requestUrlPath ;
1146
1182
}
1147
1183
else
1148
1184
{
1149
- rawTarget = pathBegin . GetAsciiString ( ref queryEnd ) ;
1185
+ rawUrlPath = pathBegin . GetAsciiString ( ref queryEnd ) ;
1150
1186
}
1151
1187
}
1152
1188
1153
- var normalizedTarget = PathNormalizer . RemoveDotSegments ( requestUrlPath ) ;
1189
+ var normalizedUrlPath = requestUrlPath == null
1190
+ ? string . Empty
1191
+ : PathNormalizer . RemoveDotSegments ( requestUrlPath ) ;
1154
1192
1155
1193
consumed = scan ;
1156
1194
Method = method ;
1157
1195
QueryString = queryString ;
1158
- RawTarget = rawTarget ;
1196
+ RawTarget = requestUriScheme + requestAuthority + rawUrlPath ;
1159
1197
HttpVersion = httpVersion ;
1160
1198
1161
1199
bool caseMatches ;
1162
- if ( RequestUrlStartsWithPathBase ( normalizedTarget , out caseMatches ) )
1200
+ if ( RequestUrlStartsWithPathBase ( normalizedUrlPath , out caseMatches ) )
1163
1201
{
1164
- PathBase = caseMatches ? _pathBase : normalizedTarget . Substring ( 0 , _pathBase . Length ) ;
1165
- Path = normalizedTarget . Substring ( _pathBase . Length ) ;
1202
+ PathBase = caseMatches ? _pathBase : normalizedUrlPath . Substring ( 0 , _pathBase . Length ) ;
1203
+ Path = normalizedUrlPath . Substring ( _pathBase . Length ) ;
1166
1204
}
1167
- else if ( rawTarget [ 0 ] == '/' ) // check rawTarget since normalizedTarget can be "" or "/" after dot segment removal
1205
+ else if ( rawUrlPath ? . Length > 0 && rawUrlPath [ 0 ] == '/' ) // check rawUrlPath since normalizedUrlPath can be "" or "/" after dot segment removal
1168
1206
{
1169
- Path = normalizedTarget ;
1207
+ Path = normalizedUrlPath ;
1170
1208
}
1171
1209
else
1172
1210
{
0 commit comments