sozu-lib 1.0.0

sozu library to build hot reconfigurable HTTP reverse proxies
Documentation
use std::fmt::Write;

use kawa::{Block, Pair, ParsingErrorKind, ParsingPhase, ParsingPhaseMarker};

use super::GenericHttpStream;

#[cfg(feature = "tolerant-http1-parser")]
const CHARSET: &str = "all characters are LATIN-1 (no UTF-8 allowed)";
#[cfg(not(feature = "tolerant-http1-parser"))]
const CHARSET: &str = "all characters are UASCII (no UTF-8 allowed)";

fn hex_dump(buffer: &[u8], window: usize, start: usize, end: usize) -> String {
    let mut result = String::with_capacity(window * 4 * 2 + 10);
    if end - start <= window {
        let slice = &buffer[start..end];
        for c in slice {
            let _ = write!(result, " {c:02x}");
        }
        result.push_str(
            &std::iter::repeat(' ')
                .take((window + start - end) * 3 + 4)
                .collect::<String>(),
        );
        result.push_str(&String::from_utf8_lossy(slice).escape_debug().to_string());
    } else {
        let slice1 = &buffer[start..start + window - 1];
        let slice2 = &buffer[end - window + 1..end];
        for c in slice1 {
            let _ = write!(result, " {c:02x}");
        }
        result.push_str(" ..    ");
        result.push_str(&String::from_utf8_lossy(slice1).escape_debug().to_string());
        result.push_str("..\n ..");
        for c in slice2 {
            let _ = write!(result, " {c:02x}");
        }
        result.push_str("    ..");
        result.push_str(&String::from_utf8_lossy(slice2).escape_debug().to_string());
    }
    result
}

pub fn diagnostic_400_502(
    marker: ParsingPhaseMarker,
    kind: ParsingErrorKind,
    kawa: &GenericHttpStream,
) -> (String, String) {
    match kind {
        ParsingErrorKind::Consuming { index } => {
            let message = match marker {
                ParsingPhaseMarker::StatusLine => {
                    format!(
                        "The request line is invalid. Make sure it is well formated and {CHARSET}."
                    )
                }
                ParsingPhaseMarker::Headers | ParsingPhaseMarker::Trailers => {
                    let marker = if marker == ParsingPhaseMarker::Headers {
                        "header"
                    } else {
                        "trailer"
                    };
                    if let Some(Block::Header(Pair { key, .. })) = kawa.blocks.back() {
                        format!(
                            "A {marker} is invalid, make sure {CHARSET}. Last valid {marker} is: {:?}.",
                            String::from_utf8_lossy(key.data(kawa.storage.buffer())),
                        )
                    } else {
                        format!("The first {marker} is invalid, make sure {CHARSET}.")
                    }
                }
                ParsingPhaseMarker::Cookies => {
                    if kawa.detached.jar.len() > 1 {
                        let Pair { key, .. } = &kawa.detached.jar[kawa.detached.jar.len() - 2];
                        format!(
                            "A cookie is invalid, make sure {CHARSET}. Last valid cookie is: {:?}.",
                            String::from_utf8_lossy(key.data(kawa.storage.buffer())),
                        )
                    } else {
                        format!("The first cookie is invalid, make sure {CHARSET}.")
                    }
                }
                ParsingPhaseMarker::Body
                | ParsingPhaseMarker::Chunks
                | ParsingPhaseMarker::Terminated
                | ParsingPhaseMarker::Error => {
                    format!("Parsing phase {marker:?} should not produce 400 error.")
                }
            };
            let buffer = kawa.storage.buffer();
            let details = format!(
                "\
Parsed successfully:
{}
Partially parsed (valid):
{}
Invalid:
{}",
                hex_dump(buffer, 32, kawa.storage.start, kawa.storage.head),
                hex_dump(buffer, 32, kawa.storage.head, index as usize),
                hex_dump(buffer, 32, index as usize, kawa.storage.end),
            );
            (message, details)
        }
        ParsingErrorKind::Processing { message } => (
            "The request was successfully parsed but presents inconsistent or invalid values."
                .into(),
            message.to_owned(),
        ),
    }
}

pub fn diagnostic_413_507(parsing_phase: ParsingPhase) -> String {
    match parsing_phase {
        kawa::ParsingPhase::StatusLine => {
            format!("Request line is too long. Note that an URL should not exceed 2083 characters.")
        }
        kawa::ParsingPhase::Headers | kawa::ParsingPhase::Cookies { .. } => {
            format!("Headers are too long. All headers should fit in a single buffer.")
        }
        phase => format!("Unexpected parsing phase: {phase:?}"),
    }
}