#[must_use]
pub fn override_header(method: &str) -> String {
format!("X-HTTP-Method-Override: {method}")
}
#[must_use]
pub fn override_header_alt(method: &str) -> String {
format!("X-HTTP-Method: {method}")
}
#[must_use]
pub fn override_header_express(method: &str) -> String {
format!("X-Method-Override: {method}")
}
#[must_use]
pub fn override_header_case_mix(method: &str) -> String {
let mixed: String = method
.chars()
.enumerate()
.map(|(i, c)| {
if i % 2 == 0 {
c.to_ascii_lowercase()
} else {
c.to_ascii_uppercase()
}
})
.collect();
format!("X-HTTP-Method-Override: {mixed}")
}
#[must_use]
pub fn override_header_padded(method: &str) -> String {
format!("X-HTTP-Method-Override: \t {method} ")
}
#[must_use]
pub fn override_header_duplicate(method_a: &str, method_b: &str) -> String {
format!("X-HTTP-Method-Override: {method_a}\r\nX-HTTP-Method-Override: {method_b}")
}
#[must_use]
pub fn form_field_method(method: &str) -> String {
format!("_method={method}")
}
#[must_use]
pub fn query_method(method: &str) -> String {
format!("?_method={method}")
}
#[must_use]
pub fn multipart_method(method: &str, boundary: &str) -> String {
format!(
"--{boundary}\r\nContent-Disposition: form-data; name=\"_method\"\r\n\r\n{method}\r\n--{boundary}--\r\n"
)
}
#[must_use]
pub fn chunked_trailer_override(method: &str, body: &str) -> String {
let body_len_hex = format!("{:x}", body.len());
format!(
"Transfer-Encoding: chunked\r\nTrailer: X-HTTP-Method-Override\r\n\r\n{body_len_hex}\r\n{body}\r\n0\r\nX-HTTP-Method-Override: {method}\r\n\r\n"
)
}
#[must_use]
pub fn header_plus_form_disagree(header_method: &str, form_method: &str) -> String {
format!(
"X-HTTP-Method-Override: {header_method}\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\n_method={form_method}"
)
}
#[must_use]
pub fn all_override_variants(method: &str) -> Vec<(&'static str, String)> {
vec![
("header-standard", override_header(method)),
("header-alt", override_header_alt(method)),
("header-express", override_header_express(method)),
("header-case-mix", override_header_case_mix(method)),
("header-padded", override_header_padded(method)),
("header-duplicate", override_header_duplicate("GET", method)),
("form-field", form_field_method(method)),
("query", query_method(method)),
(
"multipart",
multipart_method(method, "------WebKitFormBoundaryXXX"),
),
(
"chunked-trailer",
chunked_trailer_override(method, "name=value"),
),
("header-plus-form", header_plus_form_disagree("GET", method)),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn override_header_basic_delete() {
assert_eq!(override_header("DELETE"), "X-HTTP-Method-Override: DELETE");
}
#[test]
fn override_header_alt_name() {
assert_eq!(override_header_alt("PUT"), "X-HTTP-Method: PUT");
}
#[test]
fn override_header_express_name() {
assert_eq!(override_header_express("PATCH"), "X-Method-Override: PATCH");
}
#[test]
fn override_header_case_mix_alternates() {
let h = override_header_case_mix("DELETE");
assert!(h.contains("dElEtE"));
}
#[test]
fn override_header_case_mix_short() {
let h = override_header_case_mix("GET");
assert!(h.contains("gEt"));
}
#[test]
fn override_header_padded_has_whitespace() {
let h = override_header_padded("DELETE");
assert!(h.contains("\t"));
assert!(h.contains(" ")); assert!(h.contains("DELETE"));
}
#[test]
fn override_header_duplicate_emits_two_lines() {
let h = override_header_duplicate("GET", "DELETE");
assert_eq!(h.matches("X-HTTP-Method-Override:").count(), 2);
assert!(h.contains("GET"));
assert!(h.contains("DELETE"));
}
#[test]
fn form_field_method_basic() {
assert_eq!(form_field_method("DELETE"), "_method=DELETE");
}
#[test]
fn query_method_has_question_mark() {
let q = query_method("DELETE");
assert_eq!(q, "?_method=DELETE");
assert!(q.starts_with('?'));
}
#[test]
fn multipart_method_contains_boundary_and_method() {
let m = multipart_method("DELETE", "BOUND");
assert!(m.contains("--BOUND"));
assert!(m.contains("--BOUND--"));
assert!(m.contains("name=\"_method\""));
assert!(m.contains("DELETE"));
}
#[test]
fn chunked_trailer_contains_trailer_header() {
let c = chunked_trailer_override("DELETE", "key=val");
assert!(c.contains("Transfer-Encoding: chunked"));
assert!(c.contains("Trailer: X-HTTP-Method-Override"));
assert!(c.contains("X-HTTP-Method-Override: DELETE"));
assert!(c.contains("\r\n0\r\n"));
}
#[test]
fn chunked_trailer_body_length_correct_hex() {
let c = chunked_trailer_override("DELETE", "abc");
assert!(c.contains("3\r\nabc\r\n"));
}
#[test]
fn chunked_trailer_body_length_two_digit_hex() {
let c = chunked_trailer_override("DELETE", "0123456789ABCDEF0123"); assert!(c.contains("14\r\n"));
}
#[test]
fn header_plus_form_uses_both_channels() {
let h = header_plus_form_disagree("GET", "DELETE");
assert!(h.contains("X-HTTP-Method-Override: GET"));
assert!(h.contains("_method=DELETE"));
}
#[test]
fn all_override_variants_count() {
let v = all_override_variants("DELETE");
assert!(v.len() >= 10);
}
#[test]
fn all_override_variants_unique_names() {
let v = all_override_variants("DELETE");
let names: std::collections::HashSet<&&str> = v.iter().map(|(n, _)| n).collect();
assert_eq!(names.len(), v.len());
}
#[test]
fn all_override_variants_contain_target_method() {
let v = all_override_variants("UNIQUEMARKER");
let marker_lower = "uniquemarker";
for (name, payload) in &v {
assert!(
payload.to_ascii_lowercase().contains(marker_lower),
"{name} doesn't carry the method: {payload}"
);
}
}
#[test]
fn deterministic_across_calls() {
let a = all_override_variants("DELETE");
let b = all_override_variants("DELETE");
assert_eq!(a, b);
}
#[test]
fn handles_unicode_method() {
let h = override_header("DÉLÊTE");
assert!(h.contains("DÉLÊTE"));
}
#[test]
fn adversarial_long_method_no_panic() {
let big = "X".repeat(10_000);
let _ = override_header(&big);
let _ = override_header_case_mix(&big);
let _ = all_override_variants(&big);
}
#[test]
fn override_header_empty_method() {
let h = override_header("");
assert_eq!(h, "X-HTTP-Method-Override: ");
}
#[test]
fn case_mix_idempotent_on_alternation() {
let a = override_header_case_mix("HELLO");
let b = override_header_case_mix("HELLO");
assert_eq!(a, b);
}
#[test]
fn duplicate_header_no_crlf_injection_outside_header_block() {
let h = override_header_duplicate("A", "B");
assert_eq!(h.matches("\r\n").count(), 1);
}
#[test]
fn case_mix_empty_string() {
assert_eq!(override_header_case_mix(""), "X-HTTP-Method-Override: ");
}
#[test]
fn multipart_handles_special_chars_in_method() {
let m = multipart_method("DELETE\r\nX-Inject: yes", "B");
assert!(m.contains("name=\"_method\""));
}
#[test]
fn chunked_trailer_empty_body() {
let c = chunked_trailer_override("DELETE", "");
assert!(c.contains("0\r\n"));
assert!(c.contains("X-HTTP-Method-Override: DELETE"));
}
}