]>
Commit | Line | Data |
---|---|---|
abe05a73 XL |
1 | // Copyright 2016 The rust-url developers. |
2 | // | |
3 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
4 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
5 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
6 | // option. This file may not be copied, modified, or distributed | |
7 | // except according to those terms. | |
8 | ||
9 | //! Getters and setters for URL components implemented per https://url.spec.whatwg.org/#api | |
10 | //! | |
11 | //! Unless you need to be interoperable with web browsers, | |
12 | //! you probably want to use `Url` method instead. | |
13 | ||
5869c6ff XL |
14 | use crate::parser::{default_port, Context, Input, Parser, SchemeType}; |
15 | use crate::{Host, ParseError, Position, Url}; | |
abe05a73 XL |
16 | |
17 | /// https://url.spec.whatwg.org/#dom-url-domaintoascii | |
18 | pub fn domain_to_ascii(domain: &str) -> String { | |
19 | match Host::parse(domain) { | |
20 | Ok(Host::Domain(domain)) => domain, | |
21 | _ => String::new(), | |
22 | } | |
23 | } | |
24 | ||
25 | /// https://url.spec.whatwg.org/#dom-url-domaintounicode | |
26 | pub fn domain_to_unicode(domain: &str) -> String { | |
27 | match Host::parse(domain) { | |
28 | Ok(Host::Domain(ref domain)) => { | |
29 | let (unicode, _errors) = idna::domain_to_unicode(domain); | |
30 | unicode | |
31 | } | |
32 | _ => String::new(), | |
33 | } | |
34 | } | |
35 | ||
36 | /// Getter for https://url.spec.whatwg.org/#dom-url-href | |
37 | pub fn href(url: &Url) -> &str { | |
38 | url.as_str() | |
39 | } | |
40 | ||
41 | /// Setter for https://url.spec.whatwg.org/#dom-url-href | |
42 | pub fn set_href(url: &mut Url, value: &str) -> Result<(), ParseError> { | |
43 | *url = Url::parse(value)?; | |
44 | Ok(()) | |
45 | } | |
46 | ||
47 | /// Getter for https://url.spec.whatwg.org/#dom-url-origin | |
48 | pub fn origin(url: &Url) -> String { | |
49 | url.origin().ascii_serialization() | |
50 | } | |
51 | ||
52 | /// Getter for https://url.spec.whatwg.org/#dom-url-protocol | |
53 | #[inline] | |
54 | pub fn protocol(url: &Url) -> &str { | |
55 | &url.as_str()[..url.scheme().len() + ":".len()] | |
56 | } | |
57 | ||
58 | /// Setter for https://url.spec.whatwg.org/#dom-url-protocol | |
17df50a5 | 59 | #[allow(clippy::result_unit_err)] |
abe05a73 XL |
60 | pub fn set_protocol(url: &mut Url, mut new_protocol: &str) -> Result<(), ()> { |
61 | // The scheme state in the spec ignores everything after the first `:`, | |
62 | // but `set_scheme` errors if there is more. | |
63 | if let Some(position) = new_protocol.find(':') { | |
64 | new_protocol = &new_protocol[..position]; | |
65 | } | |
66 | url.set_scheme(new_protocol) | |
67 | } | |
68 | ||
69 | /// Getter for https://url.spec.whatwg.org/#dom-url-username | |
70 | #[inline] | |
71 | pub fn username(url: &Url) -> &str { | |
72 | url.username() | |
73 | } | |
74 | ||
75 | /// Setter for https://url.spec.whatwg.org/#dom-url-username | |
17df50a5 | 76 | #[allow(clippy::result_unit_err)] |
abe05a73 XL |
77 | pub fn set_username(url: &mut Url, new_username: &str) -> Result<(), ()> { |
78 | url.set_username(new_username) | |
79 | } | |
80 | ||
81 | /// Getter for https://url.spec.whatwg.org/#dom-url-password | |
82 | #[inline] | |
83 | pub fn password(url: &Url) -> &str { | |
84 | url.password().unwrap_or("") | |
85 | } | |
86 | ||
87 | /// Setter for https://url.spec.whatwg.org/#dom-url-password | |
17df50a5 | 88 | #[allow(clippy::result_unit_err)] |
abe05a73 | 89 | pub fn set_password(url: &mut Url, new_password: &str) -> Result<(), ()> { |
e74abb32 XL |
90 | url.set_password(if new_password.is_empty() { |
91 | None | |
92 | } else { | |
93 | Some(new_password) | |
94 | }) | |
abe05a73 XL |
95 | } |
96 | ||
97 | /// Getter for https://url.spec.whatwg.org/#dom-url-host | |
98 | #[inline] | |
99 | pub fn host(url: &Url) -> &str { | |
100 | &url[Position::BeforeHost..Position::AfterPort] | |
101 | } | |
102 | ||
103 | /// Setter for https://url.spec.whatwg.org/#dom-url-host | |
17df50a5 | 104 | #[allow(clippy::result_unit_err)] |
abe05a73 | 105 | pub fn set_host(url: &mut Url, new_host: &str) -> Result<(), ()> { |
f035d41b | 106 | // If context object’s url’s cannot-be-a-base-URL flag is set, then return. |
abe05a73 | 107 | if url.cannot_be_a_base() { |
e74abb32 | 108 | return Err(()); |
abe05a73 | 109 | } |
f035d41b XL |
110 | // Host parsing rules are strict, |
111 | // We don't want to trim the input | |
112 | let input = Input::no_trim(new_host); | |
abe05a73 XL |
113 | let host; |
114 | let opt_port; | |
115 | { | |
116 | let scheme = url.scheme(); | |
f035d41b | 117 | let scheme_type = SchemeType::from(scheme); |
5869c6ff XL |
118 | if scheme_type == SchemeType::File && new_host.is_empty() { |
119 | url.set_host_internal(Host::Domain(String::new()), None); | |
120 | return Ok(()); | |
121 | } | |
122 | ||
f035d41b XL |
123 | if let Ok((h, remaining)) = Parser::parse_host(input, scheme_type) { |
124 | host = h; | |
125 | opt_port = if let Some(remaining) = remaining.split_prefix(':') { | |
126 | if remaining.is_empty() { | |
127 | None | |
128 | } else { | |
abe05a73 | 129 | Parser::parse_port(remaining, || default_port(scheme), Context::Setter) |
e74abb32 XL |
130 | .ok() |
131 | .map(|(port, _remaining)| port) | |
f035d41b XL |
132 | } |
133 | } else { | |
134 | None | |
135 | }; | |
136 | } else { | |
137 | return Err(()); | |
138 | } | |
139 | } | |
140 | // Make sure we won't set an empty host to a url with a username or a port | |
141 | if host == Host::Domain("".to_string()) { | |
142 | if !username(&url).is_empty() { | |
143 | return Err(()); | |
5869c6ff XL |
144 | } else if let Some(Some(_)) = opt_port { |
145 | return Err(()); | |
146 | } else if url.port().is_some() { | |
f035d41b | 147 | return Err(()); |
abe05a73 XL |
148 | } |
149 | } | |
150 | url.set_host_internal(host, opt_port); | |
151 | Ok(()) | |
152 | } | |
153 | ||
154 | /// Getter for https://url.spec.whatwg.org/#dom-url-hostname | |
155 | #[inline] | |
156 | pub fn hostname(url: &Url) -> &str { | |
157 | url.host_str().unwrap_or("") | |
158 | } | |
159 | ||
160 | /// Setter for https://url.spec.whatwg.org/#dom-url-hostname | |
17df50a5 | 161 | #[allow(clippy::result_unit_err)] |
abe05a73 XL |
162 | pub fn set_hostname(url: &mut Url, new_hostname: &str) -> Result<(), ()> { |
163 | if url.cannot_be_a_base() { | |
e74abb32 | 164 | return Err(()); |
abe05a73 | 165 | } |
f035d41b XL |
166 | // Host parsing rules are strict we don't want to trim the input |
167 | let input = Input::no_trim(new_hostname); | |
168 | let scheme_type = SchemeType::from(url.scheme()); | |
5869c6ff XL |
169 | if scheme_type == SchemeType::File && new_hostname.is_empty() { |
170 | url.set_host_internal(Host::Domain(String::new()), None); | |
171 | return Ok(()); | |
172 | } | |
173 | ||
f035d41b XL |
174 | if let Ok((host, _remaining)) = Parser::parse_host(input, scheme_type) { |
175 | if let Host::Domain(h) = &host { | |
176 | if h.is_empty() { | |
177 | // Empty host on special not file url | |
178 | if SchemeType::from(url.scheme()) == SchemeType::SpecialNotFile | |
179 | // Port with an empty host | |
180 | ||!port(&url).is_empty() | |
181 | // Empty host that includes credentials | |
182 | || !url.username().is_empty() | |
183 | || !url.password().unwrap_or(&"").is_empty() | |
184 | { | |
185 | return Err(()); | |
186 | } | |
187 | } | |
188 | } | |
abe05a73 XL |
189 | url.set_host_internal(host, None); |
190 | Ok(()) | |
191 | } else { | |
192 | Err(()) | |
193 | } | |
194 | } | |
195 | ||
196 | /// Getter for https://url.spec.whatwg.org/#dom-url-port | |
197 | #[inline] | |
198 | pub fn port(url: &Url) -> &str { | |
199 | &url[Position::BeforePort..Position::AfterPort] | |
200 | } | |
201 | ||
202 | /// Setter for https://url.spec.whatwg.org/#dom-url-port | |
17df50a5 | 203 | #[allow(clippy::result_unit_err)] |
abe05a73 XL |
204 | pub fn set_port(url: &mut Url, new_port: &str) -> Result<(), ()> { |
205 | let result; | |
206 | { | |
207 | // has_host implies !cannot_be_a_base | |
208 | let scheme = url.scheme(); | |
2c00a5a8 | 209 | if !url.has_host() || url.host() == Some(Host::Domain("")) || scheme == "file" { |
e74abb32 | 210 | return Err(()); |
abe05a73 | 211 | } |
e74abb32 XL |
212 | result = Parser::parse_port( |
213 | Input::new(new_port), | |
214 | || default_port(scheme), | |
215 | Context::Setter, | |
216 | ) | |
abe05a73 XL |
217 | } |
218 | if let Ok((new_port, _remaining)) = result { | |
219 | url.set_port_internal(new_port); | |
220 | Ok(()) | |
221 | } else { | |
222 | Err(()) | |
223 | } | |
224 | } | |
225 | ||
226 | /// Getter for https://url.spec.whatwg.org/#dom-url-pathname | |
227 | #[inline] | |
228 | pub fn pathname(url: &Url) -> &str { | |
e74abb32 | 229 | url.path() |
abe05a73 XL |
230 | } |
231 | ||
232 | /// Setter for https://url.spec.whatwg.org/#dom-url-pathname | |
233 | pub fn set_pathname(url: &mut Url, new_pathname: &str) { | |
f035d41b XL |
234 | if url.cannot_be_a_base() { |
235 | return; | |
236 | } | |
5869c6ff | 237 | if new_pathname.starts_with('/') |
f035d41b XL |
238 | || (SchemeType::from(url.scheme()).is_special() |
239 | // \ is a segment delimiter for 'special' URLs" | |
5869c6ff | 240 | && new_pathname.starts_with('\\')) |
f035d41b | 241 | { |
abe05a73 | 242 | url.set_path(new_pathname) |
f035d41b XL |
243 | } else { |
244 | let mut path_to_set = String::from("/"); | |
245 | path_to_set.push_str(new_pathname); | |
246 | url.set_path(&path_to_set) | |
abe05a73 XL |
247 | } |
248 | } | |
249 | ||
250 | /// Getter for https://url.spec.whatwg.org/#dom-url-search | |
251 | pub fn search(url: &Url) -> &str { | |
252 | trim(&url[Position::AfterPath..Position::AfterQuery]) | |
253 | } | |
254 | ||
255 | /// Setter for https://url.spec.whatwg.org/#dom-url-search | |
256 | pub fn set_search(url: &mut Url, new_search: &str) { | |
257 | url.set_query(match new_search { | |
258 | "" => None, | |
259 | _ if new_search.starts_with('?') => Some(&new_search[1..]), | |
260 | _ => Some(new_search), | |
261 | }) | |
262 | } | |
263 | ||
264 | /// Getter for https://url.spec.whatwg.org/#dom-url-hash | |
265 | pub fn hash(url: &Url) -> &str { | |
266 | trim(&url[Position::AfterQuery..]) | |
267 | } | |
268 | ||
269 | /// Setter for https://url.spec.whatwg.org/#dom-url-hash | |
270 | pub fn set_hash(url: &mut Url, new_hash: &str) { | |
f035d41b XL |
271 | url.set_fragment(match new_hash { |
272 | // If the given value is the empty string, | |
273 | // then set context object’s url’s fragment to null and return. | |
274 | "" => None, | |
275 | // Let input be the given value with a single leading U+0023 (#) removed, if any. | |
276 | _ if new_hash.starts_with('#') => Some(&new_hash[1..]), | |
277 | _ => Some(new_hash), | |
278 | }) | |
abe05a73 XL |
279 | } |
280 | ||
281 | fn trim(s: &str) -> &str { | |
282 | if s.len() == 1 { | |
283 | "" | |
284 | } else { | |
285 | s | |
286 | } | |
287 | } |