From 8c89a8c1665b6fbec3f13b8c0e84c79464179c89 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Fri, 11 Jun 2021 14:12:56 -0700 Subject: [PATCH] feat(ffi): add option to get raw headers from response --- capi/include/hyper.h | 26 ++++++++++++++++++++++++++ src/client/conn.rs | 14 ++++++++++++++ src/ffi/body.rs | 2 +- src/ffi/client.rs | 14 ++++++++++++++ src/ffi/http_types.rs | 24 +++++++++++++++++++++++- src/proto/h1/conn.rs | 11 +++++++++++ src/proto/h1/io.rs | 4 ++++ src/proto/h1/mod.rs | 2 ++ src/proto/h1/role.rs | 37 +++++++++++++++++++++++++++++++++++++ 9 files changed, 132 insertions(+), 2 deletions(-) diff --git a/capi/include/hyper.h b/capi/include/hyper.h index a305dc4a09..452512362e 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -371,6 +371,17 @@ void hyper_clientconn_options_exec(struct hyper_clientconn_options *opts, */ enum hyper_code hyper_clientconn_options_http2(struct hyper_clientconn_options *opts, int enabled); +/* + Set the whether to include a copy of the raw headers in responses + received on this connection. + + Pass `0` to disable, `1` to enable. + + If enabled, see `hyper_response_headers_raw()` for usage. + */ +enum hyper_code hyper_clientconn_options_headers_raw(struct hyper_clientconn_options *opts, + int enabled); + /* Frees a `hyper_error`. */ @@ -475,6 +486,21 @@ const uint8_t *hyper_response_reason_phrase(const struct hyper_response *resp); */ size_t hyper_response_reason_phrase_len(const struct hyper_response *resp); +/* + Get a reference to the full raw headers of this response. + + You must have enabled `hyper_clientconn_options_headers_raw()`, or this + will return NULL. + + The returned `hyper_buf *` is just a reference, owned by the response. + You need to make a copy if you wish to use it after freeing the + response. + + The buffer is not null-terminated, see the `hyper_buf` functions for + getting the bytes and length. + */ +const struct hyper_buf *hyper_response_headers_raw(const struct hyper_response *resp); + /* Get the HTTP version used by this response. diff --git a/src/client/conn.rs b/src/client/conn.rs index c6170007a2..c557ee29c2 100644 --- a/src/client/conn.rs +++ b/src/client/conn.rs @@ -147,6 +147,8 @@ pub struct Builder { h1_preserve_header_case: bool, h1_read_buf_exact_size: Option, h1_max_buf_size: Option, + #[cfg(feature = "ffi")] + h1_headers_raw: bool, #[cfg(feature = "http2")] h2_builder: proto::h2::client::Config, version: Proto, @@ -528,6 +530,8 @@ impl Builder { h1_title_case_headers: false, h1_preserve_header_case: false, h1_max_buf_size: None, + #[cfg(feature = "ffi")] + h1_headers_raw: false, #[cfg(feature = "http2")] h2_builder: Default::default(), #[cfg(feature = "http1")] @@ -588,6 +592,12 @@ impl Builder { self } + #[cfg(feature = "ffi")] + pub(crate) fn http1_headers_raw(&mut self, enabled: bool) -> &mut Self { + self.h1_headers_raw = enabled; + self + } + /// Sets whether HTTP2 is required. /// /// Default is false. @@ -773,6 +783,10 @@ impl Builder { if opts.h09_responses { conn.set_h09_responses(); } + + #[cfg(feature = "ffi")] + conn.set_raw_headers(opts.h1_headers_raw); + if let Some(sz) = opts.h1_read_buf_exact_size { conn.set_read_buf_exact_size(sz); } diff --git a/src/ffi/body.rs b/src/ffi/body.rs index f942b769e5..d6e1394371 100644 --- a/src/ffi/body.rs +++ b/src/ffi/body.rs @@ -14,7 +14,7 @@ use crate::body::{Body, Bytes, HttpBody as _}; pub struct hyper_body(pub(super) Body); /// A buffer of bytes that is sent or received on a `hyper_body`. -pub struct hyper_buf(pub(super) Bytes); +pub struct hyper_buf(pub(crate) Bytes); pub(crate) struct UserBody { data_func: hyper_body_data_callback, diff --git a/src/ffi/client.rs b/src/ffi/client.rs index 9be4f5a04d..6fa8862ddd 100644 --- a/src/ffi/client.rs +++ b/src/ffi/client.rs @@ -159,3 +159,17 @@ ffi_fn! { } } } + +ffi_fn! { + /// Set the whether to include a copy of the raw headers in responses + /// received on this connection. + /// + /// Pass `0` to disable, `1` to enable. + /// + /// If enabled, see `hyper_response_headers_raw()` for usage. + fn hyper_clientconn_options_headers_raw(opts: *mut hyper_clientconn_options, enabled: c_int) -> hyper_code { + let opts = unsafe { &mut *opts }; + opts.builder.http1_headers_raw(enabled != 0); + hyper_code::HYPERE_OK + } +} diff --git a/src/ffi/http_types.rs b/src/ffi/http_types.rs index 924944835b..e192e4bc0a 100644 --- a/src/ffi/http_types.rs +++ b/src/ffi/http_types.rs @@ -2,7 +2,7 @@ use bytes::Bytes; use libc::{c_int, size_t}; use std::ffi::c_void; -use super::body::hyper_body; +use super::body::{hyper_body, hyper_buf}; use super::error::hyper_code; use super::task::{hyper_task_return_type, AsTaskType}; use super::HYPER_ITER_CONTINUE; @@ -27,6 +27,8 @@ pub struct hyper_headers { #[derive(Debug)] pub(crate) struct ReasonPhrase(pub(crate) Bytes); +pub(crate) struct RawHeaders(pub(crate) hyper_buf); + // ===== impl hyper_request ===== ffi_fn! { @@ -178,6 +180,26 @@ ffi_fn! { } } +ffi_fn! { + /// Get a reference to the full raw headers of this response. + /// + /// You must have enabled `hyper_clientconn_options_headers_raw()`, or this + /// will return NULL. + /// + /// The returned `hyper_buf *` is just a reference, owned by the response. + /// You need to make a copy if you wish to use it after freeing the + /// response. + /// + /// The buffer is not null-terminated, see the `hyper_buf` functions for + /// getting the bytes and length. + fn hyper_response_headers_raw(resp: *const hyper_response) -> *const hyper_buf { + match unsafe { &*resp }.0.extensions().get::() { + Some(raw) => &raw.0, + None => std::ptr::null(), + } + } ?= std::ptr::null() +} + ffi_fn! { /// Get the HTTP version used by this response. /// diff --git a/src/proto/h1/conn.rs b/src/proto/h1/conn.rs index dd5397880d..cb8fdd0ac9 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -49,6 +49,8 @@ where preserve_header_case: false, title_case_headers: false, h09_responses: false, + #[cfg(feature = "ffi")] + raw_headers: false, notify_read: false, reading: Reading::Init, writing: Writing::Init, @@ -98,6 +100,11 @@ where self.state.allow_half_close = true; } + #[cfg(feature = "ffi")] + pub(crate) fn set_raw_headers(&mut self, enabled: bool) { + self.state.raw_headers = enabled; + } + pub(crate) fn into_inner(self) -> (I, Bytes) { self.io.into_inner() } @@ -162,6 +169,8 @@ where h1_parser_config: self.state.h1_parser_config.clone(), preserve_header_case: self.state.preserve_header_case, h09_responses: self.state.h09_responses, + #[cfg(feature = "ffi")] + raw_headers: self.state.raw_headers, } )) { Ok(msg) => msg, @@ -766,6 +775,8 @@ struct State { preserve_header_case: bool, title_case_headers: bool, h09_responses: bool, + #[cfg(feature = "ffi")] + raw_headers: bool, /// Set to true when the Dispatcher should poll read operations /// again. See the `maybe_notify` method for more. notify_read: bool, diff --git a/src/proto/h1/io.rs b/src/proto/h1/io.rs index 4e6efba680..4adc6c4419 100644 --- a/src/proto/h1/io.rs +++ b/src/proto/h1/io.rs @@ -167,6 +167,8 @@ where h1_parser_config: parse_ctx.h1_parser_config.clone(), preserve_header_case: parse_ctx.preserve_header_case, h09_responses: parse_ctx.h09_responses, + #[cfg(feature = "ffi")] + raw_headers: parse_ctx.raw_headers, }, )? { Some(msg) => { @@ -675,6 +677,8 @@ mod tests { h1_parser_config: Default::default(), preserve_header_case: false, h09_responses: false, + #[cfg(feature = "ffi")] + raw_headers: false, }; assert!(buffered .parse::(cx, parse_ctx) diff --git a/src/proto/h1/mod.rs b/src/proto/h1/mod.rs index 3871277c25..b3f62f911b 100644 --- a/src/proto/h1/mod.rs +++ b/src/proto/h1/mod.rs @@ -74,6 +74,8 @@ pub(crate) struct ParseContext<'a> { h1_parser_config: ParserConfig, preserve_header_case: bool, h09_responses: bool, + #[cfg(feature = "ffi")] + raw_headers: bool, } /// Passed to Http1Transaction::encode diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index f76f6cfef6..c8174044b3 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -970,6 +970,11 @@ impl Http1Transaction for Client { #[cfg(not(feature = "ffi"))] drop(reason); + #[cfg(feature = "ffi")] + if ctx.raw_headers { + extensions.insert(crate::ffi::RawHeaders(crate::ffi::hyper_buf(slice))); + } + let head = MessageHead { version, subject: status, @@ -1424,6 +1429,8 @@ mod tests { h1_parser_config: Default::default(), preserve_header_case: false, h09_responses: false, + #[cfg(feature = "ffi")] + raw_headers: false, }, ) .unwrap() @@ -1447,6 +1454,8 @@ mod tests { h1_parser_config: Default::default(), preserve_header_case: false, h09_responses: false, + #[cfg(feature = "ffi")] + raw_headers: false, }; let msg = Client::parse(&mut raw, ctx).unwrap().unwrap(); assert_eq!(raw.len(), 0); @@ -1465,6 +1474,8 @@ mod tests { h1_parser_config: Default::default(), preserve_header_case: false, h09_responses: false, + #[cfg(feature = "ffi")] + raw_headers: false, }; Server::parse(&mut raw, ctx).unwrap_err(); } @@ -1481,6 +1492,8 @@ mod tests { h1_parser_config: Default::default(), preserve_header_case: false, h09_responses: true, + #[cfg(feature = "ffi")] + raw_headers: false, }; let msg = Client::parse(&mut raw, ctx).unwrap().unwrap(); assert_eq!(raw, H09_RESPONSE); @@ -1499,6 +1512,8 @@ mod tests { h1_parser_config: Default::default(), preserve_header_case: false, h09_responses: false, + #[cfg(feature = "ffi")] + raw_headers: false, }; Client::parse(&mut raw, ctx).unwrap_err(); assert_eq!(raw, H09_RESPONSE); @@ -1521,6 +1536,8 @@ mod tests { h1_parser_config, preserve_header_case: false, h09_responses: false, + #[cfg(feature = "ffi")] + raw_headers: false, }; let msg = Client::parse(&mut raw, ctx).unwrap().unwrap(); assert_eq!(raw.len(), 0); @@ -1540,6 +1557,8 @@ mod tests { h1_parser_config: Default::default(), preserve_header_case: false, h09_responses: false, + #[cfg(feature = "ffi")] + raw_headers: false, }; Client::parse(&mut raw, ctx).unwrap_err(); } @@ -1554,6 +1573,8 @@ mod tests { h1_parser_config: Default::default(), preserve_header_case: true, h09_responses: false, + #[cfg(feature = "ffi")] + raw_headers: false, }; let parsed_message = Server::parse(&mut raw, ctx).unwrap().unwrap(); let orig_headers = parsed_message @@ -1589,6 +1610,8 @@ mod tests { h1_parser_config: Default::default(), preserve_header_case: false, h09_responses: false, + #[cfg(feature = "ffi")] + raw_headers: false, }, ) .expect("parse ok") @@ -1605,6 +1628,8 @@ mod tests { h1_parser_config: Default::default(), preserve_header_case: false, h09_responses: false, + #[cfg(feature = "ffi")] + raw_headers: false, }, ) .expect_err(comment) @@ -1820,6 +1845,8 @@ mod tests { h1_parser_config: Default::default(), preserve_header_case: false, h09_responses: false, + #[cfg(feature = "ffi")] + raw_headers: false, } ) .expect("parse ok") @@ -1836,6 +1863,8 @@ mod tests { h1_parser_config: Default::default(), preserve_header_case: false, h09_responses: false, + #[cfg(feature = "ffi")] + raw_headers: false, }, ) .expect("parse ok") @@ -1852,6 +1881,8 @@ mod tests { h1_parser_config: Default::default(), preserve_header_case: false, h09_responses: false, + #[cfg(feature = "ffi")] + raw_headers: false, }, ) .expect_err("parse should err") @@ -2335,6 +2366,8 @@ mod tests { h1_parser_config: Default::default(), preserve_header_case: false, h09_responses: false, + #[cfg(feature = "ffi")] + raw_headers: false, }, ) .expect("parse ok") @@ -2415,6 +2448,8 @@ mod tests { h1_parser_config: Default::default(), preserve_header_case: false, h09_responses: false, + #[cfg(feature = "ffi")] + raw_headers: false, }, ) .unwrap() @@ -2451,6 +2486,8 @@ mod tests { h1_parser_config: Default::default(), preserve_header_case: false, h09_responses: false, + #[cfg(feature = "ffi")] + raw_headers: false, }, ) .unwrap()