| 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<Body>); |
| |
| /// An HTTP response. |
| pub struct hyper_response(pub(super) Response<Body>); |
| |
| /// 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::<hyper_headers>() { |
| *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::<RawHeaders>() { |
| 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<Body>) -> hyper_response { |
| let headers = std::mem::take(resp.headers_mut()); |
| let orig_casing = resp |
| .extensions_mut() |
| .remove::<HeaderCaseMap>() |
| .unwrap_or_else(HeaderCaseMap::default); |
| let orig_order = resp |
| .extensions_mut() |
| .remove::<OriginalHeaderOrder>() |
| .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::<ReasonPhrase>() { |
| 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::<hyper_headers>() { |
| ext.insert(hyper_headers::default()); |
| } |
| |
| ext.get_mut::<hyper_headers>().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<Body>) { |
| 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::<u8>::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<u8>); |
| 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::<u8>::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<u8>); |
| 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 |
| } |
| } |
| } |