1#![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, OriginSnapshot};
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.as_url())
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 pub fn is_local_scheme(&self) -> bool {
105 let scheme = self.scheme();
106 scheme == "about" || scheme == "blob" || scheme == "data"
107 }
108
109 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 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 pub fn debug_compact(&self) -> impl std::fmt::Display + '_ {
206 match self.scheme() {
207 "http" | "https" => {
208 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 if st.is_empty() {
216 st = self.as_str();
217 }
218
219 st
220 },
221 "file" => {
222 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 pub fn is_potentially_trustworthy(&self) -> bool {
238 if self.as_str() == "about:blank" || self.as_str() == "about:srcdoc" {
240 return true;
241 }
242
243 if self.scheme() == "data" {
245 return true;
246 }
247
248 self.origin().is_potentially_trustworthy()
250 }
251
252 pub fn matches_about_blank(&self) -> bool {
254 let scheme_is_about = self.scheme() == "about";
258
259 let path_is_blank = self.0.path() == "blank";
261
262 let empty_username_and_password =
264 self.0.username().is_empty() && self.0.password().is_none();
265
266 let null_host = self.0.host().is_none();
268
269 scheme_is_about && path_is_blank && empty_username_and_password && null_host
270 }
271}
272
273impl fmt::Display for ServoUrl {
274 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
275 self.0.fmt(formatter)
276 }
277}
278
279impl fmt::Debug for ServoUrl {
280 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
281 let url_string = self.0.as_str();
282 if self.scheme() != "data" || url_string.len() <= DATA_URL_DISPLAY_LENGTH {
283 return url_string.fmt(formatter);
284 }
285
286 let mut hasher = DefaultHasher::new();
287 hasher.write(self.0.as_str().as_bytes());
288
289 format!(
290 "{}... ({:x})",
291 url_string
292 .chars()
293 .take(DATA_URL_DISPLAY_LENGTH)
294 .collect::<String>(),
295 hasher.finish()
296 )
297 .fmt(formatter)
298 }
299}
300
301impl Index<RangeFull> for ServoUrl {
302 type Output = str;
303 fn index(&self, _: RangeFull) -> &str {
304 &self.0[..]
305 }
306}
307
308impl Index<RangeFrom<Position>> for ServoUrl {
309 type Output = str;
310 fn index(&self, range: RangeFrom<Position>) -> &str {
311 &self.0[range]
312 }
313}
314
315impl Index<RangeTo<Position>> for ServoUrl {
316 type Output = str;
317 fn index(&self, range: RangeTo<Position>) -> &str {
318 &self.0[range]
319 }
320}
321
322impl Index<Range<Position>> for ServoUrl {
323 type Output = str;
324 fn index(&self, range: Range<Position>) -> &str {
325 &self.0[range]
326 }
327}
328
329impl From<Url> for ServoUrl {
330 fn from(url: Url) -> Self {
331 ServoUrl::from_url(url)
332 }
333}
334
335impl From<Arc<Url>> for ServoUrl {
336 fn from(url: Arc<Url>) -> Self {
337 ServoUrl(url)
338 }
339}
340
341impl FromStr for ServoUrl {
342 type Err = <Url as FromStr>::Err;
343
344 fn from_str(value: &str) -> Result<Self, Self::Err> {
345 let url = Url::from_str(value)?;
346 Ok(url.into())
347 }
348}