use bytes::Bytes; use libc::{c_int, size_t}; use std::ffi::c_void; use super::body::{hyper_body, hyper_buf}; use super::error::hyper_code; use super::task::{hyper_task_return_type, AsTaskType}; use super::{UserDataPointer, HYPER_ITER_CONTINUE}; use crate::ext::{HeaderCaseMap, OriginalHeaderOrder, ReasonPhrase}; use crate::header::{HeaderName, HeaderValue}; use crate::{Body, HeaderMap, Method, Request, Response, Uri}; /// An HTTP request. pub struct hyper_request(pub(super) Request); /// An HTTP response. pub struct hyper_response(pub(super) Response); /// An HTTP header map. /// /// These can be part of a request or response. pub struct hyper_headers { pub(super) headers: HeaderMap, orig_casing: HeaderCaseMap, orig_order: OriginalHeaderOrder, } pub(crate) struct RawHeaders(pub(crate) hyper_buf); pub(crate) struct OnInformational { func: hyper_request_on_informational_callback, data: UserDataPointer, } type hyper_request_on_informational_callback = extern "C" fn(*mut c_void, *mut hyper_response); // ===== impl hyper_request ===== ffi_fn! { /// Construct a new HTTP request. fn hyper_request_new() -> *mut hyper_request { Box::into_raw(Box::new(hyper_request(Request::new(Body::empty())))) } ?= std::ptr::null_mut() } ffi_fn! { /// Free an HTTP request if not going to send it on a client. fn hyper_request_free(req: *mut hyper_request) { drop(non_null!(Box::from_raw(req) ?= ())); } } ffi_fn! { /// Set the HTTP Method of the request. fn hyper_request_set_method(req: *mut hyper_request, method: *const u8, method_len: size_t) -> hyper_code { let bytes = unsafe { std::slice::from_raw_parts(method, method_len as usize) }; let req = non_null!(&mut *req ?= hyper_code::HYPERE_INVALID_ARG); match Method::from_bytes(bytes) { Ok(m) => { *req.0.method_mut() = m; hyper_code::HYPERE_OK }, Err(_) => { hyper_code::HYPERE_INVALID_ARG } } } } ffi_fn! { /// Set the URI of the request. /// /// The request's URI is best described as the `request-target` from the RFCs. So in HTTP/1, /// whatever is set will get sent as-is in the first line (GET $uri HTTP/1.1). It /// supports the 4 defined variants, origin-form, absolute-form, authority-form, and /// asterisk-form. /// /// The underlying type was built to efficiently support HTTP/2 where the request-target is /// split over :scheme, :authority, and :path. As such, each part can be set explicitly, or the /// type can parse a single contiguous string and if a scheme is found, that slot is "set". If /// the string just starts with a path, only the path portion is set. All pseudo headers that /// have been parsed/set are sent when the connection type is HTTP/2. /// /// To set each slot explicitly, use `hyper_request_set_uri_parts`. fn hyper_request_set_uri(req: *mut hyper_request, uri: *const u8, uri_len: size_t) -> hyper_code { let bytes = unsafe { std::slice::from_raw_parts(uri, uri_len as usize) }; let req = non_null!(&mut *req ?= hyper_code::HYPERE_INVALID_ARG); match Uri::from_maybe_shared(bytes) { Ok(u) => { *req.0.uri_mut() = u; hyper_code::HYPERE_OK }, Err(_) => { hyper_code::HYPERE_INVALID_ARG } } } } ffi_fn! { /// Set the URI of the request with separate scheme, authority, and /// path/query strings. /// /// Each of `scheme`, `authority`, and `path_and_query` should either be /// null, to skip providing a component, or point to a UTF-8 encoded /// string. If any string pointer argument is non-null, its corresponding /// `len` parameter must be set to the string's length. fn hyper_request_set_uri_parts( req: *mut hyper_request, scheme: *const u8, scheme_len: size_t, authority: *const u8, authority_len: size_t, path_and_query: *const u8, path_and_query_len: size_t ) -> hyper_code { let mut builder = Uri::builder(); if !scheme.is_null() { let scheme_bytes = unsafe { std::slice::from_raw_parts(scheme, scheme_len as usize) }; builder = builder.scheme(scheme_bytes); } if !authority.is_null() { let authority_bytes = unsafe { std::slice::from_raw_parts(authority, authority_len as usize) }; builder = builder.authority(authority_bytes); } if !path_and_query.is_null() { let path_and_query_bytes = unsafe { std::slice::from_raw_parts(path_and_query, path_and_query_len as usize) }; builder = builder.path_and_query(path_and_query_bytes); } match builder.build() { Ok(u) => { *unsafe { &mut *req }.0.uri_mut() = u; hyper_code::HYPERE_OK }, Err(_) => { hyper_code::HYPERE_INVALID_ARG } } } } ffi_fn! { /// Set the preferred HTTP version of the request. /// /// The version value should be one of the `HYPER_HTTP_VERSION_` constants. /// /// Note that this won't change the major HTTP version of the connection, /// since that is determined at the handshake step. fn hyper_request_set_version(req: *mut hyper_request, version: c_int) -> hyper_code { use http::Version; let req = non_null!(&mut *req ?= hyper_code::HYPERE_INVALID_ARG); *req.0.version_mut() = match version { super::HYPER_HTTP_VERSION_NONE => Version::HTTP_11, super::HYPER_HTTP_VERSION_1_0 => Version::HTTP_10, super::HYPER_HTTP_VERSION_1_1 => Version::HTTP_11, super::HYPER_HTTP_VERSION_2 => Version::HTTP_2, _ => { // We don't know this version return hyper_code::HYPERE_INVALID_ARG; } }; hyper_code::HYPERE_OK } } ffi_fn! { /// Gets a reference to the HTTP headers of this request /// /// This is not an owned reference, so it should not be accessed after the /// `hyper_request` has been consumed. fn hyper_request_headers(req: *mut hyper_request) -> *mut hyper_headers { hyper_headers::get_or_default(unsafe { &mut *req }.0.extensions_mut()) } ?= std::ptr::null_mut() } ffi_fn! { /// Set the body of the request. /// /// The default is an empty body. /// /// This takes ownership of the `hyper_body *`, you must not use it or /// free it after setting it on the request. fn hyper_request_set_body(req: *mut hyper_request, body: *mut hyper_body) -> hyper_code { let body = non_null!(Box::from_raw(body) ?= hyper_code::HYPERE_INVALID_ARG); let req = non_null!(&mut *req ?= hyper_code::HYPERE_INVALID_ARG); *req.0.body_mut() = body.0; hyper_code::HYPERE_OK } } ffi_fn! { /// Set an informational (1xx) response callback. /// /// The callback is called each time hyper receives an informational (1xx) /// response for this request. /// /// The third argument is an opaque user data pointer, which is passed to /// the callback each time. /// /// The callback is passed the `void *` data pointer, and a /// `hyper_response *` which can be inspected as any other response. The /// body of the response will always be empty. /// /// NOTE: The `hyper_response *` is just borrowed data, and will not /// be valid after the callback finishes. You must copy any data you wish /// to persist. fn hyper_request_on_informational(req: *mut hyper_request, callback: hyper_request_on_informational_callback, data: *mut c_void) -> hyper_code { let ext = OnInformational { func: callback, data: UserDataPointer(data), }; let req = non_null!(&mut *req ?= hyper_code::HYPERE_INVALID_ARG); req.0.extensions_mut().insert(ext); hyper_code::HYPERE_OK } } impl hyper_request { pub(super) fn finalize_request(&mut self) { if let Some(headers) = self.0.extensions_mut().remove::() { *self.0.headers_mut() = headers.headers; self.0.extensions_mut().insert(headers.orig_casing); self.0.extensions_mut().insert(headers.orig_order); } } } // ===== impl hyper_response ===== ffi_fn! { /// Free an HTTP response after using it. fn hyper_response_free(resp: *mut hyper_response) { drop(non_null!(Box::from_raw(resp) ?= ())); } } ffi_fn! { /// Get the HTTP-Status code of this response. /// /// It will always be within the range of 100-599. fn hyper_response_status(resp: *const hyper_response) -> u16 { non_null!(&*resp ?= 0).0.status().as_u16() } } ffi_fn! { /// Get a pointer to the reason-phrase of this response. /// /// This buffer is not null-terminated. /// /// This buffer is owned by the response, and should not be used after /// the response has been freed. /// /// Use `hyper_response_reason_phrase_len()` to get the length of this /// buffer. fn hyper_response_reason_phrase(resp: *const hyper_response) -> *const u8 { non_null!(&*resp ?= std::ptr::null()).reason_phrase().as_ptr() } ?= std::ptr::null() } ffi_fn! { /// Get the length of the reason-phrase of this response. /// /// Use `hyper_response_reason_phrase()` to get the buffer pointer. fn hyper_response_reason_phrase_len(resp: *const hyper_response) -> size_t { non_null!(&*resp ?= 0).reason_phrase().len() } } 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 { let resp = non_null!(&*resp ?= std::ptr::null()); match 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. /// /// The returned value could be: /// /// - `HYPER_HTTP_VERSION_1_0` /// - `HYPER_HTTP_VERSION_1_1` /// - `HYPER_HTTP_VERSION_2` /// - `HYPER_HTTP_VERSION_NONE` if newer (or older). fn hyper_response_version(resp: *const hyper_response) -> c_int { use http::Version; match non_null!(&*resp ?= 0).0.version() { Version::HTTP_10 => super::HYPER_HTTP_VERSION_1_0, Version::HTTP_11 => super::HYPER_HTTP_VERSION_1_1, Version::HTTP_2 => super::HYPER_HTTP_VERSION_2, _ => super::HYPER_HTTP_VERSION_NONE, } } } ffi_fn! { /// Gets a reference to the HTTP headers of this response. /// /// This is not an owned reference, so it should not be accessed after the /// `hyper_response` has been freed. fn hyper_response_headers(resp: *mut hyper_response) -> *mut hyper_headers { hyper_headers::get_or_default(unsafe { &mut *resp }.0.extensions_mut()) } ?= std::ptr::null_mut() } ffi_fn! { /// Take ownership of the body of this response. /// /// It is safe to free the response even after taking ownership of its body. fn hyper_response_body(resp: *mut hyper_response) -> *mut hyper_body { let body = std::mem::take(non_null!(&mut *resp ?= std::ptr::null_mut()).0.body_mut()); Box::into_raw(Box::new(hyper_body(body))) } ?= std::ptr::null_mut() } impl hyper_response { pub(super) fn wrap(mut resp: Response) -> hyper_response { let headers = std::mem::take(resp.headers_mut()); let orig_casing = resp .extensions_mut() .remove::() .unwrap_or_else(HeaderCaseMap::default); let orig_order = resp .extensions_mut() .remove::() .unwrap_or_else(OriginalHeaderOrder::default); resp.extensions_mut().insert(hyper_headers { headers, orig_casing, orig_order, }); hyper_response(resp) } fn reason_phrase(&self) -> &[u8] { if let Some(reason) = self.0.extensions().get::() { return reason.as_bytes(); } if let Some(reason) = self.0.status().canonical_reason() { return reason.as_bytes(); } &[] } } unsafe impl AsTaskType for hyper_response { fn as_task_type(&self) -> hyper_task_return_type { hyper_task_return_type::HYPER_TASK_RESPONSE } } // ===== impl Headers ===== type hyper_headers_foreach_callback = extern "C" fn(*mut c_void, *const u8, size_t, *const u8, size_t) -> c_int; impl hyper_headers { pub(super) fn get_or_default(ext: &mut http::Extensions) -> &mut hyper_headers { if let None = ext.get_mut::() { ext.insert(hyper_headers::default()); } ext.get_mut::().unwrap() } } ffi_fn! { /// Iterates the headers passing each name and value pair to the callback. /// /// The `userdata` pointer is also passed to the callback. /// /// The callback should return `HYPER_ITER_CONTINUE` to keep iterating, or /// `HYPER_ITER_BREAK` to stop. fn hyper_headers_foreach(headers: *const hyper_headers, func: hyper_headers_foreach_callback, userdata: *mut c_void) { let headers = non_null!(&*headers ?= ()); // For each header name/value pair, there may be a value in the casemap // that corresponds to the HeaderValue. So, we iterator all the keys, // and for each one, try to pair the originally cased name with the value. // // TODO: consider adding http::HeaderMap::entries() iterator let mut ordered_iter = headers.orig_order.get_in_order().peekable(); if ordered_iter.peek().is_some() { for (name, idx) in ordered_iter { let (name_ptr, name_len) = if let Some(orig_name) = headers.orig_casing.get_all(name).nth(*idx) { (orig_name.as_ref().as_ptr(), orig_name.as_ref().len()) } else { ( name.as_str().as_bytes().as_ptr(), name.as_str().as_bytes().len(), ) }; let val_ptr; let val_len; if let Some(value) = headers.headers.get_all(name).iter().nth(*idx) { val_ptr = value.as_bytes().as_ptr(); val_len = value.as_bytes().len(); } else { // Stop iterating, something has gone wrong. return; } if HYPER_ITER_CONTINUE != func(userdata, name_ptr, name_len, val_ptr, val_len) { return; } } } else { for name in headers.headers.keys() { let mut names = headers.orig_casing.get_all(name); for value in headers.headers.get_all(name) { let (name_ptr, name_len) = if let Some(orig_name) = names.next() { (orig_name.as_ref().as_ptr(), orig_name.as_ref().len()) } else { ( name.as_str().as_bytes().as_ptr(), name.as_str().as_bytes().len(), ) }; let val_ptr = value.as_bytes().as_ptr(); let val_len = value.as_bytes().len(); if HYPER_ITER_CONTINUE != func(userdata, name_ptr, name_len, val_ptr, val_len) { return; } } } } } } ffi_fn! { /// Sets the header with the provided name to the provided value. /// /// This overwrites any previous value set for the header. fn hyper_headers_set(headers: *mut hyper_headers, name: *const u8, name_len: size_t, value: *const u8, value_len: size_t) -> hyper_code { let headers = non_null!(&mut *headers ?= hyper_code::HYPERE_INVALID_ARG); match unsafe { raw_name_value(name, name_len, value, value_len) } { Ok((name, value, orig_name)) => { headers.headers.insert(&name, value); headers.orig_casing.insert(name.clone(), orig_name.clone()); headers.orig_order.insert(name); hyper_code::HYPERE_OK } Err(code) => code, } } } ffi_fn! { /// Adds the provided value to the list of the provided name. /// /// If there were already existing values for the name, this will append the /// new value to the internal list. fn hyper_headers_add(headers: *mut hyper_headers, name: *const u8, name_len: size_t, value: *const u8, value_len: size_t) -> hyper_code { let headers = non_null!(&mut *headers ?= hyper_code::HYPERE_INVALID_ARG); match unsafe { raw_name_value(name, name_len, value, value_len) } { Ok((name, value, orig_name)) => { headers.headers.append(&name, value); headers.orig_casing.append(&name, orig_name.clone()); headers.orig_order.append(name); hyper_code::HYPERE_OK } Err(code) => code, } } } impl Default for hyper_headers { fn default() -> Self { Self { headers: Default::default(), orig_casing: HeaderCaseMap::default(), orig_order: OriginalHeaderOrder::default(), } } } unsafe fn raw_name_value( name: *const u8, name_len: size_t, value: *const u8, value_len: size_t, ) -> Result<(HeaderName, HeaderValue, Bytes), hyper_code> { let name = std::slice::from_raw_parts(name, name_len); let orig_name = Bytes::copy_from_slice(name); let name = match HeaderName::from_bytes(name) { Ok(name) => name, Err(_) => return Err(hyper_code::HYPERE_INVALID_ARG), }; let value = std::slice::from_raw_parts(value, value_len); let value = match HeaderValue::from_bytes(value) { Ok(val) => val, Err(_) => return Err(hyper_code::HYPERE_INVALID_ARG), }; Ok((name, value, orig_name)) } // ===== impl OnInformational ===== impl OnInformational { pub(crate) fn call(&mut self, resp: Response) { let mut resp = hyper_response::wrap(resp); (self.func)(self.data.0, &mut resp); } } #[cfg(test)] mod tests { use super::*; #[test] fn test_headers_foreach_cases_preserved() { let mut headers = hyper_headers::default(); let name1 = b"Set-CookiE"; let value1 = b"a=b"; hyper_headers_add( &mut headers, name1.as_ptr(), name1.len(), value1.as_ptr(), value1.len(), ); let name2 = b"SET-COOKIE"; let value2 = b"c=d"; hyper_headers_add( &mut headers, name2.as_ptr(), name2.len(), value2.as_ptr(), value2.len(), ); let mut vec = Vec::::new(); hyper_headers_foreach(&headers, concat, &mut vec as *mut _ as *mut c_void); assert_eq!(vec, b"Set-CookiE: a=b\r\nSET-COOKIE: c=d\r\n"); extern "C" fn concat( vec: *mut c_void, name: *const u8, name_len: usize, value: *const u8, value_len: usize, ) -> c_int { unsafe { let vec = &mut *(vec as *mut Vec); let name = std::slice::from_raw_parts(name, name_len); let value = std::slice::from_raw_parts(value, value_len); vec.extend(name); vec.extend(b": "); vec.extend(value); vec.extend(b"\r\n"); } HYPER_ITER_CONTINUE } } #[cfg(all(feature = "http1", feature = "ffi"))] #[test] fn test_headers_foreach_order_preserved() { let mut headers = hyper_headers::default(); let name1 = b"Set-CookiE"; let value1 = b"a=b"; hyper_headers_add( &mut headers, name1.as_ptr(), name1.len(), value1.as_ptr(), value1.len(), ); let name2 = b"Content-Encoding"; let value2 = b"gzip"; hyper_headers_add( &mut headers, name2.as_ptr(), name2.len(), value2.as_ptr(), value2.len(), ); let name3 = b"SET-COOKIE"; let value3 = b"c=d"; hyper_headers_add( &mut headers, name3.as_ptr(), name3.len(), value3.as_ptr(), value3.len(), ); let mut vec = Vec::::new(); hyper_headers_foreach(&headers, concat, &mut vec as *mut _ as *mut c_void); println!("{}", std::str::from_utf8(&vec).unwrap()); assert_eq!( vec, b"Set-CookiE: a=b\r\nContent-Encoding: gzip\r\nSET-COOKIE: c=d\r\n" ); extern "C" fn concat( vec: *mut c_void, name: *const u8, name_len: usize, value: *const u8, value_len: usize, ) -> c_int { unsafe { let vec = &mut *(vec as *mut Vec); let name = std::slice::from_raw_parts(name, name_len); let value = std::slice::from_raw_parts(value, value_len); vec.extend(name); vec.extend(b": "); vec.extend(value); vec.extend(b"\r\n"); } HYPER_ITER_CONTINUE } } }