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
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}