wafrift_encoding/encoding/
request_line.rs1#[must_use]
30pub fn exotic_methods() -> Vec<&'static str> {
31 vec![
32 "PROPFIND",
34 "PROPPATCH",
35 "MKCOL",
36 "COPY",
37 "MOVE",
38 "LOCK",
39 "UNLOCK",
40 "REPORT",
42 "ACL",
43 "SEARCH",
44 "PURGE",
46 "BAN",
47 "REFRESH",
48 "VERSION-CONTROL",
50 "MKWORKSPACE",
51 "UPDATE",
52 "CHECKIN",
53 "CHECKOUT",
54 "MKACTIVITY",
55 "BASELINE-CONTROL",
56 "MERGE",
57 "PATCH",
59 "TRACE",
61 "get",
63 "post",
64 "put",
65 "delete",
66 "GeT",
68 "PoSt",
69 "PuT",
70 "DeLeTe",
71 " GET",
74 "\tGET",
75 " GET ",
76 ]
77}
78
79#[must_use]
89pub fn absolute_uri_request_line(method: &str, host_in_uri: &str, path: &str) -> String {
90 format!("{method} http://{host_in_uri}{path} HTTP/1.1")
91}
92
93#[must_use]
95pub fn absolute_uri_https_request_line(method: &str, host_in_uri: &str, path: &str) -> String {
96 format!("{method} https://{host_in_uri}{path} HTTP/1.1")
97}
98
99#[must_use]
104pub fn request_line_with_version(method: &str, path: &str, version: &str) -> String {
105 format!("{method} {path} {version}")
106}
107
108#[must_use]
115pub fn request_line_with_whitespace(
116 method: &str,
117 method_sep: &str,
118 path: &str,
119 path_sep: &str,
120 version: &str,
121) -> String {
122 format!("{method}{method_sep}{path}{path_sep}{version}")
123}
124
125#[must_use]
128pub fn asterisk_form_request_line(method: &str) -> String {
129 format!("{method} * HTTP/1.1")
130}
131
132#[must_use]
137pub fn authority_form_request_line(method: &str, host: &str, port: u16) -> String {
138 format!("{method} {host}:{port} HTTP/1.1")
139}
140
141pub const REQUEST_LINE_TRICKS: &[&str] = &[
145 "exotic_methods",
146 "absolute_uri_request_line",
147 "absolute_uri_https_request_line",
148 "request_line_with_version",
149 "request_line_with_whitespace",
150 "asterisk_form_request_line",
151 "authority_form_request_line",
152];
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn exotic_methods_includes_propfind() {
160 assert!(exotic_methods().contains(&"PROPFIND"));
161 }
162
163 #[test]
164 fn exotic_methods_includes_purge() {
165 assert!(exotic_methods().contains(&"PURGE"));
166 }
167
168 #[test]
169 fn exotic_methods_includes_lowercase() {
170 assert!(exotic_methods().contains(&"get"));
171 assert!(exotic_methods().contains(&"post"));
172 }
173
174 #[test]
175 fn exotic_methods_includes_mixed_case() {
176 assert!(exotic_methods().contains(&"GeT"));
177 }
178
179 #[test]
180 fn exotic_methods_includes_whitespace_pad() {
181 assert!(exotic_methods().iter().any(|m| m.starts_with(' ')));
182 assert!(exotic_methods().iter().any(|m| m.starts_with('\t')));
183 }
184
185 #[test]
186 fn exotic_methods_minimum_count() {
187 assert!(
191 exotic_methods().len() >= 25,
192 "regression: lost coverage of exotic-method set"
193 );
194 }
195
196 #[test]
197 fn absolute_uri_basic() {
198 let rl = absolute_uri_request_line("GET", "evil.example", "/admin");
199 assert_eq!(rl, "GET http://evil.example/admin HTTP/1.1");
200 }
201
202 #[test]
203 fn absolute_uri_https() {
204 let rl = absolute_uri_https_request_line("POST", "evil.example", "/api");
205 assert_eq!(rl, "POST https://evil.example/api HTTP/1.1");
206 }
207
208 #[test]
209 fn absolute_uri_preserves_query() {
210 let rl = absolute_uri_request_line("GET", "h", "/a?b=c&d=e");
211 assert!(rl.contains("?b=c&d=e"));
212 }
213
214 #[test]
215 fn version_explicit_http_0_9() {
216 let rl = request_line_with_version("GET", "/", "HTTP/0.9");
217 assert_eq!(rl, "GET / HTTP/0.9");
218 }
219
220 #[test]
221 fn version_explicit_http_1_99() {
222 let rl = request_line_with_version("GET", "/", "HTTP/1.99");
223 assert_eq!(rl, "GET / HTTP/1.99");
224 }
225
226 #[test]
227 fn version_explicit_http_2_on_h1_wire() {
228 let rl = request_line_with_version("GET", "/", "HTTP/2.0");
229 assert_eq!(rl, "GET / HTTP/2.0");
230 }
231
232 #[test]
233 fn whitespace_tab_between_tokens() {
234 let rl = request_line_with_whitespace("GET", "\t", "/", "\t", "HTTP/1.1");
235 assert_eq!(rl, "GET\t/\tHTTP/1.1");
236 assert!(!rl.contains(' '), "no SP, only TAB");
237 }
238
239 #[test]
240 fn whitespace_multiple_spaces() {
241 let rl = request_line_with_whitespace("GET", " ", "/", " ", "HTTP/1.1");
242 assert_eq!(rl, "GET / HTTP/1.1");
243 }
244
245 #[test]
246 fn whitespace_mixed() {
247 let rl = request_line_with_whitespace("GET", " \t ", "/", "\t \t", "HTTP/1.1");
248 assert!(rl.contains('\t'));
249 assert!(rl.contains(' '));
250 }
251
252 #[test]
253 fn asterisk_form_options() {
254 let rl = asterisk_form_request_line("OPTIONS");
255 assert_eq!(rl, "OPTIONS * HTTP/1.1");
256 }
257
258 #[test]
259 fn asterisk_form_invalid_method_still_produces_string() {
260 let rl = asterisk_form_request_line("GET");
264 assert_eq!(rl, "GET * HTTP/1.1");
265 }
266
267 #[test]
268 fn authority_form_connect() {
269 let rl = authority_form_request_line("CONNECT", "internal", 8080);
270 assert_eq!(rl, "CONNECT internal:8080 HTTP/1.1");
271 }
272
273 #[test]
274 fn authority_form_high_port() {
275 let rl = authority_form_request_line("CONNECT", "h", u16::MAX);
276 assert!(rl.ends_with("65535 HTTP/1.1"));
277 }
278
279 #[test]
280 fn registry_lists_every_function_we_expose() {
281 assert_eq!(REQUEST_LINE_TRICKS.len(), 7);
284 }
285
286 #[test]
287 fn no_function_produces_crlf_in_output() {
288 let candidates = vec![
293 absolute_uri_request_line("GET", "h", "/p"),
294 absolute_uri_https_request_line("GET", "h", "/p"),
295 request_line_with_version("GET", "/", "HTTP/0.9"),
296 request_line_with_whitespace("GET", " ", "/", " ", "HTTP/1.1"),
297 asterisk_form_request_line("OPTIONS"),
298 authority_form_request_line("CONNECT", "h", 443),
299 ];
300 for c in candidates {
301 assert!(!c.contains("\r\n"), "no CRLF in request line: {c:?}");
302 assert!(!c.contains('\n'), "no LF in request line: {c:?}");
303 }
304 }
305
306 #[test]
307 fn deterministic_across_calls() {
308 let a = exotic_methods();
309 let b = exotic_methods();
310 assert_eq!(a, b);
311 }
312
313 #[test]
314 fn adversarial_long_path_no_panic() {
315 let big = "/a".repeat(10_000);
316 let _ = absolute_uri_request_line("GET", "h", &big);
317 let _ = request_line_with_version("GET", &big, "HTTP/1.1");
318 }
319}