nomhttp 0.1.0

Parser HTTP for the rustyproxy project based on nom
Documentation
use crate::http_chunk::HttpChunk;
use crate::http_header::HttpHeader;
use crate::http_method::HttpMethod;
use crate::http_request::HttpRequest;
use crate::http_response::HttpResponse;
use crate::http_response_status::HttpResponseStatus;
use crate::http_version::HttpVersion;
use nom::{
    bytes::streaming::{tag, take, take_until, take_while},
    character::is_alphanumeric,
    combinator::{fail, iterator, map_res},
    error::Error,
    sequence::terminated,
    IResult,
};
use std::str::FromStr;

fn parse_method(i: &[u8]) -> IResult<&[u8], HttpMethod> {
    let (i, method_au8) = take_while(is_alphanumeric)(i)?;
    let (_, method_s) = map_res(take(method_au8.len()), std::str::from_utf8)(method_au8)?;
    let method = HttpMethod::from_str(method_s).unwrap();

    let (i, _) = take(1u8)(i)?; // take space

    Ok((i, method))
}

fn parse_path(i: &[u8]) -> IResult<&[u8], String> {
    let (i, path_au8) = take_until(" ")(i)?;

    let path = path_au8.iter().map(|&b| b as char).collect();

    let (i, _) = take(1u8)(i)?; // take space

    Ok((i, path))
}

fn parse_version(i: &[u8]) -> IResult<&[u8], HttpVersion> {
    let (i, version_au8) = take_until("\r\n")(i)?;
    let (_, version_s) = map_res(take(version_au8.len()), std::str::from_utf8)(version_au8)?;
    let version = HttpVersion::from_str(version_s).unwrap();

    let (i, _) = take(2u8)(i)?; // take CRLF after Http version

    Ok((i, version))
}

fn parse_header(i: &[u8]) -> IResult<&[u8], HttpHeader> {
    let (value_au8, name_au8) = take_until(": ")(i)?;
    let name = name_au8.iter().map(|&b| b as char).collect();
    let (value_au8, _) = take(2u8)(value_au8)?;
    let value: String = value_au8.iter().map(|&b| b as char).collect();

    let header = HttpHeader::new(name, value);

    Ok((value_au8, header))
}

fn parse_headers(i: &[u8]) -> IResult<&[u8], Vec<HttpHeader>> {
    let (rest, headers_au8) = terminated(take_until("\r\n\r\n"), tag("\r\n"))(i)?;
    let headers_au8 = [headers_au8, rest].concat();

    let mut headers_iter = iterator(
        &headers_au8[..],
        terminated(take_until("\r\n"), tag::<_, _, Error<_>>("\r\n")),
    );

    let headers: Vec<HttpHeader> = headers_iter
        .map(parse_header)
        .filter_map(|r| r.ok())
        .map(|v| v.1)
        .collect();

    Ok((&rest[2..], headers)) // avoid last \r\n
}

pub fn parse_request(i: &[u8], ssl: bool) -> IResult<&[u8], HttpRequest> {
    let copy = i;
    let (i, method) = parse_method(i)?;
    let (i, path) = parse_path(i)?;
    let (i, version) = parse_version(i)?;
    let (body_au8, headers) = parse_headers(i)?;
    let mut body = Vec::with_capacity(body_au8.len());
    body.extend_from_slice(body_au8);

    let head_len = copy.len() - body_au8.len();
    let mut raw = Vec::with_capacity(head_len);
    raw.extend_from_slice(&copy[..head_len]);

    let r = HttpRequest::new(method, path, version, headers, body, raw, ssl);
    Ok((body_au8, r))
}

fn parse_status_version(i: &[u8]) -> IResult<&[u8], HttpVersion> {
    let (i, version_au8) = take_until(" ")(i)?;
    let (_, version_s) = map_res(take(version_au8.len()), std::str::from_utf8)(version_au8)?;
    let version = HttpVersion::from_str(version_s).unwrap();

    let (i, _) = take(1u8)(i)?; // take the ' '
    Ok((i, version))
}

fn parse_status_code(i: &[u8]) -> IResult<&[u8], usize> {
    let (i, code_au8) = take_until(" ")(i)?;
    let (_, code_s) = map_res(take(code_au8.len()), std::str::from_utf8)(code_au8)?;
    let code = code_s.parse::<usize>().unwrap();

    let (i, _) = take(1u8)(i)?; // take the ' '
    Ok((i, code))
}

fn parse_status_text(i: &[u8]) -> IResult<&[u8], Vec<u8>> {
    let (i, msg_au8) = take_until("\r\n")(i)?;
    let msg: Vec<u8> = msg_au8.to_vec();

    let (i, _) = take(2u8)(i)?; // take the '\r\n'

    Ok((i, msg))
}

fn parse_status_line(i: &[u8]) -> IResult<&[u8], HttpResponseStatus> {
    let (i, version) = parse_status_version(i)?;
    let (i, code) = parse_status_code(i)?;
    let (i, msg) = parse_status_text(i)?;

    let rstatus = HttpResponseStatus::new(version, code, msg);
    Ok((i, rstatus))
}

pub fn parse_response(i: &[u8]) -> IResult<&[u8], HttpResponse> {
    let copy = i;

    let (i, status_line) = parse_status_line(i)?;
    let (body_au8, headers) = parse_headers(i)?;
    let body: Vec<u8> = body_au8.to_vec();

    let head_len = copy.len() - body_au8.len();
    let mut raw = Vec::with_capacity(head_len);
    raw.extend_from_slice(&copy[..head_len]);

    let r = HttpResponse::new(status_line, headers, body, raw);

    Ok((body_au8, r))
}

pub fn parse_chunk_header(i: &[u8]) -> IResult<&[u8], usize> {
    let (i, header_au8) = take_until("\r\n")(i)?;

    let (_, size_s) = map_res(take(header_au8.len()), std::str::from_utf8)(header_au8)?;

    let size = usize::from_str_radix(size_s, 16).unwrap();

    let (i, _) = take(2u8)(i)?;

    Ok((i, size))
}

pub fn take_chunk_body(i: &[u8], size: usize) -> IResult<&[u8], Vec<u8>> {
    let (i, chunkbody) = take(size)(i)?;
    let (i, _) = take(2u8)(i)?;

    let mut body = Vec::with_capacity(chunkbody.len());
    body.extend_from_slice(chunkbody);
    body.extend_from_slice(&[0x0d, 0x0a]);

    Ok((i, body))
}

pub fn parse_chunk(i: &[u8]) -> IResult<&[u8], HttpChunk> {
    let (rest, chunksize) = parse_chunk_header(i)?;
    let (rest, chunkbody) = take_chunk_body(rest, chunksize)?;

    let chunk_raw = &i[..i.len() - rest.len()];
    let chunk = HttpChunk::new(chunk_raw.to_vec(), chunkbody, chunksize);

    Ok((rest, chunk))
}


pub fn take_all_chunks(mut i: &[u8]) -> IResult<&[u8], Vec<HttpChunk>> {
    if i.is_empty() {
        fail::<_, &[u8], _>(i)?;
    }

    let mut chunks: Vec<HttpChunk> = vec![];
    let mut chunk;
    while !i.is_empty() {
        (i, chunk) = parse_chunk(i)?;
        chunks.push(chunk);
    }

    Ok((i, chunks))
}