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
251impl fmt::Display for ServoUrl {
252    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
253        self.0.fmt(formatter)
254    }
255}
256
257impl fmt::Debug for ServoUrl {
258    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
259        let url_string = self.0.as_str();
260        if self.scheme() != "data" || url_string.len() <= DATA_URL_DISPLAY_LENGTH {
261            return url_string.fmt(formatter);
262        }
263
264        let mut hasher = DefaultHasher::new();
265        hasher.write(self.0.as_str().as_bytes());
266
267        format!(
268            "{}... ({:x})",
269            url_string
270                .chars()
271                .take(DATA_URL_DISPLAY_LENGTH)
272                .collect::<String>(),
273            hasher.finish()
274        )
275        .fmt(formatter)
276    }
277}
278
279impl Index<RangeFull> for ServoUrl {
280    type Output = str;
281    fn index(&self, _: RangeFull) -> &str {
282        &self.0[..]
283    }
284}
285
286impl Index<RangeFrom<Position>> for ServoUrl {
287    type Output = str;
288    fn index(&self, range: RangeFrom<Position>) -> &str {
289        &self.0[range]
290    }
291}
292
293impl Index<RangeTo<Position>> for ServoUrl {
294    type Output = str;
295    fn index(&self, range: RangeTo<Position>) -> &str {
296        &self.0[range]
297    }
298}
299
300impl Index<Range<Position>> for ServoUrl {
301    type Output = str;
302    fn index(&self, range: Range<Position>) -> &str {
303        &self.0[range]
304    }
305}
306
307impl From<Url> for ServoUrl {
308    fn from(url: Url) -> Self {
309        ServoUrl::from_url(url)
310    }
311}
312
313impl From<Arc<Url>> for ServoUrl {
314    fn from(url: Arc<Url>) -> Self {
315        ServoUrl(url)
316    }
317}
318
319impl FromStr for ServoUrl {
320    type Err = <Url as FromStr>::Err;
321
322    fn from_str(value: &str) -> Result<Self, Self::Err> {
323        let url = Url::from_str(value)?;
324        Ok(url.into())
325    }
326}