@@ -167,7 +167,9 @@ impl HeaderContext {
167167 self . response . status ( StatusCode :: FOUND ) ;
168168 self . has_status = true ;
169169 }
170- self . response . insert_header ( ( name. as_str ( ) , value_str) ) ;
170+ let sanitized_value = sanitize_header_value ( value_str) ;
171+ self . response
172+ . insert_header ( ( name. as_str ( ) , sanitized_value) ) ;
171173 }
172174 Ok ( self )
173175 }
@@ -242,7 +244,9 @@ impl HeaderContext {
242244 self . has_status = true ;
243245 let link = get_object_str ( data, "link" )
244246 . with_context ( || "The redirect component requires a 'link' property" ) ?;
245- self . response . insert_header ( ( header:: LOCATION , link) ) ;
247+ let sanitized_link = sanitize_header_value ( link) ;
248+ self . response
249+ . insert_header ( ( header:: LOCATION , sanitized_link) ) ;
246250 let response = self . response . body ( ( ) ) ;
247251 Ok ( response)
248252 }
@@ -288,9 +292,10 @@ impl HeaderContext {
288292 get_object_str ( options, "filename" ) . or_else ( || get_object_str ( options, "title" ) )
289293 {
290294 let extension = if filename. contains ( '.' ) { "" } else { ".csv" } ;
295+ let sanitized_filename = sanitize_header_value ( filename) ;
291296 self . response . insert_header ( (
292297 header:: CONTENT_DISPOSITION ,
293- format ! ( "attachment; filename={filename }{extension}" ) ,
298+ format ! ( "attachment; filename={sanitized_filename }{extension}" ) ,
294299 ) ) ;
295300 }
296301 let csv_renderer = CsvBodyRenderer :: new ( self . writer , options) . await ?;
@@ -315,9 +320,10 @@ impl HeaderContext {
315320 log:: debug!( "Authentication failed" ) ;
316321 // The authentication failed
317322 let http_response: HttpResponse = if let Some ( link) = get_object_str ( & data, "link" ) {
323+ let sanitized_link = sanitize_header_value ( link) ;
318324 self . response
319325 . status ( StatusCode :: FOUND )
320- . insert_header ( ( header:: LOCATION , link ) )
326+ . insert_header ( ( header:: LOCATION , sanitized_link ) )
321327 . body (
322328 "Sorry, but you are not authorized to access this page. \
323329 Redirecting to the login page...",
@@ -333,9 +339,10 @@ impl HeaderContext {
333339
334340 fn download ( mut self , options : & JsonValue ) -> anyhow:: Result < PageContext > {
335341 if let Some ( filename) = get_object_str ( options, "filename" ) {
342+ let sanitized_filename = sanitize_header_value ( filename) ;
336343 self . response . insert_header ( (
337344 header:: CONTENT_DISPOSITION ,
338- format ! ( "attachment; filename=\" {filename }\" " ) ,
345+ format ! ( "attachment; filename=\" {sanitized_filename }\" " ) ,
339346 ) ) ;
340347 }
341348 let data_url = get_object_str ( options, "data_url" )
@@ -407,6 +414,26 @@ async fn verify_password_async(
407414 . await ?
408415}
409416
417+ fn sanitize_header_value ( value : & str ) -> String {
418+ let sanitized: String = value
419+ . chars ( )
420+ . filter ( |& c| {
421+ let byte = c as u32 ;
422+ byte >= 0x20 && byte != 0x7F
423+ } )
424+ . collect ( ) ;
425+
426+ if sanitized != value {
427+ log:: warn!(
428+ "Sanitized header value by removing control characters. Original length: {}, Sanitized length: {}" ,
429+ value. len( ) ,
430+ sanitized. len( )
431+ ) ;
432+ }
433+
434+ sanitized
435+ }
436+
410437fn get_object_str < ' a > ( json : & ' a JsonValue , key : & str ) -> Option < & ' a str > {
411438 json. as_object ( )
412439 . and_then ( |obj| obj. get ( key) )
0 commit comments