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};
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 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 if self.scheme() == "data" {
244 return true;
245 }
246 self.origin().is_potentially_trustworthy()
248 }
249
250 pub fn matches_about_blank(&self) -> bool {
252 let scheme_is_about = self.scheme() == "about";
256
257 let path_is_blank = self.0.path() == "blank";
259
260 let empty_username_and_password =
262 self.0.username().is_empty() && self.0.password().is_none();
263
264 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}