@@ -37,145 +37,152 @@ internal Request(RequestContext requestContext)
3737 {
3838 // TODO: Verbose log
3939 RequestContext = requestContext ;
40- _contentBoundaryType = BoundaryType . None ;
4140
42- RequestId = requestContext . RequestId ;
43- // For HTTP/2 Http.Sys assigns each request a unique connection id for use with API calls, but the RawConnectionId represents the real connection.
44- UConnectionId = requestContext . ConnectionId ;
45- RawConnectionId = requestContext . RawConnectionId ;
46- SslStatus = requestContext . SslStatus ;
41+ try
42+ {
43+ _contentBoundaryType = BoundaryType . None ;
4744
48- KnownMethod = requestContext . VerbId ;
49- Method = requestContext . GetVerb ( ) ! ;
45+ RequestId = requestContext . RequestId ;
46+ // For HTTP/2 Http.Sys assigns each request a unique connection id for use with API calls, but the RawConnectionId represents the real connection.
47+ UConnectionId = requestContext . ConnectionId ;
48+ RawConnectionId = requestContext . RawConnectionId ;
49+ SslStatus = requestContext . SslStatus ;
5050
51- RawUrl = requestContext . GetRawUrl ( ) ! ;
51+ KnownMethod = requestContext . VerbId ;
52+ Method = requestContext . GetVerb ( ) ! ;
5253
53- var cookedUrl = requestContext . GetCookedUrl ( ) ;
54- QueryString = cookedUrl . GetQueryString ( ) ?? string . Empty ;
54+ RawUrl = requestContext . GetRawUrl ( ) ! ;
5555
56- var rawUrlInBytes = requestContext . GetRawUrlInBytes ( ) ;
57- var originalPath = RequestUriBuilder . DecodeAndUnescapePath ( rawUrlInBytes ) ;
56+ var cookedUrl = requestContext . GetCookedUrl ( ) ;
57+ QueryString = cookedUrl . GetQueryString ( ) ?? string . Empty ;
5858
59- PathBase = string . Empty ;
60- Path = originalPath ;
61- var prefix = requestContext . Server . Options . UrlPrefixes . GetPrefix ( ( int ) requestContext . UrlContext ) ;
59+ var rawUrlInBytes = requestContext . GetRawUrlInBytes ( ) ;
60+ var originalPath = RequestUriBuilder . DecodeAndUnescapePath ( rawUrlInBytes ) ;
6261
63- // 'OPTIONS * HTTP/1.1'
64- if ( KnownMethod == HttpApiTypes . HTTP_VERB . HttpVerbOPTIONS && string . Equals ( RawUrl , "*" , StringComparison . Ordinal ) )
65- {
6662 PathBase = string . Empty ;
67- Path = string . Empty ;
68- }
69- // Prefix may be null if the requested has been transfered to our queue
70- else if ( prefix is not null )
71- {
72- var pathBase = prefix . PathWithoutTrailingSlash ;
63+ Path = originalPath ;
64+ var prefix = requestContext . Server . Options . UrlPrefixes . GetPrefix ( ( int ) requestContext . UrlContext ) ;
7365
74- // url: /base/path, prefix: /base/, base: /base, path: /path
75- // url: /, prefix: /, base: , path: /
76- if ( originalPath . Equals ( pathBase , StringComparison . Ordinal ) )
77- {
78- // Exact match, no need to preserve the casing
79- PathBase = pathBase ;
80- Path = string . Empty ;
81- }
82- else if ( originalPath . Equals ( pathBase , StringComparison . OrdinalIgnoreCase ) )
66+ // 'OPTIONS * HTTP/1.1'
67+ if ( KnownMethod == HttpApiTypes . HTTP_VERB . HttpVerbOPTIONS && string . Equals ( RawUrl , "*" , StringComparison . Ordinal ) )
8368 {
84- // Preserve the user input casing
85- PathBase = originalPath ;
69+ PathBase = string . Empty ;
8670 Path = string . Empty ;
8771 }
88- else if ( originalPath . StartsWith ( prefix . Path , StringComparison . Ordinal ) )
89- {
90- // Exact match, no need to preserve the casing
91- PathBase = pathBase ;
92- Path = originalPath [ pathBase . Length ..] ;
93- }
94- else if ( originalPath . StartsWith ( prefix . Path , StringComparison . OrdinalIgnoreCase ) )
72+ // Prefix may be null if the requested has been transferred to our queue
73+ else if ( prefix is not null )
9574 {
96- // Preserve the user input casing
97- PathBase = originalPath [ ..pathBase . Length ] ;
98- Path = originalPath [ pathBase . Length ..] ;
99- }
100- else
101- {
102- // Http.Sys path base matching is based on the cooked url which applies some non-standard normalizations that we don't use
103- // like collapsing duplicate slashes "//", converting '\' to '/', and un-escaping "%2F" to '/'. Find the right split and
104- // ignore the normalizations.
105- var originalOffset = 0 ;
106- var baseOffset = 0 ;
107- while ( originalOffset < originalPath . Length && baseOffset < pathBase . Length )
75+ var pathBase = prefix . PathWithoutTrailingSlash ;
76+
77+ // url: /base/path, prefix: /base/, base: /base, path: /path
78+ // url: /, prefix: /, base: , path: /
79+ if ( originalPath . Equals ( pathBase , StringComparison . Ordinal ) )
10880 {
109- var baseValue = pathBase [ baseOffset ] ;
110- var offsetValue = originalPath [ originalOffset ] ;
111- if ( baseValue == offsetValue
112- || char . ToUpperInvariant ( baseValue ) == char . ToUpperInvariant ( offsetValue ) )
113- {
114- // case-insensitive match, continue
115- originalOffset ++ ;
116- baseOffset ++ ;
117- }
118- else if ( baseValue == '/' && offsetValue == '\\ ' )
119- {
120- // Http.Sys considers these equivalent
121- originalOffset ++ ;
122- baseOffset ++ ;
123- }
124- else if ( baseValue == '/' && originalPath . AsSpan ( originalOffset ) . StartsWith ( "%2F" , StringComparison . OrdinalIgnoreCase ) )
125- {
126- // Http.Sys un-escapes this
127- originalOffset += 3 ;
128- baseOffset ++ ;
129- }
130- else if ( baseOffset > 0 && pathBase [ baseOffset - 1 ] == '/'
131- && ( offsetValue == '/' || offsetValue == '\\ ' ) )
132- {
133- // Duplicate slash, skip
134- originalOffset ++ ;
135- }
136- else if ( baseOffset > 0 && pathBase [ baseOffset - 1 ] == '/'
137- && originalPath . AsSpan ( originalOffset ) . StartsWith ( "%2F" , StringComparison . OrdinalIgnoreCase ) )
138- {
139- // Duplicate slash equivalent, skip
140- originalOffset += 3 ;
141- }
142- else
81+ // Exact match, no need to preserve the casing
82+ PathBase = pathBase ;
83+ Path = string . Empty ;
84+ }
85+ else if ( originalPath . Equals ( pathBase , StringComparison . OrdinalIgnoreCase ) )
86+ {
87+ // Preserve the user input casing
88+ PathBase = originalPath ;
89+ Path = string . Empty ;
90+ }
91+ else if ( originalPath . StartsWith ( prefix . Path , StringComparison . Ordinal ) )
92+ {
93+ // Exact match, no need to preserve the casing
94+ PathBase = pathBase ;
95+ Path = originalPath [ pathBase . Length ..] ;
96+ }
97+ else if ( originalPath . StartsWith ( prefix . Path , StringComparison . OrdinalIgnoreCase ) )
98+ {
99+ // Preserve the user input casing
100+ PathBase = originalPath [ ..pathBase . Length ] ;
101+ Path = originalPath [ pathBase . Length ..] ;
102+ }
103+ else
104+ {
105+ // Http.Sys path base matching is based on the cooked url which applies some non-standard normalizations that we don't use
106+ // like collapsing duplicate slashes "//", converting '\' to '/', and un-escaping "%2F" to '/'. Find the right split and
107+ // ignore the normalizations.
108+ var originalOffset = 0 ;
109+ var baseOffset = 0 ;
110+ while ( originalOffset < originalPath . Length && baseOffset < pathBase . Length )
143111 {
144- // Mismatch, fall back
145- // The failing test case here is "/base/call//../bat//path1//path2", reduced to "/base/call/bat//path1//path2",
146- // where http.sys collapses "//" before "../", but we do "../" first. We've lost the context that there were dot segments,
147- // or duplicate slashes, how do we figure out that "call/" can be eliminated?
148- originalOffset = 0 ;
149- break ;
112+ var baseValue = pathBase [ baseOffset ] ;
113+ var offsetValue = originalPath [ originalOffset ] ;
114+ if ( baseValue == offsetValue
115+ || char . ToUpperInvariant ( baseValue ) == char . ToUpperInvariant ( offsetValue ) )
116+ {
117+ // case-insensitive match, continue
118+ originalOffset ++ ;
119+ baseOffset ++ ;
120+ }
121+ else if ( baseValue == '/' && offsetValue == '\\ ' )
122+ {
123+ // Http.Sys considers these equivalent
124+ originalOffset ++ ;
125+ baseOffset ++ ;
126+ }
127+ else if ( baseValue == '/' && originalPath . AsSpan ( originalOffset ) . StartsWith ( "%2F" , StringComparison . OrdinalIgnoreCase ) )
128+ {
129+ // Http.Sys un-escapes this
130+ originalOffset += 3 ;
131+ baseOffset ++ ;
132+ }
133+ else if ( baseOffset > 0 && pathBase [ baseOffset - 1 ] == '/'
134+ && ( offsetValue == '/' || offsetValue == '\\ ' ) )
135+ {
136+ // Duplicate slash, skip
137+ originalOffset ++ ;
138+ }
139+ else if ( baseOffset > 0 && pathBase [ baseOffset - 1 ] == '/'
140+ && originalPath . AsSpan ( originalOffset ) . StartsWith ( "%2F" , StringComparison . OrdinalIgnoreCase ) )
141+ {
142+ // Duplicate slash equivalent, skip
143+ originalOffset += 3 ;
144+ }
145+ else
146+ {
147+ // Mismatch, fall back
148+ // The failing test case here is "/base/call//../bat//path1//path2", reduced to "/base/call/bat//path1//path2",
149+ // where http.sys collapses "//" before "../", but we do "../" first. We've lost the context that there were dot segments,
150+ // or duplicate slashes, how do we figure out that "call/" can be eliminated?
151+ originalOffset = 0 ;
152+ break ;
153+ }
150154 }
155+ PathBase = originalPath [ ..originalOffset ] ;
156+ Path = originalPath [ originalOffset ..] ;
151157 }
152- PathBase = originalPath [ ..originalOffset ] ;
153- Path = originalPath [ originalOffset ..] ;
154158 }
155- }
156- else if ( requestContext . Server . Options . UrlPrefixes . TryMatchLongestPrefix ( IsHttps , cookedUrl . GetHost ( ) ! , originalPath , out var pathBase , out var path ) )
157- {
158- PathBase = pathBase ;
159- Path = path ;
160- }
159+ else if ( requestContext . Server . Options . UrlPrefixes . TryMatchLongestPrefix ( IsHttps , cookedUrl . GetHost ( ) ! , originalPath , out var pathBase , out var path ) )
160+ {
161+ PathBase = pathBase ;
162+ Path = path ;
163+ }
161164
162- ProtocolVersion = RequestContext . GetVersion ( ) ;
165+ ProtocolVersion = RequestContext . GetVersion ( ) ;
163166
164- Headers = new RequestHeaders ( RequestContext ) ;
167+ Headers = new RequestHeaders ( RequestContext ) ;
165168
166- User = RequestContext . GetUser ( ) ;
169+ User = RequestContext . GetUser ( ) ;
167170
168- SniHostName = string . Empty ;
169- if ( IsHttps )
170- {
171- GetTlsHandshakeResults ( ) ;
172- }
171+ SniHostName = string . Empty ;
172+ if ( IsHttps )
173+ {
174+ GetTlsHandshakeResults ( ) ;
175+ }
173176
174- // GetTlsTokenBindingInfo(); TODO: https://github.com/aspnet/HttpSysServer/issues/231
177+ // GetTlsTokenBindingInfo(); TODO: https://github.com/aspnet/HttpSysServer/issues/231
175178
176- // Finished directly accessing the HTTP_REQUEST structure.
177- RequestContext . ReleasePins ( ) ;
178- // TODO: Verbose log parameters
179+ }
180+ finally
181+ {
182+ // Finished directly accessing the HTTP_REQUEST structure.
183+ RequestContext . ReleasePins ( ) ;
184+ // TODO: Verbose log parameters
185+ }
179186
180187 RemoveContentLengthIfTransferEncodingContainsChunked ( ) ;
181188 }
0 commit comments