Странный вопрос о разборе http-заголовка по номеру ящика в rust

#rust #nom

Вопрос:

Я написал некоторый код, который использует NOM для анализа HTTP-заголовков, но получил некоторые странные результаты, как показано в примере. В основной функции строка input1 имеет только одну точку с запятой в конце, чем input2, но они получили совершенно разные результаты. Очевидно, я ожидаю результатов, аналогичных тексту(ввод2), спасибо, что помогли мне проверить, что происходит.

 [dependencies]
nom = "6"
 
 use nom::{
    branch::alt,
    bytes::complete::{tag, tag_no_case, take_until},
    character::complete::multispace0,
    combinator::opt,
    multi::separated_list0,
    sequence::tuple,
};
use nom::{AsChar, IResult, InputTakeAtPosition};
use std::collections::HashMap;
use std::fmt::Debug;

#[derive(Debug, PartialEq)]
pub struct Header<'a> {
    pub options_header_cookies: HashMap<amp;'a str, amp;'a str>,
    pub options_headers_more: HashMap<amp;'a str, amp;'a str>,
}

#[allow(dead_code)]
fn key(input: amp;str) -> IResult<amp;str, amp;str> {
    input.split_at_position_complete(|item| {
        !(item.is_alphanum() || item.as_char() == '-' || item.as_char() == '_')
    })
}

#[allow(dead_code)]
fn cookie_pairs(input: amp;str) -> IResult<amp;str, HashMap<amp;str, amp;str>> {
    let (input, cookies) = separated_list0(
        tag(";"),
        tuple((
            multispace0,
            key,
            tag("="),
            alt((take_until(";"), take_until("'"))),
            multispace0,
        )),
    )(input)?;
    Ok((input, cookies.into_iter().map(|c| (c.1, c.3)).collect()))
}

#[allow(dead_code)]
fn options_header_cookie(input: amp;str) -> IResult<amp;str, HashMap<amp;str, amp;str>> {
    let (input, (_, _, cookies, _)) = tuple((
        alt((tag("-Hx20"), tag("--headerx20"))),
        tag_no_case("'cookie:x20"),
        cookie_pairs,
        tag("'"),
    ))(input)?;
    Ok((input, cookies))
}

#[allow(dead_code)]
fn options_header_(input: amp;str) -> IResult<amp;str, (amp;str, amp;str)> {
    let (input, (_, k, _, v, _)) = tuple((
        alt((tag("-Hx20'"), tag("--headerx20'"))),
        key,
        tag(":x20"),
        take_until("'"),
        tag("'"),
    ))(input)?;
    Ok((input, (k, v)))
}

fn text(input: amp;str) -> IResult<amp;str, Header> {
    let mut h = Header {
        options_header_cookies: HashMap::new(),
        options_headers_more: HashMap::new(),
    };
    let (input, opts) = separated_list0(
        tag("x20"),
        tuple((
            opt(tag("\n")),
            multispace0,
            tuple((opt(options_header_cookie), opt(options_header_))),
        )),
    )(input)?;
    for (_, _, o) in opts {
        if let Some(cookies) = o.0 {
            h.options_header_cookies = cookies;
            continue;
        }else if let Some(header) = o.1 {
            h.options_headers_more.insert(header.0, header.1);
            continue;
        }
    }
    Ok((input, h))
}

#[allow(dead_code)]
fn debug<T: Debug>(o: T) {
    println!("=> {:#?}", o);
}

fn main() {
    let input1 = r#"
-H 'Cookie: NID=219=Ji47zdfV6mSKlkKmpVf8F67O80WTSw; DV=03-vBWQ2RBEqsNFUD5FEuieRJvkwrRfXaKa0v0Cj2wAAAAA' 
-H 'User-Agent: Mozilla/5.0 Macintosh;'"#;

    debug(text(input1));

    let input2 = r#"
-H 'Cookie: NID=219=Ji47zdfV6mSKlkKmpVf8F67O80WTSw; DV=03-vBWQ2RBEqsNFUD5FEuieRJvkwrRfXaKa0v0Cj2wAAAAA' 
-H 'User-Agent: Mozilla/5.0 Macintosh'"#;

    debug(text(input2));
}
 

Ответ №1:

Проблема заключается в вашем cookie_pairs() синтаксическом анализаторе:

 fn cookie_pairs(input: amp;str) -> IResult<amp;str, HashMap<amp;str, amp;str>> {
    let (input, cookies) = separated_list0(
        tag(";"),
        tuple((
            multispace0,
            key,
            tag("="),
            alt((take_until(";"), take_until("'"))),
            multispace0,
        )),
    )(input)?;
    Ok((input, cookies.into_iter().map(|c| (c.1, c.3)).collect()))
}
 

alt() Комбинатор запускает первый синтаксический анализатор до завершения и только в случае его сбоя пробует второй:

 alt((take_until(";"), take_until("'")))
 

Таким образом, в случае с трейлингом ; этот синтаксический анализатор по существу потребляет весь ввод, что приводит к сбою родительских анализаторов и не возвращает никаких файлов cookie.

Исправить это очень просто. Вы должны заменить его на:

 take_while(|ch| ch != ''' amp;amp; ch != ';')
 

который остановится, когда ' ; появится или или, и не будет потреблять весь ввод:

 fn cookie_pairs(input: amp;str) -> IResult<amp;str, HashMap<amp;str, amp;str>> {
    let (input, cookies) = separated_list0(
        tag(";"),
        tuple((
            multispace0,
            key,
            tag("="),
            take_while(|ch| ch != ''' amp;amp; ch != ';'),
            multispace0,
        )),
    )(input)?;
    Ok((input, cookies.into_iter().map(|c| (c.1, c.3)).collect()))
}