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