Skip to content

Commit 3624caf

Browse files
committed
Add test for creating HTTP requests
Doesn't actually perform the request yet.
1 parent d1a979e commit 3624caf

File tree

1 file changed

+283
-0
lines changed

1 file changed

+283
-0
lines changed
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
extern crate wit_bindgen;
2+
3+
wit_bindgen::generate!({
4+
inline: r"
5+
package test:test;
6+
7+
world test {
8+
include wasi:http/[email protected];
9+
}
10+
",
11+
additional_derives: [PartialEq, Eq, Hash, Clone],
12+
features:["clocks-timezone"],
13+
generate_all
14+
});
15+
16+
use wasi::http::types::{
17+
Fields, HeaderError, Request, Method, Scheme
18+
};
19+
20+
fn test_request_field_default_values(request: &Request) {
21+
assert_eq!(request.get_method(), Method::Get);
22+
assert_eq!(request.get_path_with_query(), None);
23+
assert_eq!(request.get_scheme(), None);
24+
assert_eq!(request.get_authority(), None);
25+
assert!(request.get_options().is_none());
26+
}
27+
28+
fn compute_valid_method_chars(len: usize) -> Vec<bool> {
29+
// https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6.2
30+
// field-name = token
31+
// token = 1*tchar
32+
//
33+
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
34+
// / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
35+
// / DIGIT / ALPHA
36+
// ; any VCHAR, except delimiters
37+
let mut ret = Vec::<bool>::new();
38+
ret.resize(len, false);
39+
for ch in "#$%&'!*+-.^_`|~".chars() {
40+
ret[ch as usize] = true;
41+
}
42+
for ch in 'a'..='z' {
43+
ret[ch as usize] = true;
44+
}
45+
for ch in 'A'..='Z' {
46+
ret[ch as usize] = true;
47+
}
48+
for ch in '0'..='9' {
49+
ret[ch as usize] = true;
50+
}
51+
ret
52+
}
53+
54+
fn test_method_names(request: &Request) {
55+
for (m, name) in [(Method::Get, "GET"),
56+
(Method::Head, "HEAD"),
57+
(Method::Post, "POST"),
58+
(Method::Put, "PUT"),
59+
(Method::Delete, "DELETE"),
60+
(Method::Connect, "CONNECT"),
61+
(Method::Options, "OPTIONS"),
62+
(Method::Trace, "TRACE"),
63+
(Method::Patch, "PATCH")] {
64+
assert_eq!(request.set_method(&m), Ok(()));
65+
assert_eq!(request.get_method(), m);
66+
// https://github.com/WebAssembly/wasi-http/issues/194
67+
assert_eq!(request.set_method(&Method::Other(name.to_string())), Ok(()));
68+
assert_eq!(request.get_method(), m);
69+
}
70+
71+
request.set_method(&Method::Other("coucou".to_string())).unwrap();
72+
assert_eq!(request.get_method(), Method::Other("coucou".to_string()));
73+
74+
request.set_method(&Method::Other("".to_string())).unwrap_err();
75+
assert_eq!(request.get_method(), Method::Other("coucou".to_string()));
76+
77+
let max_codepoint_to_test = 1024;
78+
let valid = compute_valid_method_chars(max_codepoint_to_test);
79+
for ch in 0..max_codepoint_to_test {
80+
let method_name = String::from(char::from_u32(ch as u32).unwrap());
81+
if "#$%&'".contains(&method_name) {
82+
// https://github.com/bytecodealliance/wasmtime/issues/11772
83+
continue;
84+
}
85+
let method = Method::Other(method_name);
86+
if valid[ch] {
87+
assert_eq!(request.set_method(&method),
88+
Ok(()));
89+
assert_eq!(request.get_method(), method);
90+
} else {
91+
assert_eq!(request.set_method(&method),
92+
Err(()));
93+
}
94+
}
95+
}
96+
97+
fn test_schemes(request: &Request) {
98+
for s in [Scheme::Http, Scheme::Https] {
99+
assert_eq!(request.set_scheme(Some(&s)), Ok(()));
100+
assert_eq!(request.get_scheme(), Some(s));
101+
}
102+
103+
// https://github.com/WebAssembly/wasi-http/issues/194
104+
request.set_scheme(Some(&Scheme::Other("https".to_string()))).unwrap();
105+
assert_eq!(request.get_scheme(), Some(Scheme::Https));
106+
request.set_scheme(Some(&Scheme::Other("http".to_string()))).unwrap();
107+
assert_eq!(request.get_scheme(), Some(Scheme::Http));
108+
109+
// https://github.com/WebAssembly/wasi-http/issues/194
110+
// https://github.com/bytecodealliance/wasmtime/issues/11778#issuecomment-3359677161
111+
// request.set_scheme(Some(&Scheme::Other("HTTPS".to_string())));
112+
// assert_eq!(request.get_scheme(), Some(Scheme::Https));
113+
// request.set_scheme(Some(&Scheme::Other("HTTP".to_string())));
114+
// assert_eq!(request.get_scheme(), Some(Scheme::Http));
115+
116+
let max_codepoint_to_test = 1024;
117+
for ch in 0..max_codepoint_to_test {
118+
let ch = char::from_u32(ch as u32).unwrap();
119+
let scheme = Scheme::Other(String::from(ch));
120+
if "+-.0123456789~".contains(ch) {
121+
// https://github.com/bytecodealliance/wasmtime/issues/11778
122+
continue;
123+
}
124+
if ch.is_ascii_alphabetic() {
125+
assert_eq!(request.set_scheme(Some(&scheme)),
126+
Ok(()));
127+
assert_eq!(request.get_scheme(), Some(scheme));
128+
} else {
129+
assert_eq!(request.set_scheme(Some(&scheme)),
130+
Err(()));
131+
}
132+
}
133+
}
134+
135+
fn is_valid_path_char(ch: char) -> bool {
136+
// https://www.rfc-editor.org/rfc/rfc3986#section-3.3
137+
// pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
138+
ch.is_ascii_alphanumeric() || "-._~".contains(ch) // unreserved
139+
|| ch == '%' // pct-encoded
140+
|| "!$&'()*+,;=".contains(ch) // sub-delims
141+
|| ":@".contains(ch)
142+
}
143+
144+
fn test_path_with_query(request: &Request) {
145+
request.set_scheme(Some(&Scheme::Http)).unwrap();
146+
request.set_method(&Method::Get).unwrap();
147+
for abs in ["/", "/a/b/c", "/a/../../bar", "/?foo"] {
148+
request.set_path_with_query(Some(&abs.to_string()))
149+
.expect(abs);
150+
assert_eq!(request.get_path_with_query(),
151+
Some(abs.to_string()));
152+
}
153+
154+
// https://github.com/WebAssembly/wasi-http/issues/178#issuecomment-3359974132
155+
for rel in ["a/b/c", "../..", "?foo"] {
156+
request.set_path_with_query(Some(&rel.to_string()))
157+
.expect(rel);
158+
assert_eq!(request.get_path_with_query(),
159+
Some(rel.to_string()));
160+
}
161+
162+
request.set_path_with_query(Some(&"".to_string())).unwrap();
163+
assert_eq!(request.get_path_with_query(),
164+
Some("/".to_string()));
165+
166+
for ch in 0..1024 {
167+
let ch = char::from_u32(ch).unwrap();
168+
if ch != '?' && ch != '/' {
169+
let s = '/'.to_string() + &String::from(ch);
170+
if is_valid_path_char(ch) {
171+
request.set_path_with_query(Some(&s)).unwrap();
172+
assert_eq!(request.get_path_with_query(), Some(s));
173+
} else {
174+
if "\"{|}^[]\\#".contains(ch) {
175+
// https://github.com/bytecodealliance/wasmtime/issues/11779
176+
continue;
177+
}
178+
request.set_path_with_query(Some(&s)).unwrap_err();
179+
}
180+
}
181+
}
182+
183+
request.set_method(&Method::Options).unwrap();
184+
request.set_path_with_query(Some(&"".to_string())).unwrap();
185+
// https://github.com/bytecodealliance/wasmtime/issues/11780
186+
// assert_eq!(request.get_path_with_query(),
187+
// Some("".to_string()));
188+
}
189+
190+
fn is_valid_authority_char(ch: char) -> bool {
191+
// https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2
192+
// host = IP-literal / IPv4address / reg-name
193+
// reg-name = unreserved / pct-encoded / sub-delims
194+
// IPv4address is a subset of reg-name. IP-literal is IPv6: [...]
195+
ch.is_ascii_alphanumeric() || "-._~".contains(ch) // unreserved
196+
|| ch == '%' // pct-encoded
197+
|| "!$&'()*+,;=".contains(ch) // sub-delims
198+
}
199+
200+
fn test_authority(request: &Request) {
201+
for valid in [
202+
"1.2.3.4",
203+
"example.com",
204+
"localhost",
205+
"1.2.3.4:80",
206+
"example.com:80",
207+
"localhost:80",
208+
209+
210+
"user@localhost:80",
211+
"user:[email protected]:80",
212+
"user:[email protected]:80",
213+
"user:pass@localhost:80",
214+
"user:pass%20@localhost:80",
215+
// https://github.com/WebAssembly/wasi-http/issues/196
216+
// "[2001:db8::1]"
217+
] {
218+
let authority = String::from(valid);
219+
request.set_authority(Some(&authority)).unwrap();
220+
assert_eq!(request.get_authority(), Some(authority));
221+
}
222+
223+
for invalid in [
224+
"::",
225+
":@",
226+
"@@",
227+
"@:",
228+
" ",
229+
"",
230+
"#",
231+
"localhost:what",
232+
] {
233+
let authority = String::from(invalid);
234+
request.set_authority(Some(&authority)).expect_err(invalid);
235+
}
236+
237+
for ch in 0..1024 {
238+
let ch = char::from_u32(ch).unwrap();
239+
if ch != '[' {
240+
// IP-literal, which we aren't properly testing here.
241+
continue;
242+
} else if ch == '%' {
243+
// We aren't properly testing percent encoding here, and
244+
// % is invalid in a host name otherwise.
245+
continue;
246+
} else {
247+
let authority = String::from(ch);
248+
if is_valid_authority_char(ch) {
249+
request.set_authority(Some(&authority)).unwrap();
250+
assert_eq!(request.get_authority(), Some(authority));
251+
} else {
252+
request.set_authority(Some(&authority)).unwrap_err();
253+
}
254+
}
255+
}
256+
}
257+
258+
fn test_immutable_headers(headers: &Fields) {
259+
assert_eq!(headers.append("Last-Modified", b"whatever"),
260+
Err(HeaderError::Immutable));
261+
}
262+
263+
fn test_headers_same(left: &Fields, right: &Fields) {
264+
assert_eq!(left.copy_all(), right.copy_all());
265+
}
266+
267+
fn main() {
268+
let headers = Fields::new();
269+
headers.append("Last-Modified", b"whatever").unwrap();
270+
let contents = None;
271+
let (_, trailers_rx) = wit_future::new(|| Ok(None));
272+
let options = None;
273+
let headers_copy = headers.clone();
274+
let (request, _sent_future) = Request::new(headers, contents, trailers_rx, options);
275+
276+
test_request_field_default_values(&request);
277+
test_schemes(&request);
278+
test_method_names(&request);
279+
test_path_with_query(&request);
280+
test_authority(&request);
281+
test_immutable_headers(&request.get_headers());
282+
test_headers_same(&request.get_headers(), &headers_copy);
283+
}

0 commit comments

Comments
 (0)