pub use fastly_shared::CacheOverride;
use super::pending::handle::PendingRequestHandle;
use crate::abi::{self, FastlyStatus, MultiValueHostcallError};
use crate::error::BufferSizeError;
use crate::handle::{BodyHandle, ResponseHandle, StreamingBodyHandle};
use crate::http::request::SendErrorCause;
use bytes::{BufMut, BytesMut};
use http::header::{HeaderName, HeaderValue};
use http::{Method, Uri, Version};
use lazy_static::lazy_static;
use std::convert::{TryFrom, TryInto};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::sync::atomic::{AtomicBool, Ordering};
use url::Url;
#[allow(unused)]
use super::Request;
#[derive(Debug, Eq, Hash, PartialEq)]
#[repr(transparent)]
pub struct RequestHandle {
handle: u32,
}
pub(crate) static GOT_CLIENT_REQ: AtomicBool = AtomicBool::new(false);
impl RequestHandle {
pub const INVALID: Self = RequestHandle {
handle: fastly_shared::INVALID_REQUEST_HANDLE,
};
pub fn is_valid(&self) -> bool {
!self.is_invalid()
}
pub fn is_invalid(&self) -> bool {
self.handle == Self::INVALID.handle
}
pub(crate) fn as_u32(&self) -> u32 {
self.handle
}
pub(crate) fn as_u32_mut(&mut self) -> &mut u32 {
&mut self.handle
}
pub(crate) fn set_got_client() {
if GOT_CLIENT_REQ.swap(true, Ordering::SeqCst) {
panic!("cannot get more than one handle to the client request per execution",);
}
}
pub fn from_client() -> Self {
Self::set_got_client();
let mut handle = RequestHandle::INVALID;
let status = unsafe {
abi::fastly_http_req::body_downstream_get(handle.as_u32_mut(), std::ptr::null_mut())
};
match status.result().map(|_| handle) {
Ok(h) if h.is_valid() => h,
_ => panic!("fastly_http_req::body_downstream_get failed"),
}
}
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
let mut handle = RequestHandle::INVALID;
let status = unsafe { abi::fastly_http_req::new(handle.as_u32_mut()) };
match status.result().map(|_| handle) {
Ok(h) if h.is_valid() => h,
_ => panic!("fastly_http_req::new failed"),
}
}
pub fn get_header_names<'a>(
&'a self,
buf_size: usize,
) -> impl Iterator<Item = Result<HeaderName, BufferSizeError>> + 'a {
self.get_header_names_impl(buf_size, Some(buf_size))
}
pub(crate) fn get_header_names_impl<'a>(
&'a self,
mut initial_buf_size: usize,
max_buf_size: Option<usize>,
) -> impl Iterator<Item = Result<HeaderName, BufferSizeError>> + 'a {
if let Some(max) = max_buf_size {
initial_buf_size = std::cmp::min(initial_buf_size, max);
}
abi::MultiValueHostcall::new(
b'\0',
initial_buf_size,
max_buf_size,
move |buf, buf_size, cursor, ending_cursor, nwritten| unsafe {
abi::fastly_http_req::header_names_get(
self.as_u32(),
buf,
buf_size,
cursor,
ending_cursor,
nwritten,
)
},
)
.map(move |res| {
use MultiValueHostcallError::{BufferTooSmall, ClosureError};
match res {
Ok(name_bytes) => Ok(HeaderName::from_bytes(&name_bytes).unwrap()),
Err(BufferTooSmall { needed_buf_size }) => Err(BufferSizeError::header_name(
initial_buf_size,
needed_buf_size,
)),
Err(ClosureError(e)) => {
panic!("fastly_http_req::header_names_get returned error: {:?}", e)
}
}
})
}
pub fn get_header_values<'a>(
&'a self,
name: &'a HeaderName,
buf_size: usize,
) -> impl Iterator<Item = Result<HeaderValue, BufferSizeError>> + 'a {
self.get_header_values_impl(name, buf_size, Some(buf_size))
}
pub(crate) fn get_header_values_impl<'a>(
&'a self,
name: &'a HeaderName,
mut initial_buf_size: usize,
max_buf_size: Option<usize>,
) -> impl Iterator<Item = Result<HeaderValue, BufferSizeError>> + 'a {
if let Some(max) = max_buf_size {
initial_buf_size = std::cmp::min(initial_buf_size, max);
}
abi::MultiValueHostcall::new(
b'\0',
initial_buf_size,
max_buf_size,
move |buf, buf_size, cursor, ending_cursor, nwritten| unsafe {
let name: &[u8] = name.as_ref();
abi::fastly_http_req::header_values_get(
self.as_u32(),
name.as_ptr(),
name.len(),
buf,
buf_size,
cursor,
ending_cursor,
nwritten,
)
},
)
.map(move |res| {
use MultiValueHostcallError::{BufferTooSmall, ClosureError};
match res {
Ok(value_bytes) => {
let header_value =
unsafe { HeaderValue::from_maybe_shared_unchecked(value_bytes) };
Ok(header_value)
}
Err(BufferTooSmall { needed_buf_size }) => Err(BufferSizeError::header_value(
initial_buf_size,
needed_buf_size,
)),
Err(ClosureError(e)) => {
panic!("fastly_http_req::header_values_get returned error: {:?}", e)
}
}
})
}
pub fn set_header_values<'a, I>(&mut self, name: &HeaderName, values: I)
where
I: IntoIterator<Item = &'a HeaderValue>,
{
let mut buf = vec![];
for value in values {
buf.put(value.as_bytes());
buf.put_u8(b'\0');
}
let name: &[u8] = name.as_ref();
unsafe {
abi::fastly_http_req::header_values_set(
self.as_u32(),
name.as_ptr(),
name.len(),
buf.as_ptr(),
buf.len(),
)
}
.result()
.expect("fastly_http_req::header_values_set failed");
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../../docs/snippets/handle-get-header-value.md")
)]
#[cfg_attr(
not(feature = "unstable-doc"),
doc = "Placeholder doc to suppress warnings without `unstable-doc` feature"
)]
pub fn get_header_value(
&self,
name: &HeaderName,
max_len: usize,
) -> Result<Option<HeaderValue>, BufferSizeError> {
let name: &[u8] = name.as_ref();
let mut buf = BytesMut::with_capacity(max_len);
let mut nwritten = 0;
let status = unsafe {
abi::fastly_http_req::header_value_get(
self.as_u32(),
name.as_ptr(),
name.len(),
buf.as_mut_ptr(),
buf.capacity(),
&mut nwritten,
)
};
match status.result().map(|_| nwritten) {
Ok(nwritten) => {
assert!(nwritten <= buf.capacity(), "hostcall wrote too many bytes");
unsafe {
buf.set_len(nwritten);
}
let value = HeaderValue::from_bytes(&buf).expect("bytes from host are valid");
Ok(Some(value))
}
Err(FastlyStatus::INVAL) => Ok(None),
Err(FastlyStatus::BUFLEN) => Err(BufferSizeError::header_value(max_len, nwritten)),
_ => panic!("fastly_http_req::header_value_get returned error"),
}
}
pub fn insert_header(&mut self, name: &HeaderName, value: &HeaderValue) {
let name_bytes: &[u8] = name.as_ref();
let value_bytes: &[u8] = value.as_ref();
let status = unsafe {
abi::fastly_http_req::header_insert(
self.as_u32(),
name_bytes.as_ptr(),
name_bytes.len(),
value_bytes.as_ptr(),
value_bytes.len(),
)
};
if status.is_err() {
panic!("fastly_http_req::header_insert returned error");
}
}
pub fn append_header(&mut self, name: &HeaderName, value: &HeaderValue) {
let name_bytes: &[u8] = name.as_ref();
let value_bytes: &[u8] = value.as_ref();
unsafe {
abi::fastly_http_req::header_append(
self.as_u32(),
name_bytes.as_ptr(),
name_bytes.len(),
value_bytes.as_ptr(),
value_bytes.len(),
)
}
.result()
.expect("fastly_http_req::header_append returned error");
}
pub fn remove_header(&mut self, name: &HeaderName) -> bool {
let name_bytes: &[u8] = name.as_ref();
let status = unsafe {
abi::fastly_http_req::header_remove(
self.as_u32(),
name_bytes.as_ptr(),
name_bytes.len(),
)
};
match status.result() {
Ok(_) => true,
Err(FastlyStatus::INVAL) => false,
_ => panic!("fastly_http_req::header_remove returned error"),
}
}
pub fn get_version(&self) -> Version {
let mut version = 0;
let status = unsafe { abi::fastly_http_req::version_get(self.as_u32(), &mut version) };
if status.is_err() {
panic!("fastly_http_req::version_get failed");
} else {
abi::HttpVersion::try_from(version)
.map(Into::into)
.expect("HTTP version must be valid")
}
}
pub fn set_version(&mut self, v: Version) {
unsafe {
abi::fastly_http_req::version_set(self.as_u32(), abi::HttpVersion::from(v) as u32)
}
.result()
.expect("fastly_http_req::version_get failed");
}
pub fn get_method(&self, max_length: usize) -> Result<Method, BufferSizeError> {
let mut method_bytes = Vec::with_capacity(max_length);
let mut nwritten = 0;
let status = unsafe {
abi::fastly_http_req::method_get(
self.as_u32(),
method_bytes.as_mut_ptr(),
method_bytes.capacity(),
&mut nwritten,
)
};
match status.result() {
Ok(_) => {
assert!(
nwritten <= method_bytes.capacity(),
"fastly_http_req::method_get wrote too many bytes"
);
unsafe {
method_bytes.set_len(nwritten);
}
Ok(Method::from_bytes(&method_bytes).expect("HTTP method must be valid"))
}
Err(FastlyStatus::BUFLEN) => Err(BufferSizeError::http_method(max_length, nwritten)),
_ => panic!("fastly_http_req::method_get failed"),
}
}
pub(crate) fn get_method_impl(
&self,
mut initial_buf_size: usize,
max_buf_size: Option<usize>,
) -> Result<Method, BufferSizeError> {
if let Some(max) = max_buf_size {
initial_buf_size = std::cmp::min(initial_buf_size, max);
}
match self.get_method(initial_buf_size) {
Ok(method) => Ok(method),
Err(err) => {
if let Some(max) = max_buf_size {
if err.needed_buf_size <= max {
self.get_method(err.needed_buf_size)
} else {
Err(err)
}
} else {
self.get_method(err.needed_buf_size)
}
}
}
}
pub fn set_method(&self, method: &Method) {
let method_bytes = method.as_str().as_bytes();
unsafe {
abi::fastly_http_req::method_set(
self.as_u32(),
method_bytes.as_ptr(),
method_bytes.len(),
)
}
.result()
.expect("fastly_http_req::method_set failed");
}
pub fn get_url(&self, max_length: usize) -> Result<Url, BufferSizeError> {
let mut url_bytes = BytesMut::with_capacity(max_length);
let mut nwritten = 0;
let status = unsafe {
abi::fastly_http_req::uri_get(
self.as_u32(),
url_bytes.as_mut_ptr(),
url_bytes.capacity(),
&mut nwritten,
)
};
match status.result() {
Ok(_) => {
assert!(
nwritten <= url_bytes.capacity(),
"fastly_http_req::uri_get wrote too many bytes"
);
unsafe {
url_bytes.set_len(nwritten);
}
let url_str =
std::str::from_utf8(&url_bytes).expect("host provided invalid request url");
let url = Url::parse(url_str).expect("host provided invalid request url");
Ok(url)
}
Err(FastlyStatus::BUFLEN) => Err(BufferSizeError::url(max_length, nwritten)),
_ => panic!("fastly_http_req::uri_get failed"),
}
}
pub(crate) fn get_url_impl(
&self,
mut initial_buf_size: usize,
max_buf_size: Option<usize>,
) -> Result<Url, BufferSizeError> {
if let Some(max) = max_buf_size {
initial_buf_size = std::cmp::min(initial_buf_size, max);
}
match self.get_url(initial_buf_size) {
Ok(url) => Ok(url),
Err(err) => {
if let Some(max) = max_buf_size {
if err.needed_buf_size <= max {
self.get_url(err.needed_buf_size)
} else {
Err(err)
}
} else {
self.get_url(err.needed_buf_size)
}
}
}
}
#[deprecated(since = "0.6.0", note = "replaced by `RequestHandle::get_url()`")]
pub fn get_uri(&self, max_length: usize) -> Result<Uri, BufferSizeError> {
Ok(self
.get_url(max_length)?
.as_str()
.parse()
.expect("host provided invalid request url"))
}
pub fn set_url(&mut self, url: &Url) {
let url_bytes = url.as_str().as_bytes();
unsafe {
abi::fastly_http_req::uri_set(self.as_u32(), url_bytes.as_ptr(), url_bytes.len())
}
.result()
.expect("fastly_http_req::uri_set failed");
}
#[deprecated(since = "0.6.0", note = "replaced by `RequestHandle::set_url()`")]
pub fn set_uri(&mut self, uri: &Uri) {
let uri_bytes = uri.to_string().into_bytes();
unsafe {
abi::fastly_http_req::uri_set(self.as_u32(), uri_bytes.as_ptr(), uri_bytes.len())
}
.result()
.expect("fastly_http_req::uri_set failed");
}
pub fn send(
self,
body: BodyHandle,
backend: &str,
) -> Result<(ResponseHandle, BodyHandle), SendErrorCause> {
let mut resp_handle = ResponseHandle::INVALID;
let mut resp_body_handle = BodyHandle::INVALID;
let status = unsafe {
abi::fastly_http_req::send(
self.as_u32(),
body.as_u32(),
backend.as_ptr(),
backend.len(),
resp_handle.as_u32_mut(),
resp_body_handle.as_u32_mut(),
)
};
if status.is_err() {
Err(SendErrorCause::status(status))
} else if resp_handle.is_invalid() || resp_body_handle.is_invalid() {
panic!("fastly_http_req::send returned invalid handles");
} else {
Ok((resp_handle, resp_body_handle))
}
}
pub fn send_async(
self,
body: BodyHandle,
backend: &str,
) -> Result<PendingRequestHandle, SendErrorCause> {
let mut pending_req_handle = PendingRequestHandle::INVALID;
let status = unsafe {
abi::fastly_http_req::send_async(
self.as_u32(),
body.as_u32(),
backend.as_ptr(),
backend.len(),
pending_req_handle.as_u32_mut(),
)
};
if status.is_err() {
Err(SendErrorCause::status(status))
} else if pending_req_handle.is_invalid() {
panic!("fastly_http_req::send_async returned an invalid handle");
} else {
Ok(pending_req_handle)
}
}
pub fn send_async_streaming(
self,
body: BodyHandle,
backend: &str,
) -> Result<(StreamingBodyHandle, PendingRequestHandle), SendErrorCause> {
let mut pending_req_handle = PendingRequestHandle::INVALID;
let status = unsafe {
abi::fastly_http_req::send_async_streaming(
self.as_u32(),
body.as_u32(),
backend.as_ptr(),
backend.len(),
pending_req_handle.as_u32_mut(),
)
};
if status.is_err() {
Err(SendErrorCause::status(status))
} else if pending_req_handle.is_invalid() {
panic!("fastly_http_req::send_async_streaming returned an invalid handle");
} else {
Ok((
StreamingBodyHandle::from_body_handle(&body),
pending_req_handle,
))
}
}
pub fn set_cache_override(&mut self, cache_override: &CacheOverride) {
let (tag, ttl, swr, sk) = cache_override.to_abi();
let (sk_ptr, sk_len) = match sk {
Some(sk) if sk.len() > 0 => (sk.as_ptr(), sk.len()),
_ => (std::ptr::null(), 0),
};
unsafe {
abi::fastly_http_req::cache_override_v2_set(
self.as_u32(),
tag,
ttl,
swr,
sk_ptr,
sk_len,
)
}
.result()
.expect("fastly_http_req::cache_override_set failed");
}
}
pub fn client_request_and_body() -> (RequestHandle, BodyHandle) {
RequestHandle::set_got_client();
BodyHandle::set_got_client();
let result = {
let mut req_handle = RequestHandle::INVALID;
let mut body_handle = BodyHandle::INVALID;
let status = unsafe {
abi::fastly_http_req::body_downstream_get(
req_handle.as_u32_mut(),
body_handle.as_u32_mut(),
)
};
status.result().map(|_| (req_handle, body_handle))
};
match result {
Ok((r, b)) if r.is_valid() && b.is_valid() => (r, b),
_ => panic!("fastly_http_req::body_downstream_get failed"),
}
}
pub fn client_original_header_names(
buf_size: usize,
) -> impl Iterator<Item = Result<String, BufferSizeError>> {
client_original_header_names_impl(buf_size, Some(buf_size))
}
pub(crate) fn client_original_header_names_impl(
mut initial_buf_size: usize,
max_buf_size: Option<usize>,
) -> impl Iterator<Item = Result<String, BufferSizeError>> {
if let Some(max) = max_buf_size {
initial_buf_size = std::cmp::min(initial_buf_size, max);
}
abi::MultiValueHostcall::new(
b'\0',
initial_buf_size,
max_buf_size,
move |buf, buf_size, cursor, ending_cursor, nwritten| unsafe {
abi::fastly_http_req::original_header_names_get(
buf,
buf_size,
cursor,
ending_cursor,
nwritten,
)
},
)
.map(move |res| {
use MultiValueHostcallError::{BufferTooSmall, ClosureError};
match res {
Ok(name_bytes) => Ok(String::from_utf8(name_bytes.to_vec()).unwrap()),
Err(BufferTooSmall { needed_buf_size }) => Err(BufferSizeError::header_value(
initial_buf_size,
needed_buf_size,
)),
Err(ClosureError(e)) => {
panic!("fastly_http_req::header_values_get returned error: {:?}", e)
}
}
})
}
pub fn client_original_header_count() -> u32 {
let mut count = 0;
let status = unsafe { abi::fastly_http_req::original_header_count(&mut count) };
if status.is_err() || count == 0 {
panic!("downstream_original_header_count failed")
}
count
}
pub fn client_ip_addr() -> Option<IpAddr> {
let mut octets = [0; 16];
let mut nwritten = 0;
let status = unsafe {
abi::fastly_http_req::downstream_client_ip_addr(octets.as_mut_ptr(), &mut nwritten)
};
if status.is_err() {
panic!("downstream_client_ip_addr failed");
}
match nwritten {
4 => {
let octets: [u8; 4] = octets[0..4]
.try_into()
.expect("octets is at least 4 bytes long");
let addr: Ipv4Addr = octets.into();
Some(addr.into())
}
16 => {
let addr: Ipv6Addr = octets.into();
Some(addr.into())
}
_ => panic!("downstream_client_ip_addr wrote an unexpected number of bytes"),
}
}
pub fn client_tls_client_hello() -> Option<&'static [u8]> {
lazy_static! {
static ref CLIENT_HELLO: Vec<u8> = {
get_bytes_adaptive(
abi::fastly_http_req::downstream_tls_client_hello,
512,
"downstream TLS ClientHello",
)
};
}
Some(CLIENT_HELLO.as_slice())
}
pub fn client_tls_cipher_openssl_name() -> Option<&'static str> {
lazy_static! {
static ref OPENSSL_NAME: String = {
let name = "downstream TLS cipher OpenSSL name";
let buf = get_bytes_adaptive(
abi::fastly_http_req::downstream_tls_cipher_openssl_name,
128,
name,
);
String::from_utf8(buf).unwrap_or_else(|_| panic!("{} must be valid UTF-8", name))
};
}
Some(OPENSSL_NAME.as_str())
}
pub fn client_tls_protocol() -> Option<&'static str> {
lazy_static! {
static ref PROTOCOL: String = {
let name = "downstream TLS cipher protocol";
let buf = get_bytes_adaptive(abi::fastly_http_req::downstream_tls_protocol, 32, name);
String::from_utf8(buf).unwrap_or_else(|_| panic!("{} must be valid UTF-8", name))
};
}
Some(PROTOCOL.as_str())
}
fn get_bytes_adaptive(
hostcall: unsafe extern "C" fn(*mut u8, usize, *mut usize) -> FastlyStatus,
default_buf_size: usize,
name: &str,
) -> Vec<u8> {
let mut buf = Vec::with_capacity(default_buf_size);
let mut nwritten = 0;
let status = unsafe { hostcall(buf.as_mut_ptr(), buf.capacity(), &mut nwritten) };
match status {
FastlyStatus::OK => (),
FastlyStatus::BUFLEN if nwritten != 0 => {
buf.reserve_exact(nwritten);
let status = unsafe { hostcall(buf.as_mut_ptr(), buf.capacity(), &mut nwritten) };
if status.is_err() {
panic!("couldn't get the {}", name);
}
}
_ => panic!("couldn't get the {}", name),
};
unsafe {
buf.set_len(nwritten);
}
buf
}