1use serde::Deserialize;
5use serde::Serialize;
6use url::Url;
7
8use crate::component::Component;
9use crate::parser::RegexSyntax;
10use crate::regexp::RegExp;
11pub use crate::Error;
12use crate::UrlPatternOptions;
13
14#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct UrlPatternInit {
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub protocol: Option<String>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub username: Option<String>,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub password: Option<String>,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub hostname: Option<String>,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub port: Option<String>,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub pathname: Option<String>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub search: Option<String>,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub hash: Option<String>,
32 #[serde(rename = "baseURL", skip_serializing_if = "Option::is_none")]
33 pub base_url: Option<String>,
34}
35
36#[allow(clippy::large_enum_variant)]
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(untagged)]
39pub enum StringOrInit {
40 String(String),
41 Init(UrlPatternInit),
42}
43
44pub fn process_construct_pattern_input(
47 input: StringOrInit,
48 base_url: Option<&str>,
49) -> Result<crate::UrlPatternInit, Error> {
50 let init = match input {
51 StringOrInit::String(pattern) => {
52 let base_url =
53 base_url.map(Url::parse).transpose().map_err(Error::Url)?;
54 crate::UrlPatternInit::parse_constructor_string::<regex::Regex>(
55 &pattern, base_url,
56 )?
57 }
58 StringOrInit::Init(init) => {
59 if base_url.is_some() {
60 return Err(Error::BaseUrlWithInit);
61 }
62 let base_url = init
63 .base_url
64 .map(|s| Url::parse(&s))
65 .transpose()
66 .map_err(Error::Url)?;
67 crate::UrlPatternInit {
68 protocol: init.protocol,
69 username: init.username,
70 password: init.password,
71 hostname: init.hostname,
72 port: init.port,
73 pathname: init.pathname,
74 search: init.search,
75 hash: init.hash,
76 base_url,
77 }
78 }
79 };
80 Ok(init)
81}
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83#[serde(rename_all = "camelCase")]
84pub struct UrlPattern {
85 pub protocol: UrlPatternComponent,
86 pub username: UrlPatternComponent,
87 pub password: UrlPatternComponent,
88 pub hostname: UrlPatternComponent,
89 pub port: UrlPatternComponent,
90 pub pathname: UrlPatternComponent,
91 pub search: UrlPatternComponent,
92 pub hash: UrlPatternComponent,
93 pub has_regexp_groups: bool,
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97#[serde(rename_all = "camelCase")]
98pub struct UrlPatternComponent {
99 pub pattern_string: String,
100 pub regexp_string: String,
101 pub matcher: Matcher,
102 pub group_name_list: Vec<String>,
103}
104
105impl From<Component<EcmaRegexp>> for UrlPatternComponent {
106 fn from(component: Component<EcmaRegexp>) -> Self {
107 Self {
108 pattern_string: component.pattern_string,
109 regexp_string: component.regexp.unwrap().0,
110 matcher: component.matcher.into(),
111 group_name_list: component.group_name_list,
112 }
113 }
114}
115
116#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct Matcher {
119 pub prefix: String,
120 pub suffix: String,
121 #[serde(flatten)]
122 pub inner: InnerMatcher,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
126#[serde(rename_all = "camelCase", tag = "kind")]
127pub enum InnerMatcher {
128 Literal {
129 literal: String,
130 },
131 #[serde(rename_all = "camelCase")]
132 SingleCapture {
133 filter: Option<char>,
134 allow_empty: bool,
135 },
136 RegExp {
137 regexp: String,
138 },
139}
140
141impl From<crate::matcher::Matcher<EcmaRegexp>> for Matcher {
142 fn from(matcher: crate::matcher::Matcher<EcmaRegexp>) -> Self {
143 Self {
144 prefix: matcher.prefix,
145 suffix: matcher.suffix,
146 inner: matcher.inner.into(),
147 }
148 }
149}
150
151impl From<crate::matcher::InnerMatcher<EcmaRegexp>> for InnerMatcher {
152 fn from(inner: crate::matcher::InnerMatcher<EcmaRegexp>) -> Self {
153 match inner {
154 crate::matcher::InnerMatcher::Literal { literal } => {
155 Self::Literal { literal }
156 }
157 crate::matcher::InnerMatcher::SingleCapture {
158 filter,
159 allow_empty,
160 } => Self::SingleCapture {
161 filter,
162 allow_empty,
163 },
164 crate::matcher::InnerMatcher::RegExp { regexp } => Self::RegExp {
165 regexp: regexp.unwrap().0,
166 },
167 }
168 }
169}
170
171struct EcmaRegexp(String, String);
172
173impl RegExp for EcmaRegexp {
174 fn syntax() -> RegexSyntax {
175 RegexSyntax::EcmaScript
176 }
177
178 fn parse(pattern: &str, flags: &str) -> Result<Self, ()> {
179 Ok(EcmaRegexp(pattern.to_string(), flags.to_string()))
180 }
181
182 fn matches<'a>(&self, text: &'a str) -> Option<Vec<Option<&'a str>>> {
183 let regexp = regex::Regex::parse(&self.0, &self.1).ok()?;
184 regexp.matches(text)
185 }
186}
187
188pub fn parse_pattern(
190 init: crate::UrlPatternInit,
191 options: UrlPatternOptions,
192) -> Result<UrlPattern, Error> {
193 let pattern =
194 crate::UrlPattern::<EcmaRegexp>::parse_internal(init, false, options)?;
195 let urlpattern = UrlPattern {
196 has_regexp_groups: pattern.has_regexp_groups(),
197 protocol: pattern.protocol.into(),
198 username: pattern.username.into(),
199 password: pattern.password.into(),
200 hostname: pattern.hostname.into(),
201 port: pattern.port.into(),
202 pathname: pattern.pathname.into(),
203 search: pattern.search.into(),
204 hash: pattern.hash.into(),
205 };
206 Ok(urlpattern)
207}
208
209pub type Inputs = (StringOrInit, Option<String>);
210
211pub fn process_match_input(
212 input: StringOrInit,
213 base_url_str: Option<&str>,
214) -> Result<Option<(crate::UrlPatternMatchInput, Inputs)>, Error> {
215 let mut inputs = (input.clone(), None);
216 let init = match input {
217 StringOrInit::String(url) => {
218 let base_url = if let Some(base_url_str) = base_url_str {
219 match Url::parse(base_url_str) {
220 Ok(base_url) => {
221 inputs.1 = Some(base_url_str.to_string());
222 Some(base_url)
223 }
224 Err(_) => return Ok(None),
225 }
226 } else {
227 None
228 };
229 match Url::options().base_url(base_url.as_ref()).parse(&url) {
230 Ok(url) => crate::UrlPatternMatchInput::Url(url),
231 Err(_) => return Ok(None),
232 }
233 }
234 StringOrInit::Init(init) => {
235 if base_url_str.is_some() {
236 return Err(Error::BaseUrlWithInit);
237 }
238 let base_url = match init.base_url.map(|s| Url::parse(&s)).transpose() {
239 Ok(base_url) => base_url,
240 Err(_) => return Ok(None),
241 };
242 let init = crate::UrlPatternInit {
243 protocol: init.protocol,
244 username: init.username,
245 password: init.password,
246 hostname: init.hostname,
247 port: init.port,
248 pathname: init.pathname,
249 search: init.search,
250 hash: init.hash,
251 base_url,
252 };
253 crate::UrlPatternMatchInput::Init(init)
254 }
255 };
256
257 Ok(Some((init, inputs)))
258}
259
260#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
261pub struct MatchInput {
262 pub protocol: String,
263 pub username: String,
264 pub password: String,
265 pub hostname: String,
266 pub port: String,
267 pub pathname: String,
268 pub search: String,
269 pub hash: String,
270}
271
272pub fn parse_match_input(
273 input: crate::UrlPatternMatchInput,
274) -> Option<MatchInput> {
275 let mut i = MatchInput::default();
276 match input {
277 crate::UrlPatternMatchInput::Init(init) => {
278 if let Ok(apply_result) = init.process(
279 crate::canonicalize_and_process::ProcessType::Url,
280 Some(i.protocol),
281 Some(i.username),
282 Some(i.password),
283 Some(i.hostname),
284 Some(i.port),
285 Some(i.pathname),
286 Some(i.search),
287 Some(i.hash),
288 ) {
289 i.protocol = apply_result.protocol.unwrap();
290 i.username = apply_result.username.unwrap();
291 i.password = apply_result.password.unwrap();
292 i.hostname = apply_result.hostname.unwrap();
293 i.port = apply_result.port.unwrap();
294 i.pathname = apply_result.pathname.unwrap();
295 i.search = apply_result.search.unwrap();
296 i.hash = apply_result.hash.unwrap();
297 } else {
298 return None;
299 }
300 }
301 crate::UrlPatternMatchInput::Url(url) => {
302 i.protocol = url.scheme().to_string();
303 i.username = url.username().to_string();
304 i.password = url.password().unwrap_or_default().to_string();
305 i.hostname = url.host_str().unwrap_or_default().to_string();
306 i.port = url::quirks::port(&url).to_string();
307 i.pathname = url::quirks::pathname(&url).to_string();
308 i.search = url.query().unwrap_or_default().to_string();
309 i.hash = url.fragment().unwrap_or_default().to_string();
310 }
311 }
312
313 Some(i)
314}