11// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
22// SPDX-License-Identifier: Apache-2.0
33
4+ use std:: collections:: HashMap ;
45use std:: io:: { Error as WriteError , Write } ;
56
67use crate :: ascii:: { COLON , CR , LF , SP } ;
78use crate :: common:: { Body , Version } ;
89use crate :: headers:: { Header , MediaType } ;
10+ use crate :: HttpHeaderError ;
911use crate :: Method ;
1012
1113/// Wrapper over a response status code.
@@ -95,6 +97,7 @@ pub struct ResponseHeaders {
9597 server : String ,
9698 allow : Vec < Method > ,
9799 accept_encoding : bool ,
100+ custom_headers : HashMap < String , String > ,
98101}
99102
100103impl Default for ResponseHeaders {
@@ -106,6 +109,7 @@ impl Default for ResponseHeaders {
106109 server : String :: from ( "Firecracker API" ) ,
107110 allow : Vec :: new ( ) ,
108111 accept_encoding : false ,
112+ custom_headers : HashMap :: default ( ) ,
109113 }
110114 }
111115}
@@ -141,6 +145,18 @@ impl ResponseHeaders {
141145 buf. write_all ( & [ CR , LF ] )
142146 }
143147
148+ // The logic pertaining to custom headers writing.
149+ fn write_custom_headers < T : Write > ( & self , buf : & mut T ) -> Result < ( ) , WriteError > {
150+ // Note that all the custom headers have already been validated as US-ASCII.
151+ for ( header, value) in & self . custom_headers {
152+ buf. write_all ( header. as_bytes ( ) ) ?;
153+ buf. write_all ( & [ COLON , SP ] ) ?;
154+ buf. write_all ( value. as_bytes ( ) ) ?;
155+ buf. write_all ( & [ CR , LF ] ) ?;
156+ }
157+ Ok ( ( ) )
158+ }
159+
144160 /// Writes the headers to `buf` using the HTTP specification.
145161 pub fn write_all < T : Write > ( & self , buf : & mut T ) -> Result < ( ) , WriteError > {
146162 buf. write_all ( Header :: Server . raw ( ) ) ?;
@@ -153,6 +169,7 @@ impl ResponseHeaders {
153169
154170 self . write_allow_header ( buf) ?;
155171 self . write_deprecation_header ( buf) ?;
172+ self . write_custom_headers ( buf) ?;
156173
157174 if let Some ( content_length) = self . content_length {
158175 buf. write_all ( Header :: ContentType . raw ( ) ) ?;
@@ -203,6 +220,26 @@ impl ResponseHeaders {
203220 pub fn set_encoding ( & mut self ) {
204221 self . accept_encoding = true ;
205222 }
223+
224+ /// Sets custom headers to be written in the HTTP response.
225+ pub fn set_custom_headers (
226+ & mut self ,
227+ custom_headers : & HashMap < String , String > ,
228+ ) -> Result < ( ) , HttpHeaderError > {
229+ // https://datatracker.ietf.org/doc/html/rfc7230
230+ // HTTP headers MUST be US-ASCII.
231+ if let Some ( ( k, v) ) = custom_headers
232+ . iter ( )
233+ . find ( |( k, v) | !k. is_ascii ( ) || !v. is_ascii ( ) )
234+ {
235+ return Err ( HttpHeaderError :: NonAsciiCharacter (
236+ k. to_owned ( ) ,
237+ v. to_owned ( ) ,
238+ ) ) ;
239+ }
240+ self . custom_headers = custom_headers. to_owned ( ) ;
241+ Ok ( ( ) )
242+ }
206243}
207244
208245/// Wrapper over an HTTP Response.
@@ -294,6 +331,19 @@ impl Response {
294331 self . headers . set_server ( server) ;
295332 }
296333
334+ /// Sets the custom headers.
335+ pub fn set_custom_headers (
336+ & mut self ,
337+ custom_headers : & HashMap < String , String > ,
338+ ) -> Result < ( ) , HttpHeaderError > {
339+ self . headers . set_custom_headers ( custom_headers)
340+ }
341+
342+ /// Gets a reference to the custom headers.
343+ pub fn custom_headers ( & self ) -> & HashMap < String , String > {
344+ & self . headers . custom_headers
345+ }
346+
297347 /// Sets the HTTP allowed methods.
298348 pub fn set_allow ( & mut self , methods : Vec < Method > ) {
299349 self . headers . allow = methods;
@@ -554,4 +604,29 @@ mod tests {
554604 assert ! ( response. write_all( & mut response_buf. as_mut( ) ) . is_ok( ) ) ;
555605 assert_eq ! ( response_buf. as_ref( ) , expected_response) ;
556606 }
607+
608+ #[ test]
609+ fn test_custom_headers ( ) {
610+ // Valid custom headers.
611+ let mut response = Response :: new ( Version :: Http10 , StatusCode :: OK ) ;
612+ let custom_headers = [ ( "Foo" . into ( ) , "Bar" . into ( ) ) ] . into ( ) ;
613+ response. set_custom_headers ( & custom_headers) . unwrap ( ) ;
614+ let expected_response = b"HTTP/1.0 200 \r \n \
615+ Server: Firecracker API\r \n \
616+ Connection: keep-alive\r \n \
617+ Foo: Bar\r \n \
618+ Content-Type: application/json\r \n \
619+ Content-Length: 0\r \n \r \n ";
620+ let mut response_buf: [ u8 ; 127 ] = [ 0 ; 127 ] ;
621+ response. write_all ( & mut response_buf. as_mut ( ) ) . unwrap ( ) ;
622+ assert_eq ! ( response_buf. as_ref( ) , expected_response) ;
623+
624+ // Should fail to set custom headers including non-ASCII character.
625+ let mut response = Response :: new ( Version :: Http10 , StatusCode :: OK ) ;
626+ let custom_headers = [ ( "Greek capital delta" . into ( ) , "Δ" . into ( ) ) ] . into ( ) ;
627+ assert_eq ! (
628+ response. set_custom_headers( & custom_headers) . unwrap_err( ) ,
629+ HttpHeaderError :: NonAsciiCharacter ( "Greek capital delta" . into( ) , "Δ" . into( ) )
630+ ) ;
631+ }
557632}
0 commit comments