wasmtime_wasi_http/
lib.rs1#![deny(missing_docs)]
8#![doc(test(attr(deny(warnings))))]
9#![doc(test(attr(allow(dead_code, unused_variables, unused_mut))))]
10#![cfg_attr(docsrs, feature(doc_cfg))]
11
12use http::{HeaderName, header};
13
14#[cfg(any(feature = "p2", feature = "p3"))]
15mod authority;
16#[cfg(any(feature = "p2", feature = "p3"))]
17pub(crate) use authority::parse_authority;
18
19mod ctx;
20#[cfg(feature = "default-send-request")]
21mod default_send_request;
22mod error;
23mod field_map;
24#[cfg(feature = "component-model-async")]
25pub mod handler;
26pub mod io;
27#[cfg(feature = "p2")]
28pub mod p2;
29#[cfg(feature = "p3")]
30pub mod p3;
31mod request_options;
32
33pub use ctx::*;
34#[cfg(feature = "default-send-request")]
35pub use default_send_request::*;
36pub use error::*;
37pub use field_map::*;
38pub use request_options::*;
39
40#[cfg(any(feature = "p2", feature = "p3"))]
44fn get_content_length(headers: &http::HeaderMap) -> wasmtime::Result<Option<u64>> {
45 let mut values = headers.get_all(header::CONTENT_LENGTH).iter();
46 let Some(v) = values.next() else {
47 return Ok(None);
48 };
49 if values.next().is_some() {
52 wasmtime::bail!("multiple `content-length` header values");
53 }
54 let v = v.to_str()?;
55 if v.is_empty() || !v.bytes().all(|b| b.is_ascii_digit()) {
59 wasmtime::bail!("invalid `content-length` header value: {v:?}");
60 }
61 let v = v.parse()?;
62 Ok(Some(v))
63}
64
65#[cfg(any(feature = "p2", feature = "p3"))]
85fn parse_path_with_query(path_with_query: &str) -> Option<http::uri::PathAndQuery> {
86 if !(path_with_query.is_empty()
87 || path_with_query.starts_with('/')
88 || path_with_query.starts_with('?'))
89 {
90 return None;
91 }
92 http::uri::Builder::new()
93 .path_and_query(path_with_query)
94 .build()
95 .ok()
96 .and_then(|uri| uri.path_and_query().cloned())
97}
98
99#[cfg(all(test, any(feature = "p2", feature = "p3")))]
100mod content_length_tests {
101 use super::get_content_length;
102 use http::{HeaderMap, HeaderValue, header};
103
104 fn headers(value: &str) -> HeaderMap {
105 let mut map = HeaderMap::new();
106 map.insert(
107 header::CONTENT_LENGTH,
108 HeaderValue::from_str(value).unwrap(),
109 );
110 map
111 }
112
113 #[test]
114 fn content_length_must_be_decimal_digits() {
115 assert_eq!(get_content_length(&HeaderMap::new()).unwrap(), None);
116 assert_eq!(get_content_length(&headers("0")).unwrap(), Some(0));
117 assert_eq!(get_content_length(&headers("1234")).unwrap(), Some(1234));
118
119 assert!(get_content_length(&headers("+5")).is_err());
121 assert!(get_content_length(&headers("-5")).is_err());
122 assert!(get_content_length(&headers(" 5")).is_err());
123 assert!(get_content_length(&headers("")).is_err());
124
125 let mut map = headers("5");
127 map.append(header::CONTENT_LENGTH, HeaderValue::from_static("5"));
128 assert!(get_content_length(&map).is_err());
129 }
130}
131
132pub const DEFAULT_FORBIDDEN_HEADERS: [HeaderName; 9] = [
135 header::CONNECTION,
136 HeaderName::from_static("keep-alive"),
137 header::PROXY_AUTHENTICATE,
138 header::PROXY_AUTHORIZATION,
139 HeaderName::from_static("proxy-connection"),
140 header::TRANSFER_ENCODING,
141 header::UPGRADE,
142 header::HOST,
143 HeaderName::from_static("http2-settings"),
144];