/rust/registry/src/index.crates.io-6f17d22bba15001f/url-2.5.4/src/quirks.rs
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright 2016 The rust-url developers. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
4 | | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
5 | | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
6 | | // option. This file may not be copied, modified, or distributed |
7 | | // except according to those terms. |
8 | | |
9 | | //! Getters and setters for URL components implemented per <https://url.spec.whatwg.org/#api> |
10 | | //! |
11 | | //! Unless you need to be interoperable with web browsers, |
12 | | //! you probably want to use `Url` method instead. |
13 | | |
14 | | use crate::parser::{default_port, Context, Input, Parser, SchemeType}; |
15 | | use crate::{Host, ParseError, Position, Url}; |
16 | | use alloc::string::String; |
17 | | use alloc::string::ToString; |
18 | | |
19 | | /// Internal components / offsets of a URL. |
20 | | /// |
21 | | /// https://user@pass:example.com:1234/foo/bar?baz#quux |
22 | | /// | | | | ^^^^| | | |
23 | | /// | | | | | | | `----- fragment_start |
24 | | /// | | | | | | `--------- query_start |
25 | | /// | | | | | `----------------- path_start |
26 | | /// | | | | `--------------------- port |
27 | | /// | | | `----------------------- host_end |
28 | | /// | | `---------------------------------- host_start |
29 | | /// | `--------------------------------------- username_end |
30 | | /// `---------------------------------------------- scheme_end |
31 | | #[derive(Copy, Clone)] |
32 | | #[cfg(feature = "expose_internals")] |
33 | | pub struct InternalComponents { |
34 | | pub scheme_end: u32, |
35 | | pub username_end: u32, |
36 | | pub host_start: u32, |
37 | | pub host_end: u32, |
38 | | pub port: Option<u16>, |
39 | | pub path_start: u32, |
40 | | pub query_start: Option<u32>, |
41 | | pub fragment_start: Option<u32>, |
42 | | } |
43 | | |
44 | | /// Internal component / parsed offsets of the URL. |
45 | | /// |
46 | | /// This can be useful for implementing efficient serialization |
47 | | /// for the URL. |
48 | | #[cfg(feature = "expose_internals")] |
49 | | pub fn internal_components(url: &Url) -> InternalComponents { |
50 | | InternalComponents { |
51 | | scheme_end: url.scheme_end, |
52 | | username_end: url.username_end, |
53 | | host_start: url.host_start, |
54 | | host_end: url.host_end, |
55 | | port: url.port, |
56 | | path_start: url.path_start, |
57 | | query_start: url.query_start, |
58 | | fragment_start: url.fragment_start, |
59 | | } |
60 | | } |
61 | | |
62 | | /// <https://url.spec.whatwg.org/#dom-url-domaintoascii> |
63 | 0 | pub fn domain_to_ascii(domain: &str) -> String { |
64 | 0 | match Host::parse(domain) { |
65 | 0 | Ok(Host::Domain(domain)) => domain, |
66 | 0 | _ => String::new(), |
67 | | } |
68 | 0 | } |
69 | | |
70 | | /// <https://url.spec.whatwg.org/#dom-url-domaintounicode> |
71 | 0 | pub fn domain_to_unicode(domain: &str) -> String { |
72 | 0 | match Host::parse(domain) { |
73 | 0 | Ok(Host::Domain(ref domain)) => { |
74 | 0 | let (unicode, _errors) = idna::domain_to_unicode(domain); |
75 | 0 | unicode |
76 | | } |
77 | 0 | _ => String::new(), |
78 | | } |
79 | 0 | } |
80 | | |
81 | | /// Getter for <https://url.spec.whatwg.org/#dom-url-href> |
82 | 0 | pub fn href(url: &Url) -> &str { |
83 | 0 | url.as_str() |
84 | 0 | } |
85 | | |
86 | | /// Setter for <https://url.spec.whatwg.org/#dom-url-href> |
87 | 0 | pub fn set_href(url: &mut Url, value: &str) -> Result<(), ParseError> { |
88 | 0 | *url = Url::parse(value)?; |
89 | 0 | Ok(()) |
90 | 0 | } |
91 | | |
92 | | /// Getter for <https://url.spec.whatwg.org/#dom-url-origin> |
93 | 0 | pub fn origin(url: &Url) -> String { |
94 | 0 | url.origin().ascii_serialization() |
95 | 0 | } |
96 | | |
97 | | /// Getter for <https://url.spec.whatwg.org/#dom-url-protocol> |
98 | | #[inline] |
99 | 0 | pub fn protocol(url: &Url) -> &str { |
100 | 0 | &url.as_str()[..url.scheme().len() + ":".len()] |
101 | 0 | } |
102 | | |
103 | | /// Setter for <https://url.spec.whatwg.org/#dom-url-protocol> |
104 | | #[allow(clippy::result_unit_err)] |
105 | 0 | pub fn set_protocol(url: &mut Url, mut new_protocol: &str) -> Result<(), ()> { |
106 | | // The scheme state in the spec ignores everything after the first `:`, |
107 | | // but `set_scheme` errors if there is more. |
108 | 0 | if let Some(position) = new_protocol.find(':') { |
109 | 0 | new_protocol = &new_protocol[..position]; |
110 | 0 | } |
111 | 0 | url.set_scheme(new_protocol) |
112 | 0 | } |
113 | | |
114 | | /// Getter for <https://url.spec.whatwg.org/#dom-url-username> |
115 | | #[inline] |
116 | 0 | pub fn username(url: &Url) -> &str { |
117 | 0 | url.username() |
118 | 0 | } |
119 | | |
120 | | /// Setter for <https://url.spec.whatwg.org/#dom-url-username> |
121 | | #[allow(clippy::result_unit_err)] |
122 | 0 | pub fn set_username(url: &mut Url, new_username: &str) -> Result<(), ()> { |
123 | 0 | url.set_username(new_username) |
124 | 0 | } |
125 | | |
126 | | /// Getter for <https://url.spec.whatwg.org/#dom-url-password> |
127 | | #[inline] |
128 | 0 | pub fn password(url: &Url) -> &str { |
129 | 0 | url.password().unwrap_or("") |
130 | 0 | } |
131 | | |
132 | | /// Setter for <https://url.spec.whatwg.org/#dom-url-password> |
133 | | #[allow(clippy::result_unit_err)] |
134 | 0 | pub fn set_password(url: &mut Url, new_password: &str) -> Result<(), ()> { |
135 | 0 | url.set_password(if new_password.is_empty() { |
136 | 0 | None |
137 | | } else { |
138 | 0 | Some(new_password) |
139 | | }) |
140 | 0 | } |
141 | | |
142 | | /// Getter for <https://url.spec.whatwg.org/#dom-url-host> |
143 | | #[inline] |
144 | 0 | pub fn host(url: &Url) -> &str { |
145 | 0 | &url[Position::BeforeHost..Position::AfterPort] |
146 | 0 | } |
147 | | |
148 | | /// Setter for <https://url.spec.whatwg.org/#dom-url-host> |
149 | | #[allow(clippy::result_unit_err)] |
150 | 0 | pub fn set_host(url: &mut Url, new_host: &str) -> Result<(), ()> { |
151 | 0 | // If context object’s url’s cannot-be-a-base-URL flag is set, then return. |
152 | 0 | if url.cannot_be_a_base() { |
153 | 0 | return Err(()); |
154 | 0 | } |
155 | 0 | // Host parsing rules are strict, |
156 | 0 | // We don't want to trim the input |
157 | 0 | let input = Input::new_no_trim(new_host); |
158 | 0 | let host; |
159 | 0 | let opt_port; |
160 | 0 | { |
161 | 0 | let scheme = url.scheme(); |
162 | 0 | let scheme_type = SchemeType::from(scheme); |
163 | 0 | if scheme_type == SchemeType::File && new_host.is_empty() { |
164 | 0 | url.set_host_internal(Host::Domain(String::new()), None); |
165 | 0 | return Ok(()); |
166 | 0 | } |
167 | | |
168 | 0 | if let Ok((h, remaining)) = Parser::parse_host(input, scheme_type) { |
169 | 0 | host = h; |
170 | 0 | opt_port = if let Some(remaining) = remaining.split_prefix(':') { |
171 | 0 | if remaining.is_empty() { |
172 | 0 | None |
173 | | } else { |
174 | 0 | Parser::parse_port(remaining, || default_port(scheme), Context::Setter) |
175 | 0 | .ok() |
176 | 0 | .map(|(port, _remaining)| port) |
177 | | } |
178 | | } else { |
179 | 0 | None |
180 | | }; |
181 | | } else { |
182 | 0 | return Err(()); |
183 | | } |
184 | | } |
185 | | // Make sure we won't set an empty host to a url with a username or a port |
186 | 0 | if host == Host::Domain("".to_string()) |
187 | 0 | && (!username(url).is_empty() || matches!(opt_port, Some(Some(_))) || url.port().is_some()) |
188 | | { |
189 | 0 | return Err(()); |
190 | 0 | } |
191 | 0 | url.set_host_internal(host, opt_port); |
192 | 0 | Ok(()) |
193 | 0 | } |
194 | | |
195 | | /// Getter for <https://url.spec.whatwg.org/#dom-url-hostname> |
196 | | #[inline] |
197 | 0 | pub fn hostname(url: &Url) -> &str { |
198 | 0 | url.host_str().unwrap_or("") |
199 | 0 | } |
200 | | |
201 | | /// Setter for <https://url.spec.whatwg.org/#dom-url-hostname> |
202 | | #[allow(clippy::result_unit_err)] |
203 | 0 | pub fn set_hostname(url: &mut Url, new_hostname: &str) -> Result<(), ()> { |
204 | 0 | if url.cannot_be_a_base() { |
205 | 0 | return Err(()); |
206 | 0 | } |
207 | 0 | // Host parsing rules are strict we don't want to trim the input |
208 | 0 | let input = Input::new_no_trim(new_hostname); |
209 | 0 | let scheme_type = SchemeType::from(url.scheme()); |
210 | 0 | if scheme_type == SchemeType::File && new_hostname.is_empty() { |
211 | 0 | url.set_host_internal(Host::Domain(String::new()), None); |
212 | 0 | return Ok(()); |
213 | 0 | } |
214 | | |
215 | 0 | if let Ok((host, _remaining)) = Parser::parse_host(input, scheme_type) { |
216 | 0 | if let Host::Domain(h) = &host { |
217 | 0 | if h.is_empty() { |
218 | | // Empty host on special not file url |
219 | 0 | if SchemeType::from(url.scheme()) == SchemeType::SpecialNotFile |
220 | | // Port with an empty host |
221 | 0 | ||!port(url).is_empty() |
222 | | // Empty host that includes credentials |
223 | 0 | || !url.username().is_empty() |
224 | 0 | || !url.password().unwrap_or("").is_empty() |
225 | | { |
226 | 0 | return Err(()); |
227 | 0 | } |
228 | 0 | } |
229 | 0 | } |
230 | 0 | url.set_host_internal(host, None); |
231 | 0 | Ok(()) |
232 | | } else { |
233 | 0 | Err(()) |
234 | | } |
235 | 0 | } |
236 | | |
237 | | /// Getter for <https://url.spec.whatwg.org/#dom-url-port> |
238 | | #[inline] |
239 | 0 | pub fn port(url: &Url) -> &str { |
240 | 0 | &url[Position::BeforePort..Position::AfterPort] |
241 | 0 | } |
242 | | |
243 | | /// Setter for <https://url.spec.whatwg.org/#dom-url-port> |
244 | | #[allow(clippy::result_unit_err)] |
245 | 0 | pub fn set_port(url: &mut Url, new_port: &str) -> Result<(), ()> { |
246 | 0 | let result; |
247 | 0 | { |
248 | 0 | // has_host implies !cannot_be_a_base |
249 | 0 | let scheme = url.scheme(); |
250 | 0 | if !url.has_host() || url.host() == Some(Host::Domain("")) || scheme == "file" { |
251 | 0 | return Err(()); |
252 | 0 | } |
253 | 0 | result = Parser::parse_port( |
254 | 0 | Input::new_no_trim(new_port), |
255 | 0 | || default_port(scheme), |
256 | 0 | Context::Setter, |
257 | 0 | ) |
258 | | } |
259 | 0 | if let Ok((new_port, _remaining)) = result { |
260 | 0 | url.set_port_internal(new_port); |
261 | 0 | Ok(()) |
262 | | } else { |
263 | 0 | Err(()) |
264 | | } |
265 | 0 | } |
266 | | |
267 | | /// Getter for <https://url.spec.whatwg.org/#dom-url-pathname> |
268 | | #[inline] |
269 | 0 | pub fn pathname(url: &Url) -> &str { |
270 | 0 | url.path() |
271 | 0 | } |
272 | | |
273 | | /// Setter for <https://url.spec.whatwg.org/#dom-url-pathname> |
274 | 0 | pub fn set_pathname(url: &mut Url, new_pathname: &str) { |
275 | 0 | if url.cannot_be_a_base() { |
276 | 0 | return; |
277 | 0 | } |
278 | 0 | if new_pathname.starts_with('/') |
279 | 0 | || (SchemeType::from(url.scheme()).is_special() |
280 | | // \ is a segment delimiter for 'special' URLs" |
281 | 0 | && new_pathname.starts_with('\\')) |
282 | | { |
283 | 0 | url.set_path(new_pathname) |
284 | 0 | } else if SchemeType::from(url.scheme()).is_special() |
285 | 0 | || !new_pathname.is_empty() |
286 | 0 | || !url.has_host() |
287 | | { |
288 | 0 | let mut path_to_set = String::from("/"); |
289 | 0 | path_to_set.push_str(new_pathname); |
290 | 0 | url.set_path(&path_to_set) |
291 | | } else { |
292 | 0 | url.set_path(new_pathname) |
293 | | } |
294 | 0 | } |
295 | | |
296 | | /// Getter for <https://url.spec.whatwg.org/#dom-url-search> |
297 | 0 | pub fn search(url: &Url) -> &str { |
298 | 0 | trim(&url[Position::AfterPath..Position::AfterQuery]) |
299 | 0 | } |
300 | | |
301 | | /// Setter for <https://url.spec.whatwg.org/#dom-url-search> |
302 | 0 | pub fn set_search(url: &mut Url, new_search: &str) { |
303 | 0 | url.set_query(match new_search { |
304 | 0 | "" => None, |
305 | 0 | _ if new_search.starts_with('?') => Some(&new_search[1..]), |
306 | 0 | _ => Some(new_search), |
307 | | }) |
308 | 0 | } |
309 | | |
310 | | /// Getter for <https://url.spec.whatwg.org/#dom-url-hash> |
311 | 0 | pub fn hash(url: &Url) -> &str { |
312 | 0 | trim(&url[Position::AfterQuery..]) |
313 | 0 | } |
314 | | |
315 | | /// Setter for <https://url.spec.whatwg.org/#dom-url-hash> |
316 | 0 | pub fn set_hash(url: &mut Url, new_hash: &str) { |
317 | 0 | url.set_fragment(match new_hash { |
318 | | // If the given value is the empty string, |
319 | | // then set context object’s url’s fragment to null and return. |
320 | 0 | "" => None, |
321 | | // Let input be the given value with a single leading U+0023 (#) removed, if any. |
322 | 0 | _ if new_hash.starts_with('#') => Some(&new_hash[1..]), |
323 | 0 | _ => Some(new_hash), |
324 | | }) |
325 | 0 | } |
326 | | |
327 | 0 | fn trim(s: &str) -> &str { |
328 | 0 | if s.len() == 1 { |
329 | 0 | "" |
330 | | } else { |
331 | 0 | s |
332 | | } |
333 | 0 | } |