http_desync_guardian 0.1.3

HTTP/1.1 request analysis to prevent HTTP Desync attacks
Documentation
///
/// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License").
/// You may not use this file except in compliance with the License.
/// A copy of the License is located at
///
///  http://aws.amazon.com/apache2.0
///
/// or in the "license" file accompanying this file. This file is distributed
/// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
/// express or implied. See the License for the specific language governing
/// permissions and limitations under the License.
///
use std::env;
use std::fs::File;
use std::io::Write;

const CR: u8 = b'\r';
const LF: u8 = b'\n';
const SP: u8 = b' ';
const HT: u8 = b'\t';
const DEL: u8 = 127;
const NIL: u8 = 0;
const TCHAR: &[u8] = b"-_.!#$%&'*+^`|~";

fn format_char(b: u8) -> String {
    use std::fmt::Write;

    let mut str = String::new();
    match b {
        b'\r' => str.push_str("\\r"),
        b'\n' => str.push_str("\\n"),
        b'\t' => str.push_str("\\t"),
        _ if is_rfc_vchar(b) => {
            str.push(b as char);
        }
        _ => {
            write!(str, "\\{:#04x}", b).expect("Writing to strings is infallible");
        }
    };
    str
}

fn generate_table(name: &str, predicate: fn(u8) -> bool) -> String {
    use std::fmt::Write;

    let mut t = String::new();
    write!(t, "pub static {}: [bool; 256] = [\r\n    ", name).ok();
    for i in 0..=255_u8 {
        write!(t, " {} /* {} */,", (predicate)(i), format_char(i)).ok();
        if i % 4 == 3 {
            write!(t, "\r\n    ").ok();
        }
    }
    write!(t, "];\r\n").ok();
    t
}

fn is_valid_uri_char(b: u8) -> bool {
    // disable CTL in URL
    b > SP && b != DEL
}

fn is_rfc_tchar(b: u8) -> bool {
    b.is_ascii_alphanumeric() || TCHAR.contains(&b)
}

fn is_rfc_vchar(b: u8) -> bool {
    b >= SP && b < DEL
}

fn is_rfc_obs_text(b: u8) -> bool {
    b >= 0x80
}

fn is_rfc_whitespace(b: u8) -> bool {
    b == SP || b == HT
}

fn is_bad_http_character(b: u8) -> bool {
    // these characters are absolutely unacceptable in header names/values
    // it means multi-line headers are not accepted too
    b == CR || b == LF || b == NIL
}

type TableGenerator = fn(u8) -> bool;

fn generate_lookup_tables() {
    use std::fmt::Write;

    let tables: &[(&str, TableGenerator)] = &[
        ("TCHAR_TABLE", |b| is_rfc_tchar(b)),
        ("VCHAR", |b| is_rfc_vchar(b)),
        ("BAD_CHARACTERS", |b| is_bad_http_character(b)),
        ("RFC_WHITE_SPACE", |b| is_rfc_whitespace(b)),
        ("VALID_URI_CHARS", |b| is_valid_uri_char(b)),
        ("HEADER_VALUE_CHARS", |b| {
            is_rfc_vchar(b) || is_rfc_obs_text(b) || is_rfc_whitespace(b)
        }),
    ];

    let mut char_tables = String::new();
    for table in tables {
        write!(char_tables, "{}", generate_table(table.0, table.1))
            .expect("Writing to strings is infallible");
    }
    let out_dir = env::var("OUT_DIR").expect("OUT_DIR variable is not set");
    let mut file =
        File::create(format!("{}/char_tables.rs", out_dir)).expect("Cannot open file for writing");
    file.write_all(char_tables.as_bytes())
        .expect("Cannot write to file");
}

fn main() {
    generate_lookup_tables();
}