@@ -3179,6 +3179,8 @@ pub struct CacheConfig {
31793179 #[ serde( default ) ]
31803180 pub bypass_request_headers : Vec < String > ,
31813181 #[ serde( default ) ]
3182+ pub bypass_request_header_values : BTreeMap < String , String > ,
3183+ #[ serde( default ) ]
31823184 pub bypass_cookie_names : Vec < String > ,
31833185 #[ serde( default ) ]
31843186 pub bypass_cookie_values : BTreeMap < String , String > ,
@@ -3226,6 +3228,7 @@ impl Default for CacheConfig {
32263228 hide_response_headers : Vec :: new ( ) ,
32273229 no_store_response_headers : Vec :: new ( ) ,
32283230 bypass_request_headers : Vec :: new ( ) ,
3231+ bypass_request_header_values : BTreeMap :: new ( ) ,
32293232 bypass_cookie_names : Vec :: new ( ) ,
32303233 bypass_cookie_values : BTreeMap :: new ( ) ,
32313234 bypass_query_params : Vec :: new ( ) ,
@@ -3271,6 +3274,10 @@ impl CacheConfig {
32713274 for header in & self . bypass_request_headers {
32723275 validate_header_name ( scope, header) ?;
32733276 }
3277+ for ( header, value) in & self . bypass_request_header_values {
3278+ validate_header_name ( scope, header) ?;
3279+ validate_cache_bypass_request_header_value ( scope, header, value) ?;
3280+ }
32743281 for cookie in & self . bypass_cookie_names {
32753282 validate_cache_cookie_name ( scope, cookie) ?;
32763283 }
@@ -3413,6 +3420,27 @@ fn validate_cache_query_param(scope: &'static str, param: &str) -> Result<(), Co
34133420 Ok ( ( ) )
34143421}
34153422
3423+ fn validate_cache_bypass_request_header_value (
3424+ scope : & ' static str ,
3425+ header : & str ,
3426+ value : & str ,
3427+ ) -> Result < ( ) , ConfigError > {
3428+ if value. trim ( ) . is_empty ( )
3429+ || value. len ( ) > 4096
3430+ || value
3431+ . as_bytes ( )
3432+ . iter ( )
3433+ . any ( |byte| matches ! ( byte, 0x00 ..=0x08 | 0x0a ..=0x1f | 0x7f ) )
3434+ {
3435+ return Err ( ConfigError :: InvalidCacheBypassRequestHeaderValue {
3436+ scope,
3437+ header : header. to_owned ( ) ,
3438+ value : value. to_owned ( ) ,
3439+ } ) ;
3440+ }
3441+ Ok ( ( ) )
3442+ }
3443+
34163444fn validate_cache_cookie_name ( scope : & ' static str , name : & str ) -> Result < ( ) , ConfigError > {
34173445 if name. is_empty ( ) || name. len ( ) > 128 || !valid_cookie_name ( name) {
34183446 return Err ( ConfigError :: InvalidCacheBypassCookieName {
@@ -3970,6 +3998,11 @@ pub enum ConfigError {
39703998 scope : & ' static str ,
39713999 param : String ,
39724000 } ,
4001+ InvalidCacheBypassRequestHeaderValue {
4002+ scope : & ' static str ,
4003+ header : String ,
4004+ value : String ,
4005+ } ,
39734006 InvalidCacheBypassCookieName {
39744007 scope : & ' static str ,
39754008 name : String ,
@@ -4395,6 +4428,14 @@ impl Display for ConfigError {
43954428 formatter,
43964429 "{scope}.bypass_query_params must contain raw query parameter names without whitespace, controls, '&', '=', '#', '?', or ';', got {param:?}"
43974430 ) ,
4431+ Self :: InvalidCacheBypassRequestHeaderValue {
4432+ scope,
4433+ header,
4434+ value,
4435+ } => write ! (
4436+ formatter,
4437+ "{scope}.bypass_request_header_values[{header:?}] must contain a non-empty safe header value without controls, got {value:?}"
4438+ ) ,
43984439 Self :: InvalidCacheBypassCookieName { scope, name } => write ! (
43994440 formatter,
44004441 "{scope}.bypass_cookie_names must contain cookie name tokens without whitespace or separators, got {name:?}"
@@ -7241,6 +7282,7 @@ mod tests {
72417282 hide_response_headers = ["set-cookie"]
72427283 no_store_response_headers = ["x-fluxheim-no-store"]
72437284 bypass_request_headers = ["cookie", "authorization"]
7285+ bypass_request_header_values = { x-preview-mode = "1" }
72447286 bypass_cookie_names = ["sessionid", "wordpress_logged_in"]
72457287 bypass_cookie_values = { preview = "1" }
72467288 bypass_query_params = ["preview", "token"]
@@ -7292,6 +7334,13 @@ mod tests {
72927334 config. cache. bypass_request_headers,
72937335 [ "cookie" . to_owned( ) , "authorization" . to_owned( ) ]
72947336 ) ;
7337+ assert_eq ! (
7338+ config
7339+ . cache
7340+ . bypass_request_header_values
7341+ . get( "x-preview-mode" ) ,
7342+ Some ( & "1" . to_owned( ) )
7343+ ) ;
72957344 assert_eq ! (
72967345 config. cache. bypass_cookie_names,
72977346 [ "sessionid" . to_owned( ) , "wordpress_logged_in" . to_owned( ) ]
@@ -7409,6 +7458,28 @@ mod tests {
74097458 ) ;
74107459 }
74117460
7461+ #[ test]
7462+ fn rejects_invalid_cache_bypass_request_header_value ( ) {
7463+ for value in [ "" , " " , "bad\n value" ] {
7464+ let config: Config = toml:: from_str ( & format ! (
7465+ r#"
7466+ [cache]
7467+ bypass_request_header_values = {{ x-preview-mode = {value:?} }}
7468+ "# ,
7469+ ) )
7470+ . unwrap ( ) ;
7471+
7472+ assert_eq ! (
7473+ config. validate( ) ,
7474+ Err ( ConfigError :: InvalidCacheBypassRequestHeaderValue {
7475+ scope: "cache" ,
7476+ header: "x-preview-mode" . to_owned( ) ,
7477+ value: value. to_owned( )
7478+ } )
7479+ ) ;
7480+ }
7481+ }
7482+
74127483 #[ test]
74137484 fn rejects_invalid_cache_no_store_response_header_name ( ) {
74147485 let config: Config = toml:: from_str (
0 commit comments