Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/rust/adblock/src/url_parser/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 16 kB image not shown  

Quelle  parser.rs

  Sprache: Rust
 

// Copyright 2013-2016 The rust-url developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::error::Error;
use std::fmt::{self, Formatter, Write};

use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
use std::ops::{Range, RangeFrom, RangeTo};

/// https://url.spec.whatwg.org/#fragment-percent-encode-set
const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');

/// https://url.spec.whatwg.org/#path-percent-encode-set
const PATH: &AsciiSet = &FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}');

/// https://url.spec.whatwg.org/#userinfo-percent-encode-set
pub(crateconst USERINFO: &AsciiSet = &PATH
    .add(b'/')
    .add(b':')
    .add(b';')
    .add(b'=')
    .add(b'@')
    .add(b'[')
    .add(b'\\')
    .add(b']')
    .add(b'^')
    .add(b'|');

#[derive(Clone)]
pub(superstruct Hostname {
    serialization: String,

    // Components
    pub(super) scheme_end: usize, // Before ':'
    pub(super) host_start: usize,
    pub(super) host_end: usize,
}

impl Hostname {
    pub fn parse(input: &str) -> Result<Hostname, ParseError> {
        Parser {
            serialization: String::with_capacity(input.len()),
        }
        .parse_url(input)
    }

    /// Equivalent to `url.host().is_some()`.
    ///
    /// # Examples
    ///
    /// ```
    /// use url::Url;
    /// # use url::ParseError;
    ///
    /// # fn run() -> Result<(), ParseError> {
    /// let url = Url::parse("ftp://rms@example.com")?;
    /// assert!(url.has_host());
    ///
    /// let url = Url::parse("unix:/run/foo.socket")?;
    /// assert!(!url.has_host());
    ///
    /// let url = Url::parse("data:text/plain,Stuff")?;
    /// assert!(!url.has_host());
    /// # Ok(())
    /// # }
    /// # run().unwrap();
    /// ```
    fn has_host(&self) -> bool {
        self.host_end > self.host_start
    }

    /// Return the string representation of the host (domain or IP address) for this URL, if any.
    ///
    /// Non-ASCII domains are punycode-encoded per IDNA.
    /// IPv6 addresses are given between `[` and `]` brackets.
    ///
    /// Cannot-be-a-base URLs (typical of `data:` and `mailto:`) and some `file:` URLs
    /// don’t have a host.
    ///
    /// See also the `host` method.
    ///
    /// # Examples
    ///
    /// ```
    /// use url::Url;
    /// # use url::ParseError;
    ///
    /// # fn run() -> Result<(), ParseError> {
    /// let url = Url::parse("https://127.0.0.1/index.html")?;
    /// assert_eq!(url.host_str(), Some("127.0.0.1"));
    ///
    /// let url = Url::parse("ftp://rms@example.com")?;
    /// assert_eq!(url.host_str(), Some("example.com"));
    ///
    /// let url = Url::parse("unix:/run/foo.socket")?;
    /// assert_eq!(url.host_str(), None);
    ///
    /// let url = Url::parse("data:text/plain,Stuff")?;
    /// assert_eq!(url.host_str(), None);
    /// # Ok(())
    /// # }
    /// # run().unwrap();
    /// ```
    pub fn host_str(&self) -> Option<&str> {
        if self.has_host() {
            Some(self.slice(self.host_start..self.host_end))
        } else {
            None
        }
    }

    pub fn url_str(&self) -> &str {
        &self.serialization
    }

    // Private helper methods:

    fn slice<R>(&self, range: R) -> &str
    where
        R: RangeArg,
    {
        range.slice_of(&self.serialization)
    }
}

trait RangeArg {
    fn slice_of<'a>(&self, s: &'a str) -> &'a str;
}

impl RangeArg for Range<usize> {
    fn slice_of<'a>(&self, s: &'a str) -> &'a str {
        &s[self.start..self.end]
    }
}

impl RangeArg for RangeFrom<usize> {
    fn slice_of<'a>(&self, s: &'a str) -> &'a str {
        &s[self.start..]
    }
}

