@@ -53,9 +53,11 @@ public ForwardedHeadersMiddleware(RequestDelegate next, ILoggerFactory loggerFac
5353 EnsureOptionNotNullorWhitespace ( options . Value . ForwardedForHeaderName , nameof ( options . Value . ForwardedForHeaderName ) ) ;
5454 EnsureOptionNotNullorWhitespace ( options . Value . ForwardedHostHeaderName , nameof ( options . Value . ForwardedHostHeaderName ) ) ;
5555 EnsureOptionNotNullorWhitespace ( options . Value . ForwardedProtoHeaderName , nameof ( options . Value . ForwardedProtoHeaderName ) ) ;
56+ EnsureOptionNotNullorWhitespace ( options . Value . ForwardedPrefixHeaderName , nameof ( options . Value . ForwardedPrefixHeaderName ) ) ;
5657 EnsureOptionNotNullorWhitespace ( options . Value . OriginalForHeaderName , nameof ( options . Value . OriginalForHeaderName ) ) ;
5758 EnsureOptionNotNullorWhitespace ( options . Value . OriginalHostHeaderName , nameof ( options . Value . OriginalHostHeaderName ) ) ;
5859 EnsureOptionNotNullorWhitespace ( options . Value . OriginalProtoHeaderName , nameof ( options . Value . OriginalProtoHeaderName ) ) ;
60+ EnsureOptionNotNullorWhitespace ( options . Value . OriginalPrefixHeaderName , nameof ( options . Value . OriginalPrefixHeaderName ) ) ;
5961
6062 _options = options . Value ;
6163 _logger = loggerFactory . CreateLogger < ForwardedHeadersMiddleware > ( ) ;
@@ -126,8 +128,8 @@ public Task Invoke(HttpContext context)
126128 public void ApplyForwarders ( HttpContext context )
127129 {
128130 // Gather expected headers.
129- string [ ] ? forwardedFor = null , forwardedProto = null , forwardedHost = null ;
130- bool checkFor = false , checkProto = false , checkHost = false ;
131+ string [ ] ? forwardedFor = null , forwardedProto = null , forwardedHost = null , forwardedPrefix = null ;
132+ bool checkFor = false , checkProto = false , checkHost = false , checkPrefix = false ;
131133 int entryCount = 0 ;
132134
133135 var request = context . Request ;
@@ -165,6 +167,21 @@ public void ApplyForwarders(HttpContext context)
165167 entryCount = Math . Max ( forwardedHost . Length , entryCount ) ;
166168 }
167169
170+ if ( _options . ForwardedHeaders . HasFlag ( ForwardedHeaders . XForwardedPrefix ) )
171+ {
172+ checkPrefix = true ;
173+ forwardedPrefix = requestHeaders . GetCommaSeparatedValues ( _options . ForwardedPrefixHeaderName ) ;
174+ if ( _options . RequireHeaderSymmetry
175+ && ( ( checkFor && forwardedFor ! . Length != forwardedPrefix . Length )
176+ || ( checkProto && forwardedProto ! . Length != forwardedPrefix . Length )
177+ || ( checkHost && forwardedHost ! . Length != forwardedPrefix . Length ) ) )
178+ {
179+ _logger . LogWarning ( 1 , "Parameter count mismatch between X-Forwarded-Prefix and X-Forwarded-Host and X-Forwarded-For or X-Forwarded-Proto." ) ;
180+ return ;
181+ }
182+ entryCount = Math . Max ( forwardedPrefix . Length , entryCount ) ;
183+ }
184+
168185 // Apply ForwardLimit, if any
169186 if ( _options . ForwardLimit . HasValue && entryCount > _options . ForwardLimit )
170187 {
@@ -189,6 +206,10 @@ public void ApplyForwarders(HttpContext context)
189206 {
190207 set . Host = forwardedHost [ forwardedHost . Length - i - 1 ] ;
191208 }
209+ if ( checkPrefix && i < forwardedPrefix ! . Length )
210+ {
211+ set . Prefix = forwardedPrefix [ forwardedPrefix . Length - i - 1 ] ;
212+ }
192213 sets [ i ] = set ;
193214 }
194215
@@ -271,6 +292,20 @@ public void ApplyForwarders(HttpContext context)
271292 return ;
272293 }
273294 }
295+
296+ if ( checkPrefix )
297+ {
298+ if ( ! string . IsNullOrEmpty ( set . Prefix ) && set . Prefix [ 0 ] == '/' )
299+ {
300+ applyChanges = true ;
301+ currentValues . Prefix = set . Prefix ;
302+ }
303+ else if ( _options . RequireHeaderSymmetry )
304+ {
305+ _logger . LogWarning ( 5 , $ "Incorrect number of x-forwarded-prefix header values, see { nameof ( _options . RequireHeaderSymmetry ) } ") ;
306+ return ;
307+ }
308+ }
274309 }
275310
276311 if ( applyChanges )
@@ -285,7 +320,8 @@ public void ApplyForwarders(HttpContext context)
285320 if ( forwardedFor ! . Length > entriesConsumed )
286321 {
287322 // Truncate the consumed header values
288- requestHeaders [ _options . ForwardedForHeaderName ] = forwardedFor . Take ( forwardedFor . Length - entriesConsumed ) . ToArray ( ) ;
323+ requestHeaders [ _options . ForwardedForHeaderName ] =
324+ TruncateConsumedHeaderValues ( forwardedFor , entriesConsumed ) ;
289325 }
290326 else
291327 {
@@ -303,7 +339,8 @@ public void ApplyForwarders(HttpContext context)
303339 if ( forwardedProto ! . Length > entriesConsumed )
304340 {
305341 // Truncate the consumed header values
306- requestHeaders [ _options . ForwardedProtoHeaderName ] = forwardedProto . Take ( forwardedProto . Length - entriesConsumed ) . ToArray ( ) ;
342+ requestHeaders [ _options . ForwardedProtoHeaderName ] =
343+ TruncateConsumedHeaderValues ( forwardedProto , entriesConsumed ) ;
307344 }
308345 else
309346 {
@@ -320,7 +357,8 @@ public void ApplyForwarders(HttpContext context)
320357 if ( forwardedHost ! . Length > entriesConsumed )
321358 {
322359 // Truncate the consumed header values
323- requestHeaders [ _options . ForwardedHostHeaderName ] = forwardedHost . Take ( forwardedHost . Length - entriesConsumed ) . ToArray ( ) ;
360+ requestHeaders [ _options . ForwardedHostHeaderName ] =
361+ TruncateConsumedHeaderValues ( forwardedHost , entriesConsumed ) ;
324362 }
325363 else
326364 {
@@ -329,6 +367,29 @@ public void ApplyForwarders(HttpContext context)
329367 }
330368 request . Host = HostString . FromUriComponent ( currentValues . Host ) ;
331369 }
370+
371+ if ( checkPrefix && currentValues . Prefix != null )
372+ {
373+ if ( request . PathBase . HasValue )
374+ {
375+ // Save the original
376+ requestHeaders [ _options . OriginalPrefixHeaderName ] = request . PathBase . ToString ( ) ;
377+ }
378+
379+ if ( forwardedPrefix ! . Length > entriesConsumed )
380+ {
381+ // Truncate the consumed header values
382+ requestHeaders [ _options . ForwardedPrefixHeaderName ] =
383+ TruncateConsumedHeaderValues ( forwardedPrefix , entriesConsumed ) ;
384+ }
385+ else
386+ {
387+ // All values were consumed
388+ requestHeaders . Remove ( _options . ForwardedPrefixHeaderName ) ;
389+ }
390+
391+ request . PathBase = PathString . FromUriComponent ( currentValues . Prefix ) ;
392+ }
332393 }
333394 }
334395
@@ -362,6 +423,7 @@ private struct SetOfForwarders
362423 public IPEndPoint ? RemoteIpAndPort ;
363424 public string Host ;
364425 public string Scheme ;
426+ public string Prefix ;
365427 }
366428
367429 // Empty was checked for by the caller
@@ -423,4 +485,12 @@ private static bool TryValidateHostPort(string hostText, int offset)
423485
424486 return hostText . AsSpan ( offset + 1 ) . IndexOfAnyExceptInRange ( '0' , '9' ) < 0 ;
425487 }
488+
489+ private static string [ ] TruncateConsumedHeaderValues ( string [ ] forwarded , int entriesConsumed )
490+ {
491+ var newLength = forwarded . Length - entriesConsumed ;
492+ var remaining = new string [ newLength ] ;
493+ Array . Copy ( forwarded , remaining , newLength ) ;
494+ return remaining ;
495+ }
426496}
0 commit comments