kingfisher-bin 2.8.0

MongoDB's blazingly fast and accurate secret scanning and validation tool
use std::cell::Cell;

use anyhow::Result;
use cssparser::{
    AtRuleParser, CowRcStr, DeclarationParser, ParseError, Parser, ParserInput, ParserState,
    RuleBodyItemParser, RuleBodyParser, StyleSheetParser, ToCss, Token, parse_important,
};

pub(super) fn stream_context_candidates<F>(source: &[u8], sink: &mut F) -> Result<()>
where
    F: FnMut(&str) -> bool,
{
    let css = String::from_utf8_lossy(source);
    if css.trim().is_empty() {
        return Ok(());
    }

    let mut input = ParserInput::new(&css);
    let mut parser = Parser::new(&mut input);
    let stopped = Cell::new(false);
    let mut collector = Collector { sink, stopped: &stopped };
    let mut stylesheet = StyleSheetParser::new(&mut parser, &mut collector);
    while !stopped.get() {
        if stylesheet.next().is_none() {
            break;
        }
    }
    Ok(())
}

struct Collector<'a, F> {
    sink: &'a mut F,
    stopped: &'a Cell<bool>,
}

impl<'a, F> Collector<'a, F>
where
    F: FnMut(&str) -> bool,
{
    fn emit(&mut self, name: &str, value: &str) {
        if self.stopped.get() {
            return;
        }
        let candidate = format!("{name} = {value}");
        self.stopped.set(!(self.sink)(&candidate));
    }
}

impl<'i, F> DeclarationParser<'i> for Collector<'_, F>
where
    F: FnMut(&str) -> bool,
{
    type Declaration = ();
    type Error = ();

    fn parse_value<'t>(
        &mut self,
        name: CowRcStr<'i>,
        input: &mut Parser<'i, 't>,
        _declaration_start: &ParserState,
    ) -> Result<(), ParseError<'i, ()>> {
        let mut values = Vec::new();
        let mut important = false;
        loop {
            let start = input.state();
            let token = match input.next_including_whitespace().cloned() {
                Ok(token) => token,
                Err(_) => break,
            };

            if token == Token::Delim('!') {
                input.reset(&start);
                if parse_important(input).is_ok() && input.is_exhausted() {
                    important = true;
                    break;
                }
                input.reset(&start);
            }

            collect_token_values(token, input, &mut values);
        }

        if values.is_empty() && !important {
            return Ok(());
        }

        if values.is_empty() && important {
            values.push("important".to_string());
        }

        for value in values {
            self.emit(&name, &value);
            if self.stopped.get() {
                break;
            }
        }
        Ok(())
    }
}

impl<'i, F> AtRuleParser<'i> for Collector<'_, F>
where
    F: FnMut(&str) -> bool,
{
    type Prelude = ();
    type AtRule = ();
    type Error = ();
}

impl<'i, F> cssparser::QualifiedRuleParser<'i> for Collector<'_, F>
where
    F: FnMut(&str) -> bool,
{
    type Prelude = ();
    type QualifiedRule = ();
    type Error = ();

    fn parse_prelude<'t>(&mut self, input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i, ()>> {
        while input.next_including_whitespace().is_ok() {}
        Ok(())
    }

    fn parse_block<'t>(
        &mut self,
        _prelude: (),
        _start: &ParserState,
        input: &mut Parser<'i, 't>,
    ) -> Result<(), ParseError<'i, ()>> {
        let stopped = self.stopped;
        let mut rule_body = RuleBodyParser::new(input, self);
        while !stopped.get() {
            if rule_body.next().is_none() {
                break;
            }
        }
        Ok(())
    }
}

impl<F> RuleBodyItemParser<'_, (), ()> for Collector<'_, F>
where
    F: FnMut(&str) -> bool,
{
    fn parse_qualified(&self) -> bool {
        true
    }

    fn parse_declarations(&self) -> bool {
        true
    }
}

fn collect_token_values<'i, 't>(
    token: Token<'i>,
    input: &mut Parser<'i, 't>,
    values: &mut Vec<String>,
) {
    match token {
        Token::QuotedString(value) => values.push(value.to_string()),
        Token::UnquotedUrl(value) => values.push(value.to_string()),
        Token::Ident(value) => values.push(value.to_string()),
        Token::Hash(value) | Token::IDHash(value) => values.push(value.to_string()),
        Token::Number { .. }
        | Token::Percentage { .. }
        | Token::Dimension { .. }
        | Token::Function(_) => {
            values.push(token.to_css_string());
            if matches!(token, Token::Function(_)) {
                let _ = input.parse_nested_block(|nested| {
                    while let Ok(next) = nested.next_including_whitespace().cloned() {
                        collect_token_values(next, nested, values);
                    }
                    Ok::<(), ParseError<'i, ()>>(())
                });
            }
        }
        Token::ParenthesisBlock | Token::SquareBracketBlock | Token::CurlyBracketBlock => {
            let _ = input.parse_nested_block(|nested| {
                while let Ok(next) = nested.next_including_whitespace().cloned() {
                    collect_token_values(next, nested, values);
                }
                Ok::<(), ParseError<'i, ()>>(())
            });
        }
        _ => {}
    }
}