impl RangeArg for RangeTo<usize> {
    fn slice_of<'a>(&self, s: &'a str) -> &'a str {
        &s[..self.end]
    }
}

pub type ParseResult<T> = Result<T, ParseError>;

macro_rules! simple_enum_error {
    ($($name: ident => $description: expr,)+) => {
        /// Errors that can occur during parsing.
        #[derive(PartialEq, Eq, Clone, Copy, Debug)]
        pub enum ParseError {
            $(
                $name,
            )+
        }

        impl Error for ParseError {}

        impl fmt::Display for ParseError {
            fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
                match *self {
                    $(
                        ParseError::$name => $description,
                    )+
                }.fmt(fmt)
            }
        }
    }
}

simple_enum_error! {
    // EmptyHost => "empty host",
    IdnaError => "invalid international domain name",
    // InvalidPort => "invalid port number",
    // InvalidIpv4Address => "invalid IPv4 address",
    // InvalidIpv6Address => "invalid IPv6 address",
    // InvalidDomainCharacter => "invalid domain character",
    // HostParseError => "internal host parse error",
    RelativeUrlWithoutBase => "relative URL without a base",
    // RelativeUrlWithCannotBeABaseBase => "relative URL with a cannot-be-a-base base",
    // SetHostOnCannotBeABaseUrl => "a cannot-be-a-base URL doesn’t have a host to set",
    // Overflow => "URLs more than 4 GB are not supported",
    FileUrlNotSupported => "file URLs are not supported",
    ExpectedMoreChars => "Expected more characters",
}

impl From<idna::Errors> for ParseError {
    fn from(_: idna::Errors) -> ParseError {
        ParseError::IdnaError
    }
}

#[derive(Copy, Clone)]
pub enum SchemeType {
    File,
    SpecialNotFile,
    NotSpecial,
}

impl SchemeType {
    pub fn is_special(self) -> bool {
        !matches!(self, SchemeType::NotSpecial)
    }

    pub fn from(s: &str) -> Self {
        match s {
            "http" | "https" | "ws" | "wss" | "ftp" | "gopher" => SchemeType::SpecialNotFile,
            "file" => SchemeType::File,
            _ => SchemeType::NotSpecial,
        }
    }
}

#[derive(Clone)]
pub struct Input<'i> {
    chars: std::str::Chars<'i>,
}

impl<'i> Input<'i> {
    pub fn new(input: &'i str) -> Self {
        let input = input.trim_matches(c0_control_or_space);
        Input {
            chars: input.chars(),
        }
    }

    pub fn is_empty(&self) -> bool {
        self.clone().next().is_none()
    }

    fn starts_with<P: Pattern>(&self, p: P) -> bool {
        p.split_prefix(&mut self.clone())
    }

    pub fn split_prefix<P: Pattern>(&self, p: P) -> Option<Self> {
        let mut remaining = self.clone();
        if p.split_prefix(&mut remaining) {
            Some(remaining)
        } else {
            None
        }
    }

    fn count_matching<F: Fn(char) -> bool>(&self, f: F) -> (u32, Self) {
        let mut count = 0;
        let mut remaining = self.clone();
        loop {
            let mut input = remaining.clone();
            if matches!(input.next(), Some(c) if f(c)) {
                remaining = input;
                count += 1;
            } else {
                return (count, remaining);
            }
        }
    }

    fn next_utf8(&mut self) -> Option<(char, &'i str)> {
        loop {
            let utf8 = self.chars.as_str();
            match self.chars.next() {
                Some(c) => {
                    if !matches!(c, '\t' | '\n' | '\r') {
                        return Some((c, &utf8[..c.len_utf8()]));
                    }
                }
                None => return None,
            }
        }
    }
}

pub trait Pattern {
    fn split_prefix(self, input: &mut Input) -> bool;
}

impl Pattern for char {
    fn split_prefix(self, input: &mut Input) -> bool {
        input.next() == Some(self)
    }
}

