//! This module contains functions required to integrate this library into //! browsers. If you are not building a browser, you can ignore this module. use serde::Deserialize; use serde::Serialize; use std::borrow::Cow; use url::Url; pub use crate::Error; use crate::UrlPatternOptions; pub use crate::component::Component; use crate::parser::RegexSyntax; use crate::regexp::RegExp; #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct UrlPatternInit { #[serde(skip_serializing_if = "Option::is_none")] pub protocol: Option, #[serde(skip_serializing_if = "Option::is_none")] pub username: Option, #[serde(skip_serializing_if = "Option::is_none")] pub password: Option, #[serde(skip_serializing_if = "Option::is_none")] pub hostname: Option, #[serde(skip_serializing_if = "Option::is_none")] pub port: Option, #[serde(skip_serializing_if = "Option::is_none")] pub pathname: Option, #[serde(skip_serializing_if = "Option::is_none")] pub search: Option, #[serde(skip_serializing_if = "Option::is_none")] pub hash: Option, #[serde(rename = "baseURL", skip_serializing_if = "Option::is_none")] pub base_url: Option, } #[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum StringOrInit<'a> { String(Cow<'a, str>), Init(UrlPatternInit), } /// This function constructs a UrlPattern given a string or UrlPatternInit and /// optionally a base url. pub fn process_construct_pattern_input( input: StringOrInit, base_url: Option<&str>, ) -> Result { let init = match input { StringOrInit::String(pattern) => { let base_url = base_url.map(Url::parse).transpose().map_err(Error::Url)?; crate::UrlPatternInit::parse_constructor_string::( &pattern, base_url, )? } StringOrInit::Init(init) => { if base_url.is_some() { return Err(Error::BaseUrlWithInit); } let base_url = init .base_url .map(|s| Url::parse(&s)) .transpose() .map_err(Error::Url)?; crate::UrlPatternInit { protocol: init.protocol, username: init.username, password: init.password, hostname: init.hostname, port: init.port, pathname: init.pathname, search: init.search, hash: init.hash, base_url, } } }; Ok(init) } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UrlPattern { pub protocol: UrlPatternComponent, pub username: UrlPatternComponent, pub password: UrlPatternComponent, pub hostname: UrlPatternComponent, pub port: UrlPatternComponent, pub pathname: UrlPatternComponent, pub search: UrlPatternComponent, pub hash: UrlPatternComponent, pub has_regexp_groups: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UrlPatternComponent { pub pattern_string: String, pub regexp_string: String, pub matcher: Matcher, pub group_name_list: Vec, } impl From> for UrlPatternComponent { fn from(component: Component) -> Self { let regexp_string = component .regexp .map(|r| r.pattern_string().to_owned()) .unwrap_or_default(); Self { pattern_string: component.pattern_string, regexp_string, matcher: component.matcher.into(), group_name_list: component.group_name_list, } } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Matcher { pub prefix: String, pub suffix: String, #[serde(flatten)] pub inner: InnerMatcher, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase", tag = "kind")] pub enum InnerMatcher { Literal { literal: String, }, #[serde(rename_all = "camelCase")] SingleCapture { filter: Option, allow_empty: bool, }, RegExp { regexp: String, }, } impl From> for Matcher { fn from(matcher: crate::matcher::Matcher) -> Self { Self { prefix: matcher.prefix, suffix: matcher.suffix, inner: matcher.inner.into(), } } } impl From> for InnerMatcher { fn from(inner: crate::matcher::InnerMatcher) -> Self { match inner { crate::matcher::InnerMatcher::Literal { literal } => { Self::Literal { literal } } crate::matcher::InnerMatcher::SingleCapture { filter, allow_empty, } => Self::SingleCapture { filter, allow_empty, }, crate::matcher::InnerMatcher::RegExp { regexp } => Self::RegExp { regexp: regexp .map(|r| r.pattern_string().to_owned()) .unwrap_or_default(), }, } } } pub struct EcmaRegexp(String, String); impl RegExp for EcmaRegexp { fn syntax() -> RegexSyntax { RegexSyntax::EcmaScript } fn parse(pattern: &str, flags: &str, force_eval: bool) -> Result { if force_eval { let regexp = regex::Regex::parse(pattern, flags, false); match regexp { Ok(r) => Ok(EcmaRegexp(r.to_string(), flags.to_string())), _ => Err(()), } } else { Ok(EcmaRegexp(pattern.to_string(), flags.to_string())) } } fn matches<'a>(&self, text: &'a str) -> Option>> { let regexp = regex::Regex::parse(&self.0, &self.1, false).ok()?; regexp.matches(text) } fn pattern_string(&self) -> &str { self.0.as_ref() } } /// Parse a pattern into its components. pub fn parse_pattern( init: crate::UrlPatternInit, options: UrlPatternOptions, ) -> Result { let pattern = crate::UrlPattern::::parse_internal(init, true, options)?; let urlpattern = UrlPattern { has_regexp_groups: pattern.has_regexp_groups(), protocol: pattern.protocol.into(), username: pattern.username.into(), password: pattern.password.into(), hostname: pattern.hostname.into(), port: pattern.port.into(), pathname: pattern.pathname.into(), search: pattern.search.into(), hash: pattern.hash.into(), }; Ok(urlpattern) } pub fn parse_pattern_as_lib( init: crate::UrlPatternInit, options: UrlPatternOptions, ) -> Result, Error> { let pattern = crate::UrlPattern::::parse_internal(init, true, options)?; Ok(pattern) } pub type Inputs<'a> = (StringOrInit<'a>, Option); pub fn process_match_input<'a>( input: StringOrInit<'a>, base_url_str: Option<&str>, ) -> Result)>, Error> { let mut inputs = (input.clone(), None); let init = match input { StringOrInit::String(url) => { let base_url = if let Some(base_url_str) = base_url_str { match Url::parse(base_url_str) { Ok(base_url) => { inputs.1 = Some(base_url_str.to_string()); Some(base_url) } Err(_) => return Ok(None), } } else { None }; match Url::options().base_url(base_url.as_ref()).parse(&url) { Ok(url) => crate::UrlPatternMatchInput::Url(url), Err(_) => return Ok(None), } } StringOrInit::Init(init) => { if base_url_str.is_some() { return Err(Error::BaseUrlWithInit); } let base_url = match init.base_url.map(|s| Url::parse(&s)).transpose() { Ok(base_url) => base_url, Err(_) => return Ok(None), }; let init = crate::UrlPatternInit { protocol: init.protocol, username: init.username, password: init.password, hostname: init.hostname, port: init.port, pathname: init.pathname, search: init.search, hash: init.hash, base_url, }; crate::UrlPatternMatchInput::Init(init) } }; Ok(Some((init, inputs))) } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct MatchInput { pub protocol: String, pub username: String, pub password: String, pub hostname: String, pub port: String, pub pathname: String, pub search: String, pub hash: String, } pub fn parse_match_input( input: crate::UrlPatternMatchInput, ) -> Option { let mut i = MatchInput::default(); match input { crate::UrlPatternMatchInput::Init(init) => { if let Ok(apply_result) = init.process( crate::canonicalize_and_process::ProcessType::Url, Some(i.protocol), Some(i.username), Some(i.password), Some(i.hostname), Some(i.port), Some(i.pathname), Some(i.search), Some(i.hash), ) { i.protocol = apply_result.protocol.unwrap(); i.username = apply_result.username.unwrap(); i.password = apply_result.password.unwrap(); i.hostname = apply_result.hostname.unwrap(); i.port = apply_result.port.unwrap(); i.pathname = apply_result.pathname.unwrap(); i.search = apply_result.search.unwrap(); i.hash = apply_result.hash.unwrap(); } else { return None; } } crate::UrlPatternMatchInput::Url(url) => { i.protocol = url.scheme().to_string(); i.username = url.username().to_string(); i.password = url.password().unwrap_or_default().to_string(); i.hostname = url.host_str().unwrap_or_default().to_string(); i.port = url::quirks::port(&url).to_string(); i.pathname = url::quirks::pathname(&url).to_string(); i.search = url.query().unwrap_or_default().to_string(); i.hash = url.fragment().unwrap_or_default().to_string(); } } Some(i) }