feat(http2): Strip connection headers before sending
Automatically removes "connection" headers before sending over HTTP2. These headers are illegal in HTTP2, and would otherwise cause errors. Closes: #1551
This commit is contained in:
		
				
					committed by
					
						 Sean McArthur
						Sean McArthur
					
				
			
			
				
	
			
			
			
						parent
						
							a0a0fcdd9b
						
					
				
				
					commit
					f20afba57d
				
			| @@ -1,10 +1,13 @@ | |||||||
| use bytes::Buf; | use bytes::Buf; | ||||||
| use futures::{Async, Future, Poll}; | use futures::{Async, Future, Poll}; | ||||||
| use h2::{Reason, SendStream}; | use h2::{Reason, SendStream}; | ||||||
|  | use http::header::{ | ||||||
|  |     HeaderName, CONNECTION, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, TE, TRAILER, | ||||||
|  |     TRANSFER_ENCODING, UPGRADE, | ||||||
|  | }; | ||||||
| use http::HeaderMap; | use http::HeaderMap; | ||||||
| use http::header::{CONNECTION, TRANSFER_ENCODING}; |  | ||||||
|  |  | ||||||
| use ::body::Payload; | use body::Payload; | ||||||
|  |  | ||||||
| mod client; | mod client; | ||||||
| mod server; | mod server; | ||||||
| @@ -13,13 +16,42 @@ pub(crate) use self::client::Client; | |||||||
| pub(crate) use self::server::Server; | pub(crate) use self::server::Server; | ||||||
|  |  | ||||||
| fn strip_connection_headers(headers: &mut HeaderMap) { | fn strip_connection_headers(headers: &mut HeaderMap) { | ||||||
|     if headers.remove(TRANSFER_ENCODING).is_some() { |     // List of connection headers from: | ||||||
|         trace!("removed illegal Transfer-Encoding header"); |     // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection | ||||||
|  |     let connection_headers = [ | ||||||
|  |         HeaderName::from_lowercase(b"keep-alive").unwrap(), | ||||||
|  |         HeaderName::from_lowercase(b"proxy-connection").unwrap(), | ||||||
|  |         PROXY_AUTHENTICATE, | ||||||
|  |         PROXY_AUTHORIZATION, | ||||||
|  |         TE, | ||||||
|  |         TRAILER, | ||||||
|  |         TRANSFER_ENCODING, | ||||||
|  |         UPGRADE, | ||||||
|  |     ]; | ||||||
|  |  | ||||||
|  |     for header in connection_headers.iter() { | ||||||
|  |         if headers.remove(header).is_some() { | ||||||
|  |             warn!("Connection header illegal in HTTP/2: {}", header.as_str()); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|     if headers.contains_key(CONNECTION) { |  | ||||||
|         warn!("Connection header illegal in HTTP/2"); |     if let Some(header) = headers.remove(CONNECTION) { | ||||||
|         //TODO: actually remove it, after checking the value |         warn!( | ||||||
|         //and removing all related headers |             "Connection header illegal in HTTP/2: {}", | ||||||
|  |             CONNECTION.as_str() | ||||||
|  |         ); | ||||||
|  |         let header_contents = header.to_str().unwrap(); | ||||||
|  |  | ||||||
|  |         // A `Connection` header may have a comma-separated list of names of other headers that | ||||||
|  |         // are meant for only this specific connection. | ||||||
|  |         // | ||||||
|  |         // Iterate these names and remove them as headers. Connection-specific headers are | ||||||
|  |         // forbidden in HTTP2, as that information has been moved into frame types of the h2 | ||||||
|  |         // protocol. | ||||||
|  |         for name in header_contents.split(',') { | ||||||
|  |             let name = name.trim(); | ||||||
|  |             headers.remove(name); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -55,7 +87,8 @@ where | |||||||
|  |  | ||||||
|     fn send_eos_frame(&mut self) -> ::Result<()> { |     fn send_eos_frame(&mut self) -> ::Result<()> { | ||||||
|         trace!("send body eos"); |         trace!("send body eos"); | ||||||
|         self.body_tx.send_data(SendBuf(None), true) |         self.body_tx | ||||||
|  |             .send_data(SendBuf(None), true) | ||||||
|             .map_err(::Error::new_body_write) |             .map_err(::Error::new_body_write) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -94,13 +127,14 @@ where | |||||||
|                         ); |                         ); | ||||||
|  |  | ||||||
|                         let buf = SendBuf(Some(chunk)); |                         let buf = SendBuf(Some(chunk)); | ||||||
|                         self.body_tx.send_data(buf, is_eos) |                         self.body_tx | ||||||
|  |                             .send_data(buf, is_eos) | ||||||
|                             .map_err(::Error::new_body_write)?; |                             .map_err(::Error::new_body_write)?; | ||||||
|  |  | ||||||
|                         if is_eos { |                         if is_eos { | ||||||
|                             return Ok(Async::Ready(())) |                             return Ok(Async::Ready(())); | ||||||
|                         } |                         } | ||||||
|                     }, |                     } | ||||||
|                     None => { |                     None => { | ||||||
|                         let is_eos = self.stream.is_end_stream(); |                         let is_eos = self.stream.is_end_stream(); | ||||||
|                         if is_eos { |                         if is_eos { | ||||||
| @@ -109,19 +143,20 @@ where | |||||||
|                             self.data_done = true; |                             self.data_done = true; | ||||||
|                             // loop again to poll_trailers |                             // loop again to poll_trailers | ||||||
|                         } |                         } | ||||||
|                     }, |                     } | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|                 match try_ready!(self.stream.poll_trailers().map_err(|e| self.on_err(e))) { |                 match try_ready!(self.stream.poll_trailers().map_err(|e| self.on_err(e))) { | ||||||
|                     Some(trailers) => { |                     Some(trailers) => { | ||||||
|                         self.body_tx.send_trailers(trailers) |                         self.body_tx | ||||||
|  |                             .send_trailers(trailers) | ||||||
|                             .map_err(::Error::new_body_write)?; |                             .map_err(::Error::new_body_write)?; | ||||||
|                         return Ok(Async::Ready(())); |                         return Ok(Async::Ready(())); | ||||||
|                     }, |                     } | ||||||
|                     None => { |                     None => { | ||||||
|                         // There were no trailers, so send an empty DATA frame... |                         // There were no trailers, so send an empty DATA frame... | ||||||
|                         return self.send_eos_frame().map(Async::Ready); |                         return self.send_eos_frame().map(Async::Ready); | ||||||
|                     }, |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -133,24 +168,16 @@ struct SendBuf<B>(Option<B>); | |||||||
| impl<B: Buf> Buf for SendBuf<B> { | impl<B: Buf> Buf for SendBuf<B> { | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn remaining(&self) -> usize { |     fn remaining(&self) -> usize { | ||||||
|         self.0 |         self.0.as_ref().map(|b| b.remaining()).unwrap_or(0) | ||||||
|             .as_ref() |  | ||||||
|             .map(|b| b.remaining()) |  | ||||||
|             .unwrap_or(0) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn bytes(&self) -> &[u8] { |     fn bytes(&self) -> &[u8] { | ||||||
|         self.0 |         self.0.as_ref().map(|b| b.bytes()).unwrap_or(&[]) | ||||||
|             .as_ref() |  | ||||||
|             .map(|b| b.bytes()) |  | ||||||
|             .unwrap_or(&[]) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn advance(&mut self, cnt: usize) { |     fn advance(&mut self, cnt: usize) { | ||||||
|         self.0 |         self.0.as_mut().map(|b| b.advance(cnt)); | ||||||
|             .as_mut() |  | ||||||
|             .map(|b| b.advance(cnt)); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -62,6 +62,84 @@ t! { | |||||||
|             ; |             ; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | t! { | ||||||
|  |     get_strip_connection_header, | ||||||
|  |     client: | ||||||
|  |         request: | ||||||
|  |             uri: "/", | ||||||
|  |             ; | ||||||
|  |         response: | ||||||
|  |             status: 200, | ||||||
|  |             headers: { | ||||||
|  |                 // h2 doesn't actually receive the connection header | ||||||
|  |             }, | ||||||
|  |             body: "hello world", | ||||||
|  |             ; | ||||||
|  |     server: | ||||||
|  |         request: | ||||||
|  |             uri: "/", | ||||||
|  |             ; | ||||||
|  |         response: | ||||||
|  |             headers: { | ||||||
|  |                 // http2 should strip this header | ||||||
|  |                 "connection" => "close", | ||||||
|  |             }, | ||||||
|  |             body: "hello world", | ||||||
|  |             ; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | t! { | ||||||
|  |     get_strip_keep_alive_header, | ||||||
|  |     client: | ||||||
|  |         request: | ||||||
|  |             uri: "/", | ||||||
|  |             ; | ||||||
|  |         response: | ||||||
|  |             status: 200, | ||||||
|  |             headers: { | ||||||
|  |                 // h2 doesn't actually receive the keep-alive header | ||||||
|  |             }, | ||||||
|  |             body: "hello world", | ||||||
|  |             ; | ||||||
|  |     server: | ||||||
|  |         request: | ||||||
|  |             uri: "/", | ||||||
|  |             ; | ||||||
|  |         response: | ||||||
|  |             headers: { | ||||||
|  |                 // http2 should strip this header | ||||||
|  |                 "keep-alive" => "timeout=5, max=1000", | ||||||
|  |             }, | ||||||
|  |             body: "hello world", | ||||||
|  |             ; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | t! { | ||||||
|  |     get_strip_upgrade_header, | ||||||
|  |     client: | ||||||
|  |         request: | ||||||
|  |             uri: "/", | ||||||
|  |             ; | ||||||
|  |         response: | ||||||
|  |             status: 200, | ||||||
|  |             headers: { | ||||||
|  |                 // h2 doesn't actually receive the upgrade header | ||||||
|  |             }, | ||||||
|  |             body: "hello world", | ||||||
|  |             ; | ||||||
|  |     server: | ||||||
|  |         request: | ||||||
|  |             uri: "/", | ||||||
|  |             ; | ||||||
|  |         response: | ||||||
|  |             headers: { | ||||||
|  |                 // http2 should strip this header | ||||||
|  |                 "upgrade" => "h2c", | ||||||
|  |             }, | ||||||
|  |             body: "hello world", | ||||||
|  |             ; | ||||||
|  | } | ||||||
|  |  | ||||||
| t! { | t! { | ||||||
|     get_body_chunked, |     get_body_chunked, | ||||||
|     client: |     client: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user