servo_url/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#![deny(unsafe_code)]
6#![crate_name = "servo_url"]
7#![crate_type = "rlib"]
8
9pub mod encoding;
10pub mod origin;
11
12use std::collections::hash_map::DefaultHasher;
13use std::fmt;
14use std::hash::Hasher;
15use std::net::IpAddr;
16use std::ops::{Index, Range, RangeFrom, RangeFull, RangeTo};
17use std::path::Path;
18use std::str::FromStr;
19
20use malloc_size_of_derive::MallocSizeOf;
21use serde::{Deserialize, Serialize};
22use servo_arc::Arc;
23pub use url::Host;
24use url::{Position, Url};
25
26pub use crate::origin::{ImmutableOrigin, MutableOrigin, OpaqueOrigin};
27
28const DATA_URL_DISPLAY_LENGTH: usize = 40;
29
30#[derive(Debug)]
31pub enum UrlError {
32    SetUsername,
33    SetIpHost,
34    SetPassword,
35    ToFilePath,
36    FromFilePath,
37}
38
39#[derive(Clone, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize)]
40pub struct ServoUrl(#[conditional_malloc_size_of] Arc<Url>);
41
42impl ServoUrl {
43    pub fn from_url(url: Url) -> Self {
44        ServoUrl(Arc::new(url))
45    }
46
47    pub fn parse_with_base(base: Option<&Self>, input: &str) -> Result<Self, url::ParseError> {
48        Url::options()
49            .base_url(base.map(|b| &*b.0))
50            .parse(input)
51            .map(Self::from_url)
52    }
53
54    pub fn into_string(self) -> String {
55        String::from(self.into_url())
56    }
57
58    pub fn into_url(self) -> Url {
59        self.as_url().clone()
60    }
61
62    pub fn get_arc(&self) -> Arc<Url> {
63        self.0.clone()
64    }
65
66    pub fn as_url(&self) -> &Url {
67        &self.0
68    }
69
70    pub fn parse(input: &str) -> Result<Self, url::ParseError> {
71        Url::parse(input).map(Self::from_url)
72    }
73
74    pub fn cannot_be_a_base(&self) -> bool {
75        self.0.cannot_be_a_base()
76    }
77
78    pub fn domain(&self) -> Option<&str> {
79        self.0.domain()
80    }
81
82    pub fn fragment(&self) -> Option<&str> {
83        self.0.fragment()
84    }
85
86    pub fn path(&self) -> &str {
87        self.0.path()
88    }
89
90    pub fn origin(&self) -> ImmutableOrigin {
91        ImmutableOrigin::new(self.0.origin())
92    }
93
94    pub fn scheme(&self) -> &str {
95        self.0.scheme()
96    }
97
98    pub fn is_secure_scheme(&self) -> bool {
99        let scheme = self.scheme();
100        scheme == "https" || scheme == "wss"
101    }
102
103    /// <https://fetch.spec.whatwg.org/#local-scheme>
104    pub fn is_local_scheme(&self) -> bool {
105        let scheme = self.scheme();
106        scheme == "about" || scheme == "blob" || scheme == "data"
107    }
108
109    /// <https://url.spec.whatwg.org/#special-scheme>
110    pub fn is_special_scheme(&self) -> bool {
111        let scheme = self.scheme();
112        scheme == "ftp" ||
113            scheme == "file" ||
114            scheme == "http" ||
115            scheme == "https" ||
116            scheme == "ws" ||
117            scheme == "wss"
118    }
119
120    /// <https://url.spec.whatwg.org/#url-equivalence>
121    /// In the future this may be removed if the helper is added upstream in rust-url
122    /// see <https://github.com/servo/rust-url/issues/1063> for details
123    pub fn is_equal_excluding_fragments(&self, other: &ServoUrl) -> bool {
124        self.0[..Position::AfterQuery] == other.0[..Position::AfterQuery]
125    }
126
127    pub fn as_str(&self) -> &str {
128        self.0.as_str()
129    }
130
131    pub fn as_mut_url(&mut self) -> &mut Url {
132        Arc::make_mut(&mut self.0)
133    }
134
135    pub fn set_username(&mut self, user: &str) -> Result<(), UrlError> {
136        self.as_mut_url()
137            .set_username(user)
138            .map_err(|_| UrlError::SetUsername)
139    }
140
141    pub fn set_ip_host(&mut self, addr: IpAddr) -> Result<(), UrlError> {
142        self.as_mut_url()
143            .set_ip_host(addr)
144            .map_err(|_| UrlError::SetIpHost)
145    }
146
147    pub fn set_password(&mut self, pass: Option<&str>) -> Result<(), UrlError> {
148        self.as_mut_url()
149            .set_password(pass)
150            .map_err(|_| UrlError::SetPassword)
151    }
152
153    pub fn set_fragment(&mut self, fragment: Option<&str>) {
154        self.as_mut_url().set_fragment(fragment)
155    }
156
157    pub fn username(&self) -> &str {
158        self.0.username()
159    }
160
161    pub fn password(&self) -> Option<&str> {
162        self.0.password()
163    }
164
165    pub fn to_file_path(&self) -> Result<::std::path::PathBuf, UrlError> {
166        self.0.to_file_path().map_err(|_| UrlError::ToFilePath)
167    }
168
169    pub fn host(&self) -> Option<url::Host<&str>> {
170        self.0.host()
171    }
172
173    pub fn host_str(&self) -> Option<&str> {
174        self.0.host_str()
175    }
176
177    pub fn port(&self) -> Option<u16> {
178        self.0.port()
179    }
180
181    pub fn port_or_known_default(&self) -> Option<u16> {
182        self.0.port_or_known_default()
183    }
184
185    pub fn join(&self, input: &str) -> Result<ServoUrl, url::ParseError> {
186        self.0.join(input).map(Self::from_url)
187    }
188
189    pub fn path_segments(&self) -> Option<::std::str::Split<'_, char>> {
190        self.0.path_segments()
191    }
192
193    pub fn query(&self) -> Option<&str> {
194        self.0.query()
195    }
196
197    pub fn from_file_path<P: AsRef<Path>>(path: P) -> Result<Self, UrlError> {
198        Url::from_file_path(path)
199            .map(Self::from_url)
200            .map_err(|_| UrlError::FromFilePath)
201    }
202
203    /// Return a non-standard shortened form of the URL. Mainly intended to be
204    /// used for debug printing in a constrained space (e.g., thread names).
205    pub fn debug_compact(&self) -> impl std::fmt::Display + '_ {
206        match self.scheme() {
207            "http" | "https" => {
208                // Strip `scheme://`, which is hardly useful for identifying websites
209                let mut st = self.as_str();
210                st = st.strip_prefix(self.scheme()).unwrap_or(st);
211                st = st.strip_prefix(':').unwrap_or(st);
212                st = st.trim_start_matches('/');
213
214                // Don't want to return an empty string
215                if st.is_empty() {
216                    st = self.as_str();
217                }
218
219                st
220            },
221            "file" => {
222                // The only useful part in a `file` URL is usually only the last
223                // few components
224                let path = self.path();
225                let i = path.rfind('/');
226                let i = i.map(|i| path[..i].rfind('/').unwrap_or(i));
227                match i {
228                    None | Some(0) => path,
229                    Some(i) => &path[i + 1..],
230                }
231            },
232            _ => self.as_str(),
233        }
234    }
235
236    /// <https://w3c.github.io/webappsec-secure-contexts/#potentially-trustworthy-url>
237    pub fn is_potentially_trustworthy(&self) -> bool {
238        // Step 1
239        if self.as_str() == "about:blank" || self.as_str() == "about:srcdoc" {
240            return true;
241        }
242        // Step 2
243        if self.scheme() == "data" {
244            return true;
245        }
246        // Step 3
247        self.origin().is_potentially_trustworthy()
248    }
249
250    /// <https://html.spec.whatwg.org/multipage/#matches-about:blank>
251    pub fn matches_about_blank(&self) -> bool {
252        // A URL matches about:blank if
253
254        // its scheme is "about",
255        let scheme_is_about = self.scheme() == "about";
256
257        // its path contains a single string "blank",
258        let path_is_blank = self.0.path() == "blank";
259
260        // its username and password are the empty string,
261        let empty_username_and_password =
262            self.0.username().is_empty() && self.0.password().is_none();
263
264        // and its host is null.
265        let null_host = self.0.host().is_none();
266
267        scheme_is_about && path_is_blank && empty_username_and_password && null_host
268    }
269}
270
271impl fmt::Display for ServoUrl {
272    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
273        self.0.fmt(formatter)
274    }
275}
276
277impl fmt::Debug for ServoUrl {
278    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
279        let url_string = self.0.as_str();
280        if self.scheme() != "data" || url_string.len() <= DATA_URL_DISPLAY_LENGTH {
281            return url_string.fmt(formatter);
282        }
283
284        let mut hasher = DefaultHasher::new();
285        hasher.write(self.0.as_str().as_bytes());
286
287        format!(
288            "{}... ({:x})",
289            url_string
290                .chars()
291                .take(DATA_URL_DISPLAY_LENGTH)
292                .collect::<String>(),
293            hasher.finish()
294        )
295        .fmt(formatter)
296    }
297}
298
299impl Index<RangeFull> for ServoUrl {
300    type Output = str;
301    fn index(&self, _: RangeFull) -> &str {
302        &self.0[..]
303    }
304}
305
306impl Index<RangeFrom<Position>> for ServoUrl {
307    type Output = str;
308    fn index(&self, range: RangeFrom<Position>) -> &str {
309        &self.0[range]
310    }
311}
312
313impl Index<RangeTo<Position>> for ServoUrl {
314    type Output = str;
315    fn index(&self, range: RangeTo<Position>) -> &str {
316        &self.0[range]
317    }
318}
319
320impl Index<Range<Position>> for ServoUrl {
321    type Output = str;
322    fn index(&self, range: Range<Position>) -> &str {
323        &self.0[range]
324    }
325}
326
327impl From<Url> for ServoUrl {
328    fn from(url: Url) -> Self {
329        ServoUrl::from_url(url)
330    }
331}
332
333impl From<Arc<Url>> for ServoUrl {
334    fn from(url: Arc<Url>) -> Self {
335        ServoUrl(url)
336    }
337}
338
339impl FromStr for ServoUrl {
340    type Err = <Url as FromStr>::Err;
341
342    fn from_str(value: &str) -> Result<Self, Self::Err> {
343        let url = Url::from_str(value)?;
344        Ok(url.into())
345    }
346}