impl Pattern for &str {
    fn split_prefix(self, input: &mut Input) -> bool {
        for c in self.chars() {
            if input.next() != Some(c) {
                return false;
            }
        }
        true
    }
}

impl<F: FnMut(char) -> bool> Pattern for F {
    fn split_prefix(self, input: &mut Input) -> bool {
        input.next().is_some_and(self)
    }
}

impl Iterator for Input<'_> {
    type Item = char;
    fn next(&mut self) -> Option<char> {
        self.chars.next() //by_ref().find(|&c| !matches!(c, '\t' | '\n' | '\r'))
    }
}

pub struct Parser {
    pub serialization: String,
}

impl Parser {
    /// https://url.spec.whatwg.org/#concept-basic-url-parser
    pub fn parse_url(mut self, input: &str) -> ParseResult<Hostname> {
        // println!("Parse {}", input);
        let input = Input::new(input);
        if let Ok(remaining) = self.parse_scheme(input.clone()) {
            return self.parse_with_scheme(remaining);
        }

        // No-scheme state
        Err(ParseError::RelativeUrlWithoutBase)
    }

    pub fn parse_scheme<'i>(&mut self, mut input: Input<'i>) -> Result<Input<'i>, ()> {
        if input.is_empty() || !input.starts_with(|c: char| c.is_ascii_alphabetic()) {
            return Err(());
        }
        debug_assert!(self.serialization.is_empty());
        while let Some(c) = input.next() {
            match c {
                'a'..='z' => self.serialization.push(c),
                'A'..='Z' => self.serialization.push(c.to_ascii_lowercase()),
                '0'..='9' | '+' | '-' | '.' => self.serialization.push(c),
                ':' => return Ok(input),
                _ => {
                    self.serialization.clear();
                    return Err(());
                }
            }
        }

        Err(())
    }

    fn parse_with_scheme(mut self, input: Input) -> ParseResult<Hostname> {
        let scheme_end = self.serialization.len();
        let scheme_type = SchemeType::from(&self.serialization);
        self.serialization.push(':');
        match scheme_type {
            SchemeType::File => {
                // println!("Parse file - not supported");
                Err(ParseError::FileUrlNotSupported)
            }
            SchemeType::SpecialNotFile => {
                // println!("Parse special, not file");
                // special relative or authority state
                let (_, remaining) = input.count_matching(|c| matches!(c, '/' | '\\'));
                // special authority slashes state
                // println!("Parse after double slash {}", remaining.chars.as_str());
                self.after_double_slash(remaining, scheme_type, scheme_end)
            }
            SchemeType::NotSpecial => {
                // println!("Parse non special {}", &self.serialization);
                self.parse_non_special(input, scheme_type, scheme_end)
            }
        }
    }

    /// Scheme other than file, http, https, ws, ws, ftp, gopher.
    fn parse_non_special(
        mut self,
        input: Input,
        scheme_type: SchemeType,
        scheme_end: usize,
    ) -> ParseResult<Hostname> {
        // path or authority state (
        if let Some(input) = input.split_prefix("//") {
            return self.after_double_slash(input, scheme_type, scheme_end);
        }
        // Anarchist URL (no authority)
        let path_start = self.serialization.len();
        let host_start = path_start;
        let host_end = path_start;
        self.serialization.push_str(input.chars.as_str());
        let ser_remaining = self.serialization.as_mut_str().get_mut(host_end..);
        ser_remaining.map(|s| {
            s.make_ascii_lowercase();
            &*s
        });

        Ok(Hostname {
            serialization: self.serialization,
            scheme_end,
            host_start,
            host_end,
        })
    }

    fn after_double_slash(
        mut self,
        input: Input,
        scheme_type: SchemeType,
        scheme_end: usize,
    ) -> ParseResult<Hostname> {
        self.serialization.push_str("//");
        // authority state
        let (_username_end, remaining) = self.parse_userinfo(input, scheme_type)?;
        // host state
        let host_start = self.serialization.len();
        let (host_end, remaining) = self.parse_host(remaining, scheme_type)?;
        self.serialization.push_str(remaining.chars.as_str());
        Ok(Hostname {
            serialization: self.serialization,
            scheme_end,
            host_start,
            host_end,
        })
    }

    /// Return (username_end, remaining)
    fn parse_userinfo<'i>(
        &mut self,
        mut input: Input<'i>,
        scheme_type: SchemeType,
    ) -> ParseResult<(usize, Input<'i>)> {
        let mut last_at = None;
        let mut remaining = input.clone();
        let mut char_count = 0;
        while let Some(c) = remaining.next() {
            match c {
                '@' => last_at = Some((char_count, remaining.clone())),
                '/' | '?' | '#' => break,
                '\\' if scheme_type.is_special() => break,
                _ => (),
            }
            char_count += 1;
        }
        let (mut userinfo_char_count, remaining) = match last_at {
            None => return Ok((self.serialization.len(), input)),
            Some((0, remaining)) => return Ok((self.serialization.len(), remaining)),
            Some(x) => x,
        };

        let mut username_end = None;
        let mut has_password = false;
        let mut has_username = false;
        while userinfo_char_count > 0 {
            let (c, utf8_c) = input.next_utf8().ok_or(ParseError::ExpectedMoreChars)?;
            userinfo_char_count -= 1;
            if c == ':' && username_end.is_none() {
                // Start parsing password
                username_end = Some(self.serialization.len());
                // We don't add a colon if the password is empty
                if userinfo_char_count > 0 {
                    self.serialization.push(':');
                    has_password = true;
                }
            } else {
                if !has_password {
                    has_username = true;
                }
                self.serialization
                    .extend(utf8_percent_encode(utf8_c, USERINFO));
            }
        }
        let username_end = match username_end {
            Some(i) => i,
            None => self.serialization.len(),
        };
        if has_username || has_password {
            self.serialization.push('@');
        }
        Ok((username_end, remaining))
    }

    pub fn parse_host<'i>(
        &mut self,
        mut input: Input<'i>,
        scheme_type: SchemeType,
    ) -> ParseResult<(usize, Input<'i>)> {
        // Undo the Input abstraction here to avoid allocating in the common case
        // where the host part of the input does not contain any tab or newline
        let input_str = input.chars.as_str();
        let mut remaining = input.clone();
        let mut inside_square_brackets = false;
        let mut has_ignored_chars = false;
        let mut non_ignored_chars = 0;
        let mut bytes = 0;
        for c in input_str.chars() {
            match c {
                ':' if !inside_square_brackets => break,
                '\\' if scheme_type.is_special() => break,
                '/' | '?' | '#' => break,
                '\t' | '\n' | '\r' => {
                    has_ignored_chars = true;
                }
                '[' => {
                    inside_square_brackets = true;
                    non_ignored_chars += 1
                }
                ']' => {
                    inside_square_brackets = false;
                    non_ignored_chars += 1
                }
                _ => non_ignored_chars += 1,
            }
            remaining.next();
            bytes += c.len_utf8();
        }
        let replaced: String;
        let host_str;
        {
            let host_input = input.by_ref().take(non_ignored_chars);
            if has_ignored_chars {
                replaced = host_input.collect();
                host_str = &*replaced
            } else {
                for _ in host_input {}
                host_str = &input_str[..bytes]
            }
        }

        if host_str.is_ascii() {
            write!(&mut self.serialization, "{host_str}").unwrap();
        } else {
            let encoded = idna::domain_to_ascii(host_str)?;
            write!(&mut self.serialization, "{encoded}").unwrap();
        }

        let host_end = self.serialization.len();
        Ok((host_end, remaining))
    }
}

/// https://url.spec.whatwg.org/#c0-controls-and-space
#[inline]
fn c0_control_or_space(ch: char) -> bool {
    ch <= ' ' // U+0000 to U+0020
}

Messung V0.5 in Prozent
C=91 H=96 G=93

¤ Dauer der Verarbeitung: 0.27 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.