Skip to content

Commit 4ce8997

Browse files
committed
Add tests for HTTP fields
1 parent 9b0f840 commit 4ce8997

File tree

1 file changed

+327
-0
lines changed

1 file changed

+327
-0
lines changed
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
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+
include wasi:cli/[email protected];
10+
}
11+
",
12+
additional_derives: [PartialEq, Eq, Hash, Clone],
13+
features:["clocks-timezone"],
14+
generate_all
15+
});
16+
17+
use wasi::http::types::Fields;
18+
use wasi::http::types::HeaderError;
19+
20+
fn test_empty_fields_inner(fields: Fields) {
21+
assert!(!fields.has("foo"));
22+
assert!(fields.get("foo").is_empty());
23+
assert!(fields.get_and_delete("foo").unwrap().is_empty());
24+
fields.delete("foo").unwrap();
25+
fields.delete("other").unwrap();
26+
assert!(fields.copy_all().is_empty());
27+
}
28+
29+
fn test_empty_fields() {
30+
let fields = Fields::new();
31+
let clone = fields.clone();
32+
test_empty_fields_inner(fields);
33+
test_empty_fields_inner(clone);
34+
test_empty_fields_inner(Fields::from_list(&[]).unwrap());
35+
}
36+
37+
fn test_fields_with_foo_inner(fields: Fields) {
38+
assert!(fields.has("foo"));
39+
assert_eq!(fields.get("foo"), [b"bar".to_vec()]);
40+
fields.delete("foo").unwrap();
41+
assert!(!fields.has("foo"));
42+
assert!(fields.get("foo").is_empty());
43+
44+
fields.set("foo", &[]).unwrap();
45+
assert!(!fields.has("foo"));
46+
assert!(fields.get("foo").is_empty());
47+
fields.set("foo", &[b"bar".to_vec(),
48+
b"baz".to_vec()]).unwrap();
49+
assert!(fields.has("foo"));
50+
assert_eq!(fields.get("foo"), [b"bar".to_vec(),
51+
b"baz".to_vec()]);
52+
assert_eq!(fields.get_and_delete("foo").unwrap(),
53+
[b"bar".to_vec(), b"baz".to_vec()]);
54+
assert!(fields.get_and_delete("foo").unwrap().is_empty());
55+
56+
fields.set("foo", &[b"bar".to_vec(), b"baz".to_vec()]).unwrap();
57+
assert!(fields.has("foo"));
58+
assert_eq!(fields.get("foo"), [b"bar".to_vec(),
59+
b"baz".to_vec()]);
60+
assert_eq!(fields.get_and_delete("foo").unwrap(),
61+
[b"bar".to_vec(), b"baz".to_vec()]);
62+
assert!(fields.get_and_delete("foo").unwrap().is_empty());
63+
assert!(!fields.has("foo"));
64+
65+
fields.append("foo", b"bar").unwrap();
66+
fields.append("foo", b"baz").unwrap();
67+
assert!(fields.has("foo"));
68+
assert_eq!(fields.get("foo"), [b"bar".to_vec(),
69+
b"baz".to_vec()]);
70+
assert_eq!(fields.get_and_delete("foo").unwrap(),
71+
[b"bar".to_vec(), b"baz".to_vec()]);
72+
assert!(fields.get_and_delete("foo").unwrap().is_empty());
73+
assert!(!fields.has("foo"));
74+
}
75+
76+
fn test_fields_with_foo() {
77+
let fields = Fields::from_list(&[("foo".to_string(),
78+
b"bar".to_vec())]).unwrap();
79+
let clone = fields.clone();
80+
test_fields_with_foo_inner(fields);
81+
test_fields_with_foo_inner(clone);
82+
}
83+
84+
fn test_invalid_field_name(field: &str) {
85+
let fields = Fields::new();
86+
assert!(!fields.has(field));
87+
assert!(fields.get(field).is_empty());
88+
assert_eq!(fields.delete(field),
89+
Err(HeaderError::InvalidSyntax));
90+
assert_eq!(fields.get_and_delete(field),
91+
Err(HeaderError::InvalidSyntax));
92+
assert_eq!(fields.set(field, &[b"val".to_vec()]),
93+
Err(HeaderError::InvalidSyntax));
94+
assert_eq!(fields.append(field, b"val"),
95+
Err(HeaderError::InvalidSyntax));
96+
assert!(fields.copy_all().is_empty());
97+
assert!(!fields.has(field));
98+
assert!(fields.get(field).is_empty());
99+
100+
assert_eq!(Fields::from_list(&[(field.to_string(), b"val".to_vec())])
101+
.unwrap_err(),
102+
HeaderError::InvalidSyntax);
103+
}
104+
105+
fn test_valid_field_name(field: &str) {
106+
let fields = Fields::new();
107+
assert!(!fields.has(field));
108+
assert!(fields.get(field).is_empty());
109+
fields.delete(field).unwrap();
110+
assert!(fields.get_and_delete(field).unwrap().is_empty());
111+
fields.set(field, &[b"val".to_vec()]).unwrap();
112+
fields.append(field, b"val2").unwrap();
113+
assert_eq!(fields.copy_all(),
114+
[(field.to_string(), b"val".to_vec()),
115+
(field.to_string(), b"val2".to_vec())]);
116+
assert_eq!(Fields::from_list(&[(field.to_string(), b"val".to_vec()),
117+
(field.to_string(), b"val2".to_vec())])
118+
.unwrap().copy_all(),
119+
fields.clone().copy_all());
120+
}
121+
122+
fn test_invalid_field_value(val: &[u8]) {
123+
let fields = Fields::new();
124+
assert_eq!(fields.set("foo", &[val.to_vec()]),
125+
Err(HeaderError::InvalidSyntax));
126+
assert_eq!(fields.append("foo", val),
127+
Err(HeaderError::InvalidSyntax));
128+
assert_eq!(Fields::from_list(&[("foo".to_string(), val.to_vec())])
129+
.unwrap_err(),
130+
HeaderError::InvalidSyntax);
131+
}
132+
133+
fn test_valid_field_value(val: &[u8]) {
134+
let fields = Fields::new();
135+
fields.set("foo", &[val.to_vec()]).unwrap();
136+
fields.append("foo", val).unwrap();
137+
assert_eq!(fields.copy_all(),
138+
[("foo".to_string(), val.to_vec()),
139+
("foo".to_string(), val.to_vec())]);
140+
assert_eq!(Fields::from_list(&[("foo".to_string(), val.to_vec()),
141+
("foo".to_string(), val.to_vec())])
142+
.unwrap().copy_all(),
143+
fields.clone().copy_all());
144+
}
145+
146+
fn compute_valid_field_name_chars(len: usize) -> Vec<bool> {
147+
// https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6.2
148+
// field-name = token
149+
// token = 1*tchar
150+
//
151+
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
152+
// / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
153+
// / DIGIT / ALPHA
154+
// ; any VCHAR, except delimiters
155+
let mut ret = Vec::<bool>::new();
156+
ret.resize(len, false);
157+
for ch in "!#$%&'*+-.^_`|~".chars() {
158+
ret[ch as usize] = true;
159+
}
160+
for ch in 'a'..='z' {
161+
ret[ch as usize] = true;
162+
}
163+
for ch in 'A'..='Z' {
164+
ret[ch as usize] = true;
165+
}
166+
for ch in '0'..='9' {
167+
ret[ch as usize] = true;
168+
}
169+
ret
170+
}
171+
172+
fn test_invalid_field_names() {
173+
test_invalid_field_name("");
174+
test_invalid_field_name("voilà");
175+
test_invalid_field_name("hey ho");
176+
test_invalid_field_name(" ");
177+
test_invalid_field_name(" hey");
178+
test_invalid_field_name("hey ");
179+
test_invalid_field_name("(what)");
180+
test_invalid_field_name("[what]");
181+
test_invalid_field_name("{what}");
182+
// https://github.com/bytecodealliance/wasmtime/issues/11771
183+
// test_invalid_field_name("\"foo\"");
184+
let max_codepoint_to_test = 1024;
185+
let valid = compute_valid_field_name_chars(max_codepoint_to_test);
186+
for ch in 0..max_codepoint_to_test {
187+
if !valid[ch] {
188+
let ch = char::from_u32(ch as u32).unwrap();
189+
if ch == '"' {
190+
// https://github.com/bytecodealliance/wasmtime/issues/11771
191+
continue;
192+
}
193+
test_invalid_field_name(&String::from(ch));
194+
}
195+
}
196+
}
197+
198+
fn test_valid_field_names() {
199+
let max_codepoint_to_test = 1024;
200+
let valid = compute_valid_field_name_chars(max_codepoint_to_test);
201+
for ch in 0..max_codepoint_to_test {
202+
if valid[ch] {
203+
let ch = char::from_u32(ch as u32).unwrap();
204+
if ch.is_uppercase() {
205+
// https://github.com/bytecodealliance/wasmtime/issues/11770
206+
continue;
207+
}
208+
test_valid_field_name(&String::from(ch));
209+
}
210+
}
211+
212+
test_valid_field_name("1");
213+
test_valid_field_name("142");
214+
// https://github.com/bytecodealliance/wasmtime/issues/11770
215+
// test_valid_field_name("Foo");
216+
// test_valid_field_name("ConnectionLevel142");
217+
test_valid_field_name("kebab-data-100");
218+
test_valid_field_name(str::from_utf8(&[b"f"[0]; 1024]).unwrap());
219+
}
220+
221+
fn compute_valid_field_value_bytes() -> Vec<bool> {
222+
// https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6.2
223+
// field-value = *field-content
224+
// field-content = field-vchar
225+
// [ 1*( SP / HTAB / field-vchar ) field-vchar ]
226+
// field-vchar = VCHAR / obs-text
227+
// VCHAR = %x21-7E
228+
// obs-text = %x80-FF
229+
let mut ret = Vec::<bool>::new();
230+
ret.resize(256, false);
231+
ret[' ' as usize] = true;
232+
ret['\t' as usize] = true;
233+
for ch in 0x21..=0x7e {
234+
ret[ch] = true;
235+
}
236+
for ch in 0x80..=0xff {
237+
ret[ch] = true;
238+
}
239+
ret
240+
}
241+
242+
fn test_invalid_field_values() {
243+
let valid = compute_valid_field_value_bytes();
244+
for byte in 0u8..=0xff {
245+
if !valid[byte as usize] {
246+
test_invalid_field_value(&[byte]);
247+
}
248+
}
249+
250+
test_invalid_field_value(b"\n");
251+
test_invalid_field_value(b"\r");
252+
test_invalid_field_value(b"\0");
253+
}
254+
255+
fn test_valid_field_values() {
256+
let valid = compute_valid_field_value_bytes();
257+
for byte in 0u8..=0xff {
258+
if valid[byte as usize] {
259+
test_valid_field_value(&[byte])
260+
}
261+
}
262+
263+
test_valid_field_value(b"");
264+
test_valid_field_value(b" \t \t \t \t \t ");
265+
test_valid_field_value(b"Foo");
266+
test_valid_field_value(b"ConnectionLevel142");
267+
test_valid_field_value(b"kebab-data-100");
268+
test_valid_field_value(&[b"f"[0]; 1024]);
269+
}
270+
271+
fn test_field_name_case_insensitivity() {
272+
let lower = "foo";
273+
let upper = "FOO";
274+
275+
let fields = Fields::new();
276+
fields.append(lower, b"val1").unwrap();
277+
assert!(fields.has(lower));
278+
assert!(fields.has(upper));
279+
assert_eq!(fields.get(lower), fields.get(upper));
280+
fields.delete(upper).unwrap();
281+
assert!(!fields.has(lower));
282+
assert!(!fields.has(upper));
283+
284+
fields.append(upper, b"val1").unwrap();
285+
assert!(fields.has(upper));
286+
assert!(fields.has(lower));
287+
assert_eq!(fields.get(lower), fields.get(upper));
288+
fields.delete(lower).unwrap();
289+
assert!(!fields.has(upper));
290+
assert!(!fields.has(lower));
291+
292+
fields.append(lower, b"val1").unwrap();
293+
fields.append(upper, b"val2").unwrap();
294+
assert_eq!(fields.copy_all(),
295+
[(lower.to_string(), b"val1".to_vec()),
296+
(lower.to_string(), b"val2".to_vec())]);
297+
assert_eq!(fields.get_and_delete(upper).unwrap(),
298+
[b"val1".to_vec(), b"val2".to_vec()]);
299+
300+
fields.append(upper, b"val2").unwrap();
301+
fields.append(lower, b"val1").unwrap();
302+
// https://github.com/bytecodealliance/wasmtime/issues/11770
303+
// assert_eq!(fields.copy_all(),
304+
// [(upper.to_string(), b"val2".to_vec()),
305+
// (upper.to_string(), b"val1".to_vec())]);
306+
assert_eq!(fields.get_and_delete(lower).unwrap(),
307+
[b"val2".to_vec(), b"val1".to_vec()]);
308+
}
309+
310+
struct Component;
311+
export!(Component);
312+
impl exports::wasi::cli::run::Guest for Component {
313+
async fn run() -> Result<(), ()> {
314+
test_empty_fields();
315+
test_fields_with_foo();
316+
test_invalid_field_names();
317+
test_valid_field_names();
318+
test_invalid_field_values();
319+
test_valid_field_values();
320+
test_field_name_case_insensitivity();
321+
Ok(())
322+
}
323+
}
324+
325+
fn main() {
326+
unreachable!("main is a stub");
327+
}

0 commit comments

Comments
 (0)