]> git.proxmox.com Git - rustc.git/blame - vendor/url/src/lib.rs
New upstream version 1.50.0+dfsg1
[rustc.git] / vendor / url / src / lib.rs
CommitLineData
abe05a73
XL
1// Copyright 2013-2015 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/*!
10
11rust-url is an implementation of the [URL Standard](http://url.spec.whatwg.org/)
12for the [Rust](http://rust-lang.org/) programming language.
13
14
15# URL parsing and data structures
16
17First, URL parsing may fail for various reasons and therefore returns a `Result`.
18
19```
20use url::{Url, ParseError};
21
22assert!(Url::parse("http://[:::1]") == Err(ParseError::InvalidIpv6Address))
23```
24
25Let’s parse a valid URL and look at its components.
26
27```
e74abb32 28use url::{Url, Host, Position};
abe05a73
XL
29# use url::ParseError;
30# fn run() -> Result<(), ParseError> {
31let issue_list_url = Url::parse(
32 "https://github.com/rust-lang/rust/issues?labels=E-easy&state=open"
33)?;
34
35
36assert!(issue_list_url.scheme() == "https");
37assert!(issue_list_url.username() == "");
38assert!(issue_list_url.password() == None);
39assert!(issue_list_url.host_str() == Some("github.com"));
40assert!(issue_list_url.host() == Some(Host::Domain("github.com")));
41assert!(issue_list_url.port() == None);
42assert!(issue_list_url.path() == "/rust-lang/rust/issues");
43assert!(issue_list_url.path_segments().map(|c| c.collect::<Vec<_>>()) ==
44 Some(vec!["rust-lang", "rust", "issues"]));
45assert!(issue_list_url.query() == Some("labels=E-easy&state=open"));
e74abb32 46assert!(&issue_list_url[Position::BeforePath..] == "/rust-lang/rust/issues?labels=E-easy&state=open");
abe05a73
XL
47assert!(issue_list_url.fragment() == None);
48assert!(!issue_list_url.cannot_be_a_base());
49# Ok(())
50# }
51# run().unwrap();
52```
53
54Some URLs are said to be *cannot-be-a-base*:
55they don’t have a username, password, host, or port,
56and their "path" is an arbitrary string rather than slash-separated segments:
57
58```
59use url::Url;
60# use url::ParseError;
61
62# fn run() -> Result<(), ParseError> {
63let data_url = Url::parse("data:text/plain,Hello?World#")?;
64
65assert!(data_url.cannot_be_a_base());
66assert!(data_url.scheme() == "data");
67assert!(data_url.path() == "text/plain,Hello");
68assert!(data_url.path_segments().is_none());
69assert!(data_url.query() == Some("World"));
70assert!(data_url.fragment() == Some(""));
71# Ok(())
72# }
73# run().unwrap();
74```
75
76
77# Base URL
78
79Many contexts allow URL *references* that can be relative to a *base URL*:
80
81```html
82<link rel="stylesheet" href="../main.css">
83```
84
e74abb32 85Since parsed URLs are absolute, giving a base is required for parsing relative URLs:
abe05a73
XL
86
87```
88use url::{Url, ParseError};
89
90assert!(Url::parse("../main.css") == Err(ParseError::RelativeUrlWithoutBase))
91```
92
93Use the `join` method on an `Url` to use it as a base URL:
94
95```
96use url::Url;
97# use url::ParseError;
98
99# fn run() -> Result<(), ParseError> {
100let this_document = Url::parse("http://servo.github.io/rust-url/url/index.html")?;
101let css_url = this_document.join("../main.css")?;
102assert_eq!(css_url.as_str(), "http://servo.github.io/rust-url/main.css");
103# Ok(())
104# }
105# run().unwrap();
106*/
107
f035d41b 108#![doc(html_root_url = "https://docs.rs/url/2.1.1")]
abe05a73 109
0731742a 110#[macro_use]
e74abb32
XL
111extern crate matches;
112extern crate idna;
113extern crate percent_encoding;
114#[cfg(feature = "serde")]
115extern crate serde;
abe05a73 116
abe05a73 117use host::HostInternal;
e74abb32
XL
118use parser::{to_u32, Context, Parser, SchemeType, PATH_SEGMENT, USERINFO};
119use percent_encoding::{percent_decode, percent_encode, utf8_percent_encode};
abe05a73
XL
120use std::borrow::Borrow;
121use std::cmp;
e74abb32
XL
122#[cfg(feature = "serde")]
123use std::error::Error;
124use std::fmt::{self, Write};
abe05a73
XL
125use std::hash;
126use std::io;
127use std::mem;
e74abb32 128use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
abe05a73
XL
129use std::ops::{Range, RangeFrom, RangeTo};
130use std::path::{Path, PathBuf};
131use std::str;
132
e74abb32
XL
133pub use host::Host;
134pub use origin::{OpaqueOrigin, Origin};
2c00a5a8 135pub use parser::{ParseError, SyntaxViolation};
e74abb32
XL
136pub use path_segments::PathSegmentsMut;
137pub use query_encoding::EncodingOverride;
abe05a73
XL
138pub use slicing::Position;
139
abe05a73
XL
140mod host;
141mod origin;
abe05a73 142mod parser;
e74abb32
XL
143mod path_segments;
144mod query_encoding;
abe05a73
XL
145mod slicing;
146
147pub mod form_urlencoded;
e74abb32
XL
148#[doc(hidden)]
149pub mod quirks;
abe05a73
XL
150
151/// A parsed URL record.
152#[derive(Clone)]
153pub struct Url {
154 /// Syntax in pseudo-BNF:
155 ///
156 /// url = scheme ":" [ hierarchical | non-hierarchical ] [ "?" query ]? [ "#" fragment ]?
157 /// non-hierarchical = non-hierarchical-path
158 /// non-hierarchical-path = /* Does not start with "/" */
159 /// hierarchical = authority? hierarchical-path
160 /// authority = "//" userinfo? host [ ":" port ]?
161 /// userinfo = username [ ":" password ]? "@"
162 /// hierarchical-path = [ "/" path-segment ]+
163 serialization: String,
164
165 // Components
e74abb32
XL
166 scheme_end: u32, // Before ':'
167 username_end: u32, // Before ':' (if a password is given) or '@' (if not)
abe05a73
XL
168 host_start: u32,
169 host_end: u32,
170 host: HostInternal,
171 port: Option<u16>,
e74abb32
XL
172 path_start: u32, // Before initial '/', if any
173 query_start: Option<u32>, // Before '?', unlike Position::QueryStart
174 fragment_start: Option<u32>, // Before '#', unlike Position::FragmentStart
abe05a73
XL
175}
176
177/// Full configuration for the URL parser.
178#[derive(Copy, Clone)]
179pub struct ParseOptions<'a> {
180 base_url: Option<&'a Url>,
e74abb32
XL
181 encoding_override: EncodingOverride<'a>,
182 violation_fn: Option<&'a dyn Fn(SyntaxViolation)>,
abe05a73
XL
183}
184
185impl<'a> ParseOptions<'a> {
186 /// Change the base URL
187 pub fn base_url(mut self, new: Option<&'a Url>) -> Self {
188 self.base_url = new;
189 self
190 }
191
192 /// Override the character encoding of query strings.
193 /// This is a legacy concept only relevant for HTML.
e74abb32
XL
194 pub fn encoding_override(mut self, new: EncodingOverride<'a>) -> Self {
195 self.encoding_override = new;
2c00a5a8
XL
196 self
197 }
198
199 /// Call the provided function or closure for a non-fatal `SyntaxViolation`
200 /// when it occurs during parsing. Note that since the provided function is
201 /// `Fn`, the caller might need to utilize _interior mutability_, such as with
202 /// a `RefCell`, to collect the violations.
203 ///
204 /// ## Example
205 /// ```
206 /// use std::cell::RefCell;
207 /// use url::{Url, SyntaxViolation};
208 /// # use url::ParseError;
209 /// # fn run() -> Result<(), url::ParseError> {
210 /// let violations = RefCell::new(Vec::new());
211 /// let url = Url::options()
212 /// .syntax_violation_callback(Some(&|v| violations.borrow_mut().push(v)))
213 /// .parse("https:////example.com")?;
214 /// assert_eq!(url.as_str(), "https://example.com/");
215 /// assert_eq!(violations.into_inner(),
216 /// vec!(SyntaxViolation::ExpectedDoubleSlash));
217 /// # Ok(())
218 /// # }
219 /// # run().unwrap();
220 /// ```
e74abb32
XL
221 pub fn syntax_violation_callback(mut self, new: Option<&'a dyn Fn(SyntaxViolation)>) -> Self {
222 self.violation_fn = new;
abe05a73
XL
223 self
224 }
225
226 /// Parse an URL string with the configuration so far.
227 pub fn parse(self, input: &str) -> Result<Url, ::ParseError> {
228 Parser {
229 serialization: String::with_capacity(input.len()),
230 base_url: self.base_url,
231 query_encoding_override: self.encoding_override,
2c00a5a8 232 violation_fn: self.violation_fn,
abe05a73 233 context: Context::UrlParser,
e74abb32
XL
234 }
235 .parse_url(input)
abe05a73
XL
236 }
237}
238
239impl Url {
240 /// Parse an absolute URL from a string.
241 ///
242 /// # Examples
243 ///
244 /// ```rust
245 /// use url::Url;
246 /// # use url::ParseError;
247 ///
248 /// # fn run() -> Result<(), ParseError> {
249 /// let url = Url::parse("https://example.net")?;
250 /// # Ok(())
251 /// # }
252 /// # run().unwrap();
253 /// ```
2c00a5a8 254 ///
abe05a73
XL
255 /// # Errors
256 ///
257 /// If the function can not parse an absolute URL from the given string,
258 /// a [`ParseError`] variant will be returned.
259 ///
260 /// [`ParseError`]: enum.ParseError.html
261 #[inline]
262 pub fn parse(input: &str) -> Result<Url, ::ParseError> {
263 Url::options().parse(input)
264 }
265
266 /// Parse an absolute URL from a string and add params to its query string.
267 ///
268 /// Existing params are not removed.
269 ///
270 /// # Examples
271 ///
272 /// ```rust
273 /// use url::Url;
274 /// # use url::ParseError;
275 ///
276 /// # fn run() -> Result<(), ParseError> {
277 /// let url = Url::parse_with_params("https://example.net?dont=clobberme",
278 /// &[("lang", "rust"), ("browser", "servo")])?;
279 /// # Ok(())
280 /// # }
281 /// # run().unwrap();
282 /// ```
283 ///
284 /// # Errors
285 ///
286 /// If the function can not parse an absolute URL from the given string,
287 /// a [`ParseError`] variant will be returned.
288 ///
289 /// [`ParseError`]: enum.ParseError.html
290 #[inline]
291 pub fn parse_with_params<I, K, V>(input: &str, iter: I) -> Result<Url, ::ParseError>
e74abb32
XL
292 where
293 I: IntoIterator,
294 I::Item: Borrow<(K, V)>,
295 K: AsRef<str>,
296 V: AsRef<str>,
abe05a73
XL
297 {
298 let mut url = Url::options().parse(input);
299
300 if let Ok(ref mut url) = url {
301 url.query_pairs_mut().extend_pairs(iter);
302 }
303
304 url
305 }
306
307 /// Parse a string as an URL, with this URL as the base URL.
308 ///
309 /// Note: a trailing slash is significant.
310 /// Without it, the last path component is considered to be a “file” name
311 /// to be removed to get at the “directory” that is used as the base:
312 ///
313 /// # Examples
314 ///
315 /// ```rust
316 /// use url::Url;
317 /// # use url::ParseError;
2c00a5a8 318 ///
abe05a73
XL
319 /// # fn run() -> Result<(), ParseError> {
320 /// let base = Url::parse("https://example.net/a/b.html")?;
321 /// let url = base.join("c.png")?;
322 /// assert_eq!(url.as_str(), "https://example.net/a/c.png"); // Not /a/b.html/c.png
323 ///
324 /// let base = Url::parse("https://example.net/a/b/")?;
325 /// let url = base.join("c.png")?;
326 /// assert_eq!(url.as_str(), "https://example.net/a/b/c.png");
327 /// # Ok(())
328 /// # }
329 /// # run().unwrap();
330 /// ```
331 ///
332 /// # Errors
333 ///
2c00a5a8 334 /// If the function can not parse an URL from the given string
abe05a73
XL
335 /// with this URL as the base URL, a [`ParseError`] variant will be returned.
336 ///
337 /// [`ParseError`]: enum.ParseError.html
338 #[inline]
339 pub fn join(&self, input: &str) -> Result<Url, ::ParseError> {
340 Url::options().base_url(Some(self)).parse(input)
341 }
342
343 /// Return a default `ParseOptions` that can fully configure the URL parser.
344 ///
345 /// # Examples
346 ///
347 /// Get default `ParseOptions`, then change base url
348 ///
349 /// ```rust
350 /// use url::Url;
351 /// # use url::ParseError;
352 /// # fn run() -> Result<(), ParseError> {
353 /// let options = Url::options();
354 /// let api = Url::parse("https://api.example.com")?;
355 /// let base_url = options.base_url(Some(&api));
356 /// let version_url = base_url.parse("version.json")?;
357 /// assert_eq!(version_url.as_str(), "https://api.example.com/version.json");
358 /// # Ok(())
359 /// # }
360 /// # run().unwrap();
361 /// ```
362 pub fn options<'a>() -> ParseOptions<'a> {
363 ParseOptions {
364 base_url: None,
e74abb32
XL
365 encoding_override: None,
366 violation_fn: None,
abe05a73
XL
367 }
368 }
369
370 /// Return the serialization of this URL.
371 ///
372 /// This is fast since that serialization is already stored in the `Url` struct.
373 ///
374 /// # Examples
375 ///
376 /// ```rust
377 /// use url::Url;
378 /// # use url::ParseError;
379 ///
380 /// # fn run() -> Result<(), ParseError> {
381 /// let url_str = "https://example.net/";
382 /// let url = Url::parse(url_str)?;
383 /// assert_eq!(url.as_str(), url_str);
384 /// # Ok(())
385 /// # }
386 /// # run().unwrap();
387 /// ```
388 #[inline]
389 pub fn as_str(&self) -> &str {
390 &self.serialization
391 }
392
393 /// Return the serialization of this URL.
394 ///
395 /// This consumes the `Url` and takes ownership of the `String` stored in it.
396 ///
397 /// # Examples
398 ///
399 /// ```rust
400 /// use url::Url;
401 /// # use url::ParseError;
402 ///
403 /// # fn run() -> Result<(), ParseError> {
404 /// let url_str = "https://example.net/";
405 /// let url = Url::parse(url_str)?;
406 /// assert_eq!(url.into_string(), url_str);
407 /// # Ok(())
408 /// # }
409 /// # run().unwrap();
410 /// ```
411 #[inline]
412 pub fn into_string(self) -> String {
413 self.serialization
414 }
415
416 /// For internal testing, not part of the public API.
417 ///
418 /// Methods of the `Url` struct assume a number of invariants.
419 /// This checks each of these invariants and panic if one is not met.
420 /// This is for testing rust-url itself.
421 #[doc(hidden)]
422 pub fn check_invariants(&self) -> Result<(), String> {
423 macro_rules! assert {
424 ($x: expr) => {
425 if !$x {
e74abb32
XL
426 return Err(format!(
427 "!( {} ) for URL {:?}",
428 stringify!($x),
429 self.serialization
430 ));
abe05a73 431 }
e74abb32 432 };
abe05a73
XL
433 }
434
435 macro_rules! assert_eq {
436 ($a: expr, $b: expr) => {
437 {
438 let a = $a;
439 let b = $b;
440 if a != b {
441 return Err(format!("{:?} != {:?} ({} != {}) for URL {:?}",
442 a, b, stringify!($a), stringify!($b),
443 self.serialization))
444 }
445 }
446 }
447 }
448
449 assert!(self.scheme_end >= 1);
e74abb32
XL
450 assert!(matches!(self.byte_at(0), b'a'..=b'z' | b'A'..=b'Z'));
451 assert!(self
452 .slice(1..self.scheme_end)
453 .chars()
454 .all(|c| matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '+' | '-' | '.')));
abe05a73
XL
455 assert_eq!(self.byte_at(self.scheme_end), b':');
456
e74abb32 457 if self.slice(self.scheme_end + 1..).starts_with("//") {
abe05a73 458 // URL with authority
f035d41b
XL
459 if self.username_end != self.serialization.len() as u32 {
460 match self.byte_at(self.username_end) {
461 b':' => {
462 assert!(self.host_start >= self.username_end + 2);
463 assert_eq!(self.byte_at(self.host_start - 1), b'@');
464 }
465 b'@' => assert!(self.host_start == self.username_end + 1),
466 _ => assert_eq!(self.username_end, self.scheme_end + 3),
abe05a73 467 }
abe05a73
XL
468 }
469 assert!(self.host_start >= self.username_end);
470 assert!(self.host_end >= self.host_start);
471 let host_str = self.slice(self.host_start..self.host_end);
472 match self.host {
473 HostInternal::None => assert_eq!(host_str, ""),
474 HostInternal::Ipv4(address) => assert_eq!(host_str, address.to_string()),
475 HostInternal::Ipv6(address) => {
476 let h: Host<String> = Host::Ipv6(address);
477 assert_eq!(host_str, h.to_string())
478 }
479 HostInternal::Domain => {
480 if SchemeType::from(self.scheme()).is_special() {
481 assert!(!host_str.is_empty())
482 }
483 }
484 }
485 if self.path_start == self.host_end {
486 assert_eq!(self.port, None);
487 } else {
488 assert_eq!(self.byte_at(self.host_end), b':');
489 let port_str = self.slice(self.host_end + 1..self.path_start);
e74abb32
XL
490 assert_eq!(
491 self.port,
492 Some(port_str.parse::<u16>().expect("Couldn't parse port?"))
493 );
abe05a73 494 }
f035d41b
XL
495 assert!(
496 self.path_start as usize == self.serialization.len()
497 || matches!(self.byte_at(self.path_start), b'/' | b'#' | b'?')
498 );
abe05a73
XL
499 } else {
500 // Anarchist URL (no authority)
501 assert_eq!(self.username_end, self.scheme_end + 1);
502 assert_eq!(self.host_start, self.scheme_end + 1);
503 assert_eq!(self.host_end, self.scheme_end + 1);
504 assert_eq!(self.host, HostInternal::None);
505 assert_eq!(self.port, None);
506 assert_eq!(self.path_start, self.scheme_end + 1);
507 }
508 if let Some(start) = self.query_start {
f035d41b 509 assert!(start >= self.path_start);
abe05a73
XL
510 assert_eq!(self.byte_at(start), b'?');
511 }
512 if let Some(start) = self.fragment_start {
f035d41b 513 assert!(start >= self.path_start);
abe05a73
XL
514 assert_eq!(self.byte_at(start), b'#');
515 }
516 if let (Some(query_start), Some(fragment_start)) = (self.query_start, self.fragment_start) {
517 assert!(fragment_start > query_start);
518 }
519
520 let other = Url::parse(self.as_str()).expect("Failed to parse myself?");
521 assert_eq!(&self.serialization, &other.serialization);
522 assert_eq!(self.scheme_end, other.scheme_end);
523 assert_eq!(self.username_end, other.username_end);
524 assert_eq!(self.host_start, other.host_start);
525 assert_eq!(self.host_end, other.host_end);
e74abb32
XL
526 assert!(
527 self.host == other.host ||
abe05a73
XL
528 // XXX No host round-trips to empty host.
529 // See https://github.com/whatwg/url/issues/79
e74abb32
XL
530 (self.host_str(), other.host_str()) == (None, Some(""))
531 );
abe05a73
XL
532 assert_eq!(self.port, other.port);
533 assert_eq!(self.path_start, other.path_start);
534 assert_eq!(self.query_start, other.query_start);
535 assert_eq!(self.fragment_start, other.fragment_start);
536 Ok(())
537 }
538
539 /// Return the origin of this URL (<https://url.spec.whatwg.org/#origin>)
540 ///
541 /// Note: this returns an opaque origin for `file:` URLs, which causes
542 /// `url.origin() != url.origin()`.
543 ///
544 /// # Examples
545 ///
546 /// URL with `ftp` scheme:
547 ///
548 /// ```rust
549 /// use url::{Host, Origin, Url};
550 /// # use url::ParseError;
551 ///
552 /// # fn run() -> Result<(), ParseError> {
553 /// let url = Url::parse("ftp://example.com/foo")?;
554 /// assert_eq!(url.origin(),
555 /// Origin::Tuple("ftp".into(),
556 /// Host::Domain("example.com".into()),
557 /// 21));
558 /// # Ok(())
559 /// # }
560 /// # run().unwrap();
561 /// ```
562 ///
563 /// URL with `blob` scheme:
564 ///
565 /// ```rust
566 /// use url::{Host, Origin, Url};
567 /// # use url::ParseError;
568 ///
569 /// # fn run() -> Result<(), ParseError> {
570 /// let url = Url::parse("blob:https://example.com/foo")?;
571 /// assert_eq!(url.origin(),
572 /// Origin::Tuple("https".into(),
573 /// Host::Domain("example.com".into()),
574 /// 443));
575 /// # Ok(())
576 /// # }
577 /// # run().unwrap();
578 /// ```
579 ///
580 /// URL with `file` scheme:
581 ///
582 /// ```rust
583 /// use url::{Host, Origin, Url};
584 /// # use url::ParseError;
585 ///
586 /// # fn run() -> Result<(), ParseError> {
587 /// let url = Url::parse("file:///tmp/foo")?;
588 /// assert!(!url.origin().is_tuple());
589 ///
590 /// let other_url = Url::parse("file:///tmp/foo")?;
591 /// assert!(url.origin() != other_url.origin());
592 /// # Ok(())
593 /// # }
594 /// # run().unwrap();
595 /// ```
596 ///
597 /// URL with other scheme:
598 ///
599 /// ```rust
600 /// use url::{Host, Origin, Url};
601 /// # use url::ParseError;
602 ///
603 /// # fn run() -> Result<(), ParseError> {
604 /// let url = Url::parse("foo:bar")?;
605 /// assert!(!url.origin().is_tuple());
606 /// # Ok(())
607 /// # }
608 /// # run().unwrap();
609 /// ```
610 #[inline]
611 pub fn origin(&self) -> Origin {
612 origin::url_origin(self)
613 }
614
615 /// Return the scheme of this URL, lower-cased, as an ASCII string without the ':' delimiter.
616 ///
617 /// # Examples
618 ///
619 /// ```
620 /// use url::Url;
621 /// # use url::ParseError;
622 ///
623 /// # fn run() -> Result<(), ParseError> {
624 /// let url = Url::parse("file:///tmp/foo")?;
625 /// assert_eq!(url.scheme(), "file");
626 /// # Ok(())
627 /// # }
628 /// # run().unwrap();
629 /// ```
630 #[inline]
631 pub fn scheme(&self) -> &str {
632 self.slice(..self.scheme_end)
633 }
634
635 /// Return whether the URL has an 'authority',
636 /// which can contain a username, password, host, and port number.
637 ///
638 /// URLs that do *not* are either path-only like `unix:/run/foo.socket`
639 /// or cannot-be-a-base like `data:text/plain,Stuff`.
640 ///
641 /// # Examples
642 ///
643 /// ```
644 /// use url::Url;
645 /// # use url::ParseError;
646 ///
647 /// # fn run() -> Result<(), ParseError> {
648 /// let url = Url::parse("ftp://rms@example.com")?;
649 /// assert!(url.has_authority());
650 ///
651 /// let url = Url::parse("unix:/run/foo.socket")?;
652 /// assert!(!url.has_authority());
653 ///
654 /// let url = Url::parse("data:text/plain,Stuff")?;
655 /// assert!(!url.has_authority());
656 /// # Ok(())
657 /// # }
658 /// # run().unwrap();
659 /// ```
660 #[inline]
661 pub fn has_authority(&self) -> bool {
662 debug_assert!(self.byte_at(self.scheme_end) == b':');
663 self.slice(self.scheme_end..).starts_with("://")
664 }
665
666 /// Return whether this URL is a cannot-be-a-base URL,
667 /// meaning that parsing a relative URL string with this URL as the base will return an error.
668 ///
669 /// This is the case if the scheme and `:` delimiter are not followed by a `/` slash,
670 /// as is typically the case of `data:` and `mailto:` URLs.
671 ///
672 /// # Examples
673 ///
674 /// ```
675 /// use url::Url;
676 /// # use url::ParseError;
677 ///
678 /// # fn run() -> Result<(), ParseError> {
679 /// let url = Url::parse("ftp://rms@example.com")?;
680 /// assert!(!url.cannot_be_a_base());
681 ///
682 /// let url = Url::parse("unix:/run/foo.socket")?;
683 /// assert!(!url.cannot_be_a_base());
684 ///
685 /// let url = Url::parse("data:text/plain,Stuff")?;
686 /// assert!(url.cannot_be_a_base());
687 /// # Ok(())
688 /// # }
689 /// # run().unwrap();
690 /// ```
691 #[inline]
692 pub fn cannot_be_a_base(&self) -> bool {
f035d41b 693 !self.slice(self.scheme_end + 1..).starts_with('/')
abe05a73
XL
694 }
695
696 /// Return the username for this URL (typically the empty string)
697 /// as a percent-encoded ASCII string.
698 ///
699 /// # Examples
700 ///
701 /// ```
702 /// use url::Url;
703 /// # use url::ParseError;
704 ///
705 /// # fn run() -> Result<(), ParseError> {
706 /// let url = Url::parse("ftp://rms@example.com")?;
707 /// assert_eq!(url.username(), "rms");
708 ///
709 /// let url = Url::parse("ftp://:secret123@example.com")?;
710 /// assert_eq!(url.username(), "");
711 ///
712 /// let url = Url::parse("https://example.com")?;
713 /// assert_eq!(url.username(), "");
714 /// # Ok(())
715 /// # }
716 /// # run().unwrap();
717 /// ```
718 pub fn username(&self) -> &str {
719 if self.has_authority() {
720 self.slice(self.scheme_end + ("://".len() as u32)..self.username_end)
721 } else {
722 ""
723 }
724 }
725
726 /// Return the password for this URL, if any, as a percent-encoded ASCII string.
727 ///
728 /// # Examples
729 ///
730 /// ```
731 /// use url::Url;
732 /// # use url::ParseError;
733 ///
734 /// # fn run() -> Result<(), ParseError> {
735 /// let url = Url::parse("ftp://rms:secret123@example.com")?;
736 /// assert_eq!(url.password(), Some("secret123"));
737 ///
738 /// let url = Url::parse("ftp://:secret123@example.com")?;
739 /// assert_eq!(url.password(), Some("secret123"));
740 ///
741 /// let url = Url::parse("ftp://rms@example.com")?;
742 /// assert_eq!(url.password(), None);
743 ///
744 /// let url = Url::parse("https://example.com")?;
745 /// assert_eq!(url.password(), None);
746 /// # Ok(())
747 /// # }
748 /// # run().unwrap();
749 /// ```
750 pub fn password(&self) -> Option<&str> {
751 // This ':' is not the one marking a port number since a host can not be empty.
752 // (Except for file: URLs, which do not have port numbers.)
f035d41b
XL
753 if self.has_authority()
754 && self.username_end != self.serialization.len() as u32
755 && self.byte_at(self.username_end) == b':'
756 {
abe05a73
XL
757 debug_assert!(self.byte_at(self.host_start - 1) == b'@');
758 Some(self.slice(self.username_end + 1..self.host_start - 1))
759 } else {
760 None
761 }
762 }
763
764 /// Equivalent to `url.host().is_some()`.
765 ///
766 /// # Examples
767 ///
768 /// ```
769 /// use url::Url;
770 /// # use url::ParseError;
771 ///
772 /// # fn run() -> Result<(), ParseError> {
773 /// let url = Url::parse("ftp://rms@example.com")?;
774 /// assert!(url.has_host());
775 ///
776 /// let url = Url::parse("unix:/run/foo.socket")?;
777 /// assert!(!url.has_host());
778 ///
779 /// let url = Url::parse("data:text/plain,Stuff")?;
780 /// assert!(!url.has_host());
781 /// # Ok(())
782 /// # }
783 /// # run().unwrap();
784 /// ```
785 pub fn has_host(&self) -> bool {
786 !matches!(self.host, HostInternal::None)
787 }
788
789 /// Return the string representation of the host (domain or IP address) for this URL, if any.
790 ///
791 /// Non-ASCII domains are punycode-encoded per IDNA.
792 /// IPv6 addresses are given between `[` and `]` brackets.
793 ///
794 /// Cannot-be-a-base URLs (typical of `data:` and `mailto:`) and some `file:` URLs
795 /// don’t have a host.
796 ///
797 /// See also the `host` method.
798 ///
799 /// # Examples
800 ///
801 /// ```
802 /// use url::Url;
803 /// # use url::ParseError;
804 ///
805 /// # fn run() -> Result<(), ParseError> {
806 /// let url = Url::parse("https://127.0.0.1/index.html")?;
807 /// assert_eq!(url.host_str(), Some("127.0.0.1"));
808 ///
809 /// let url = Url::parse("ftp://rms@example.com")?;
810 /// assert_eq!(url.host_str(), Some("example.com"));
811 ///
812 /// let url = Url::parse("unix:/run/foo.socket")?;
813 /// assert_eq!(url.host_str(), None);
814 ///
815 /// let url = Url::parse("data:text/plain,Stuff")?;
816 /// assert_eq!(url.host_str(), None);
817 /// # Ok(())
818 /// # }
819 /// # run().unwrap();
820 /// ```
821 pub fn host_str(&self) -> Option<&str> {
822 if self.has_host() {
823 Some(self.slice(self.host_start..self.host_end))
824 } else {
825 None
826 }
827 }
828
829 /// Return the parsed representation of the host for this URL.
830 /// Non-ASCII domain labels are punycode-encoded per IDNA.
831 ///
832 /// Cannot-be-a-base URLs (typical of `data:` and `mailto:`) and some `file:` URLs
833 /// don’t have a host.
834 ///
835 /// See also the `host_str` method.
836 ///
837 /// # Examples
838 ///
839 /// ```
840 /// use url::Url;
841 /// # use url::ParseError;
842 ///
843 /// # fn run() -> Result<(), ParseError> {
844 /// let url = Url::parse("https://127.0.0.1/index.html")?;
845 /// assert!(url.host().is_some());
846 ///
847 /// let url = Url::parse("ftp://rms@example.com")?;
848 /// assert!(url.host().is_some());
849 ///
850 /// let url = Url::parse("unix:/run/foo.socket")?;
851 /// assert!(url.host().is_none());
852 ///
853 /// let url = Url::parse("data:text/plain,Stuff")?;
854 /// assert!(url.host().is_none());
855 /// # Ok(())
856 /// # }
857 /// # run().unwrap();
858 /// ```
859 pub fn host(&self) -> Option<Host<&str>> {
860 match self.host {
861 HostInternal::None => None,
862 HostInternal::Domain => Some(Host::Domain(self.slice(self.host_start..self.host_end))),
863 HostInternal::Ipv4(address) => Some(Host::Ipv4(address)),
864 HostInternal::Ipv6(address) => Some(Host::Ipv6(address)),
865 }
866 }
867
868 /// If this URL has a host and it is a domain name (not an IP address), return it.
869 ///
870 /// # Examples
871 ///
872 /// ```
873 /// use url::Url;
874 /// # use url::ParseError;
875 ///
876 /// # fn run() -> Result<(), ParseError> {
877 /// let url = Url::parse("https://127.0.0.1/")?;
878 /// assert_eq!(url.domain(), None);
879 ///
880 /// let url = Url::parse("mailto:rms@example.net")?;
881 /// assert_eq!(url.domain(), None);
882 ///
883 /// let url = Url::parse("https://example.com/")?;
884 /// assert_eq!(url.domain(), Some("example.com"));
885 /// # Ok(())
886 /// # }
887 /// # run().unwrap();
888 /// ```
889 pub fn domain(&self) -> Option<&str> {
890 match self.host {
891 HostInternal::Domain => Some(self.slice(self.host_start..self.host_end)),
892 _ => None,
893 }
894 }
895
896 /// Return the port number for this URL, if any.
897 ///
e74abb32
XL
898 /// Note that default port numbers are never reflected by the serialization,
899 /// use the `port_or_known_default()` method if you want a default port number returned.
900 ///
abe05a73
XL
901 /// # Examples
902 ///
903 /// ```
904 /// use url::Url;
905 /// # use url::ParseError;
906 ///
907 /// # fn run() -> Result<(), ParseError> {
908 /// let url = Url::parse("https://example.com")?;
909 /// assert_eq!(url.port(), None);
910 ///
e74abb32
XL
911 /// let url = Url::parse("https://example.com:443/")?;
912 /// assert_eq!(url.port(), None);
913 ///
abe05a73
XL
914 /// let url = Url::parse("ssh://example.com:22")?;
915 /// assert_eq!(url.port(), Some(22));
916 /// # Ok(())
917 /// # }
918 /// # run().unwrap();
919 /// ```
920 #[inline]
921 pub fn port(&self) -> Option<u16> {
922 self.port
923 }
924
925 /// Return the port number for this URL, or the default port number if it is known.
926 ///
927 /// This method only knows the default port number
928 /// of the `http`, `https`, `ws`, `wss`, `ftp`, and `gopher` schemes.
929 ///
930 /// For URLs in these schemes, this method always returns `Some(_)`.
931 /// For other schemes, it is the same as `Url::port()`.
932 ///
933 /// # Examples
934 ///
935 /// ```
936 /// use url::Url;
937 /// # use url::ParseError;
938 ///
939 /// # fn run() -> Result<(), ParseError> {
940 /// let url = Url::parse("foo://example.com")?;
941 /// assert_eq!(url.port_or_known_default(), None);
942 ///
943 /// let url = Url::parse("foo://example.com:1456")?;
944 /// assert_eq!(url.port_or_known_default(), Some(1456));
945 ///
946 /// let url = Url::parse("https://example.com")?;
947 /// assert_eq!(url.port_or_known_default(), Some(443));
948 /// # Ok(())
949 /// # }
950 /// # run().unwrap();
951 /// ```
952 #[inline]
953 pub fn port_or_known_default(&self) -> Option<u16> {
954 self.port.or_else(|| parser::default_port(self.scheme()))
955 }
956
e74abb32 957 /// Resolve a URL’s host and port number to `SocketAddr`.
abe05a73 958 ///
e74abb32
XL
959 /// If the URL has the default port number of a scheme that is unknown to this library,
960 /// `default_port_number` provides an opportunity to provide the actual port number.
961 /// In non-example code this should be implemented either simply as `|| None`,
962 /// or by matching on the URL’s `.scheme()`.
abe05a73 963 ///
e74abb32
XL
964 /// If the host is a domain, it is resolved using the standard library’s DNS support.
965 ///
966 /// # Examples
967 ///
968 /// ```no_run
969 /// let url = url::Url::parse("https://example.net/").unwrap();
970 /// let addrs = url.socket_addrs(|| None).unwrap();
971 /// std::net::TcpStream::connect(&*addrs)
972 /// # ;
973 /// ```
abe05a73 974 ///
e74abb32
XL
975 /// ```
976 /// /// With application-specific known default port numbers
977 /// fn socket_addrs(url: url::Url) -> std::io::Result<Vec<std::net::SocketAddr>> {
978 /// url.socket_addrs(|| match url.scheme() {
979 /// "socks5" | "socks5h" => Some(1080),
980 /// _ => None,
981 /// })
abe05a73
XL
982 /// }
983 /// ```
e74abb32
XL
984 pub fn socket_addrs(
985 &self,
986 default_port_number: impl Fn() -> Option<u16>,
987 ) -> io::Result<Vec<SocketAddr>> {
988 // Note: trying to avoid the Vec allocation by returning `impl AsRef<[SocketAddr]>`
989 // causes borrowck issues because the return value borrows `default_port_number`:
990 //
991 // https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md#scoping-for-type-and-lifetime-parameters
992 //
993 // > This RFC proposes that *all* type parameters are considered in scope
994 // > for `impl Trait` in return position
995
996 fn io_result<T>(opt: Option<T>, message: &str) -> io::Result<T> {
997 opt.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, message))
998 }
999
1000 let host = io_result(self.host(), "No host name in the URL")?;
1001 let port = io_result(
1002 self.port_or_known_default().or_else(default_port_number),
1003 "No port number in the URL",
1004 )?;
1005 Ok(match host {
1006 Host::Domain(domain) => (domain, port).to_socket_addrs()?.collect(),
1007 Host::Ipv4(ip) => vec![(ip, port).into()],
1008 Host::Ipv6(ip) => vec![(ip, port).into()],
abe05a73
XL
1009 })
1010 }
1011
1012 /// Return the path for this URL, as a percent-encoded ASCII string.
1013 /// For cannot-be-a-base URLs, this is an arbitrary string that doesn’t start with '/'.
1014 /// For other URLs, this starts with a '/' slash
1015 /// and continues with slash-separated path segments.
1016 ///
1017 /// # Examples
1018 ///
1019 /// ```rust
1020 /// use url::{Url, ParseError};
1021 ///
1022 /// # fn run() -> Result<(), ParseError> {
1023 /// let url = Url::parse("https://example.com/api/versions?page=2")?;
1024 /// assert_eq!(url.path(), "/api/versions");
1025 ///
1026 /// let url = Url::parse("https://example.com")?;
1027 /// assert_eq!(url.path(), "/");
1028 ///
1029 /// let url = Url::parse("https://example.com/countries/việt nam")?;
1030 /// assert_eq!(url.path(), "/countries/vi%E1%BB%87t%20nam");
1031 /// # Ok(())
1032 /// # }
1033 /// # run().unwrap();
1034 /// ```
1035 pub fn path(&self) -> &str {
1036 match (self.query_start, self.fragment_start) {
1037 (None, None) => self.slice(self.path_start..),
e74abb32 1038 (Some(next_component_start), _) | (None, Some(next_component_start)) => {
abe05a73
XL
1039 self.slice(self.path_start..next_component_start)
1040 }
1041 }
1042 }
1043
1044 /// Unless this URL is cannot-be-a-base,
1045 /// return an iterator of '/' slash-separated path segments,
1046 /// each as a percent-encoded ASCII string.
1047 ///
1048 /// Return `None` for cannot-be-a-base URLs.
1049 ///
1050 /// When `Some` is returned, the iterator always contains at least one string
1051 /// (which may be empty).
1052 ///
1053 /// # Examples
1054 ///
1055 /// ```
1056 /// use url::Url;
1057 /// # use std::error::Error;
1058 ///
1059 /// # fn run() -> Result<(), Box<Error>> {
1060 /// let url = Url::parse("https://example.com/foo/bar")?;
1061 /// let mut path_segments = url.path_segments().ok_or_else(|| "cannot be base")?;
1062 /// assert_eq!(path_segments.next(), Some("foo"));
1063 /// assert_eq!(path_segments.next(), Some("bar"));
1064 /// assert_eq!(path_segments.next(), None);
1065 ///
1066 /// let url = Url::parse("https://example.com")?;
1067 /// let mut path_segments = url.path_segments().ok_or_else(|| "cannot be base")?;
1068 /// assert_eq!(path_segments.next(), Some(""));
1069 /// assert_eq!(path_segments.next(), None);
1070 ///
1071 /// let url = Url::parse("data:text/plain,HelloWorld")?;
1072 /// assert!(url.path_segments().is_none());
1073 ///
1074 /// let url = Url::parse("https://example.com/countries/việt nam")?;
1075 /// let mut path_segments = url.path_segments().ok_or_else(|| "cannot be base")?;
1076 /// assert_eq!(path_segments.next(), Some("countries"));
1077 /// assert_eq!(path_segments.next(), Some("vi%E1%BB%87t%20nam"));
1078 /// # Ok(())
1079 /// # }
1080 /// # run().unwrap();
1081 /// ```
1082 pub fn path_segments(&self) -> Option<str::Split<char>> {
1083 let path = self.path();
1084 if path.starts_with('/') {
1085 Some(path[1..].split('/'))
1086 } else {
1087 None
1088 }
1089 }
1090
1091 /// Return this URL’s query string, if any, as a percent-encoded ASCII string.
1092 ///
1093 /// # Examples
1094 ///
1095 /// ```rust
1096 /// use url::Url;
1097 /// # use url::ParseError;
1098 ///
1099 /// fn run() -> Result<(), ParseError> {
1100 /// let url = Url::parse("https://example.com/products?page=2")?;
1101 /// let query = url.query();
1102 /// assert_eq!(query, Some("page=2"));
1103 ///
1104 /// let url = Url::parse("https://example.com/products")?;
1105 /// let query = url.query();
1106 /// assert!(query.is_none());
1107 ///
1108 /// let url = Url::parse("https://example.com/?country=español")?;
1109 /// let query = url.query();
1110 /// assert_eq!(query, Some("country=espa%C3%B1ol"));
1111 /// # Ok(())
1112 /// # }
1113 /// # run().unwrap();
1114 /// ```
1115 pub fn query(&self) -> Option<&str> {
1116 match (self.query_start, self.fragment_start) {
1117 (None, _) => None,
1118 (Some(query_start), None) => {
1119 debug_assert!(self.byte_at(query_start) == b'?');
1120 Some(self.slice(query_start + 1..))
1121 }
1122 (Some(query_start), Some(fragment_start)) => {
1123 debug_assert!(self.byte_at(query_start) == b'?');
1124 Some(self.slice(query_start + 1..fragment_start))
1125 }
1126 }
1127 }
1128
1129 /// Parse the URL’s query string, if any, as `application/x-www-form-urlencoded`
1130 /// and return an iterator of (key, value) pairs.
1131 ///
1132 /// # Examples
1133 ///
1134 /// ```rust
1135 /// use std::borrow::Cow;
1136 ///
1137 /// use url::Url;
1138 /// # use url::ParseError;
1139 ///
1140 /// # fn run() -> Result<(), ParseError> {
1141 /// let url = Url::parse("https://example.com/products?page=2&sort=desc")?;
1142 /// let mut pairs = url.query_pairs();
1143 ///
1144 /// assert_eq!(pairs.count(), 2);
1145 ///
1146 /// assert_eq!(pairs.next(), Some((Cow::Borrowed("page"), Cow::Borrowed("2"))));
1147 /// assert_eq!(pairs.next(), Some((Cow::Borrowed("sort"), Cow::Borrowed("desc"))));
1148 /// # Ok(())
1149 /// # }
1150 /// # run().unwrap();
1151 ///
1152
1153 #[inline]
1154 pub fn query_pairs(&self) -> form_urlencoded::Parse {
1155 form_urlencoded::parse(self.query().unwrap_or("").as_bytes())
1156 }
1157
1158 /// Return this URL’s fragment identifier, if any.
1159 ///
1160 /// A fragment is the part of the URL after the `#` symbol.
1161 /// The fragment is optional and, if present, contains a fragment identifier
1162 /// that identifies a secondary resource, such as a section heading
1163 /// of a document.
1164 ///
1165 /// In HTML, the fragment identifier is usually the id attribute of a an element
1166 /// that is scrolled to on load. Browsers typically will not send the fragment portion
1167 /// of a URL to the server.
1168 ///
1169 /// **Note:** the parser did *not* percent-encode this component,
1170 /// but the input may have been percent-encoded already.
1171 ///
1172 /// # Examples
1173 ///
1174 /// ```rust
1175 /// use url::Url;
1176 /// # use url::ParseError;
1177 ///
1178 /// # fn run() -> Result<(), ParseError> {
1179 /// let url = Url::parse("https://example.com/data.csv#row=4")?;
1180 ///
1181 /// assert_eq!(url.fragment(), Some("row=4"));
1182 ///
1183 /// let url = Url::parse("https://example.com/data.csv#cell=4,1-6,2")?;
1184 ///
1185 /// assert_eq!(url.fragment(), Some("cell=4,1-6,2"));
1186 /// # Ok(())
1187 /// # }
1188 /// # run().unwrap();
1189 /// ```
1190 pub fn fragment(&self) -> Option<&str> {
1191 self.fragment_start.map(|start| {
1192 debug_assert!(self.byte_at(start) == b'#');
1193 self.slice(start + 1..)
1194 })
1195 }
1196
1197 fn mutate<F: FnOnce(&mut Parser) -> R, R>(&mut self, f: F) -> R {
1198 let mut parser = Parser::for_setter(mem::replace(&mut self.serialization, String::new()));
1199 let result = f(&mut parser);
1200 self.serialization = parser.serialization;
1201 result
1202 }
1203
1204 /// Change this URL’s fragment identifier.
1205 ///
1206 /// # Examples
1207 ///
1208 /// ```rust
1209 /// use url::Url;
1210 /// # use url::ParseError;
1211 ///
1212 /// # fn run() -> Result<(), ParseError> {
1213 /// let mut url = Url::parse("https://example.com/data.csv")?;
1214 /// assert_eq!(url.as_str(), "https://example.com/data.csv");
1215
1216 /// url.set_fragment(Some("cell=4,1-6,2"));
2c00a5a8 1217 /// assert_eq!(url.as_str(), "https://example.com/data.csv#cell=4,1-6,2");
abe05a73
XL
1218 /// assert_eq!(url.fragment(), Some("cell=4,1-6,2"));
1219 ///
1220 /// url.set_fragment(None);
2c00a5a8 1221 /// assert_eq!(url.as_str(), "https://example.com/data.csv");
abe05a73
XL
1222 /// assert!(url.fragment().is_none());
1223 /// # Ok(())
1224 /// # }
1225 /// # run().unwrap();
1226 /// ```
1227 pub fn set_fragment(&mut self, fragment: Option<&str>) {
1228 // Remove any previous fragment
1229 if let Some(start) = self.fragment_start {
1230 debug_assert!(self.byte_at(start) == b'#');
1231 self.serialization.truncate(start as usize);
1232 }
1233 // Write the new one
1234 if let Some(input) = fragment {
1235 self.fragment_start = Some(to_u32(self.serialization.len()).unwrap());
1236 self.serialization.push('#');
f035d41b 1237 self.mutate(|parser| parser.parse_fragment(parser::Input::no_trim(input)))
abe05a73
XL
1238 } else {
1239 self.fragment_start = None
1240 }
1241 }
1242
1243 fn take_fragment(&mut self) -> Option<String> {
1244 self.fragment_start.take().map(|start| {
1245 debug_assert!(self.byte_at(start) == b'#');
1246 let fragment = self.slice(start + 1..).to_owned();
1247 self.serialization.truncate(start as usize);
1248 fragment
1249 })
1250 }
1251
1252 fn restore_already_parsed_fragment(&mut self, fragment: Option<String>) {
1253 if let Some(ref fragment) = fragment {
1254 assert!(self.fragment_start.is_none());
1255 self.fragment_start = Some(to_u32(self.serialization.len()).unwrap());
1256 self.serialization.push('#');
1257 self.serialization.push_str(fragment);
1258 }
1259 }
1260
1261 /// Change this URL’s query string.
1262 ///
1263 /// # Examples
1264 ///
1265 /// ```rust
1266 /// use url::Url;
1267 /// # use url::ParseError;
1268 ///
1269 /// # fn run() -> Result<(), ParseError> {
1270 /// let mut url = Url::parse("https://example.com/products")?;
1271 /// assert_eq!(url.as_str(), "https://example.com/products");
1272 ///
1273 /// url.set_query(Some("page=2"));
2c00a5a8 1274 /// assert_eq!(url.as_str(), "https://example.com/products?page=2");
abe05a73
XL
1275 /// assert_eq!(url.query(), Some("page=2"));
1276 /// # Ok(())
1277 /// # }
1278 /// # run().unwrap();
1279 /// ```
1280 pub fn set_query(&mut self, query: Option<&str>) {
1281 let fragment = self.take_fragment();
1282
1283 // Remove any previous query
1284 if let Some(start) = self.query_start.take() {
1285 debug_assert!(self.byte_at(start) == b'?');
1286 self.serialization.truncate(start as usize);
1287 }
1288 // Write the new query, if any
1289 if let Some(input) = query {
1290 self.query_start = Some(to_u32(self.serialization.len()).unwrap());
1291 self.serialization.push('?');
e74abb32 1292 let scheme_type = SchemeType::from(self.scheme());
abe05a73 1293 let scheme_end = self.scheme_end;
e74abb32 1294 self.mutate(|parser| {
f035d41b
XL
1295 let vfn = parser.violation_fn;
1296 parser.parse_query(
1297 scheme_type,
1298 scheme_end,
1299 parser::Input::trim_tab_and_newlines(input, vfn),
1300 )
e74abb32 1301 });
abe05a73
XL
1302 }
1303
1304 self.restore_already_parsed_fragment(fragment);
1305 }
1306
1307 /// Manipulate this URL’s query string, viewed as a sequence of name/value pairs
1308 /// in `application/x-www-form-urlencoded` syntax.
1309 ///
1310 /// The return value has a method-chaining API:
1311 ///
1312 /// ```rust
1313 /// # use url::{Url, ParseError};
1314 ///
1315 /// # fn run() -> Result<(), ParseError> {
1316 /// let mut url = Url::parse("https://example.net?lang=fr#nav")?;
1317 /// assert_eq!(url.query(), Some("lang=fr"));
1318 ///
1319 /// url.query_pairs_mut().append_pair("foo", "bar");
1320 /// assert_eq!(url.query(), Some("lang=fr&foo=bar"));
1321 /// assert_eq!(url.as_str(), "https://example.net/?lang=fr&foo=bar#nav");
1322 ///
1323 /// url.query_pairs_mut()
1324 /// .clear()
1325 /// .append_pair("foo", "bar & baz")
1326 /// .append_pair("saisons", "\u{00C9}t\u{00E9}+hiver");
1327 /// assert_eq!(url.query(), Some("foo=bar+%26+baz&saisons=%C3%89t%C3%A9%2Bhiver"));
1328 /// assert_eq!(url.as_str(),
1329 /// "https://example.net/?foo=bar+%26+baz&saisons=%C3%89t%C3%A9%2Bhiver#nav");
1330 /// # Ok(())
1331 /// # }
1332 /// # run().unwrap();
1333 /// ```
1334 ///
1335 /// Note: `url.query_pairs_mut().clear();` is equivalent to `url.set_query(Some(""))`,
1336 /// not `url.set_query(None)`.
1337 ///
1338 /// The state of `Url` is unspecified if this return value is leaked without being dropped.
1339 pub fn query_pairs_mut(&mut self) -> form_urlencoded::Serializer<UrlQuery> {
1340 let fragment = self.take_fragment();
1341
1342 let query_start;
1343 if let Some(start) = self.query_start {
1344 debug_assert!(self.byte_at(start) == b'?');
1345 query_start = start as usize;
1346 } else {
1347 query_start = self.serialization.len();
1348 self.query_start = Some(to_u32(query_start).unwrap());
1349 self.serialization.push('?');
1350 }
1351
e74abb32
XL
1352 let query = UrlQuery {
1353 url: Some(self),
1354 fragment,
1355 };
abe05a73
XL
1356 form_urlencoded::Serializer::for_suffix(query, query_start + "?".len())
1357 }
1358
1359 fn take_after_path(&mut self) -> String {
1360 match (self.query_start, self.fragment_start) {
1361 (Some(i), _) | (None, Some(i)) => {
1362 let after_path = self.slice(i..).to_owned();
1363 self.serialization.truncate(i as usize);
1364 after_path
e74abb32 1365 }
abe05a73
XL
1366 (None, None) => String::new(),
1367 }
1368 }
1369
1370 /// Change this URL’s path.
1371 ///
1372 /// # Examples
1373 ///
1374 /// ```rust
1375 /// use url::Url;
1376 /// # use url::ParseError;
1377 ///
1378 /// # fn run() -> Result<(), ParseError> {
1379 /// let mut url = Url::parse("https://example.com")?;
1380 /// url.set_path("api/comments");
2c00a5a8 1381 /// assert_eq!(url.as_str(), "https://example.com/api/comments");
abe05a73
XL
1382 /// assert_eq!(url.path(), "/api/comments");
1383 ///
1384 /// let mut url = Url::parse("https://example.com/api")?;
1385 /// url.set_path("data/report.csv");
2c00a5a8 1386 /// assert_eq!(url.as_str(), "https://example.com/data/report.csv");
abe05a73
XL
1387 /// assert_eq!(url.path(), "/data/report.csv");
1388 /// # Ok(())
1389 /// # }
1390 /// # run().unwrap();
1391 /// ```
1392 pub fn set_path(&mut self, mut path: &str) {
1393 let after_path = self.take_after_path();
1394 let old_after_path_pos = to_u32(self.serialization.len()).unwrap();
1395 let cannot_be_a_base = self.cannot_be_a_base();
1396 let scheme_type = SchemeType::from(self.scheme());
1397 self.serialization.truncate(self.path_start as usize);
1398 self.mutate(|parser| {
1399 if cannot_be_a_base {
1400 if path.starts_with('/') {
1401 parser.serialization.push_str("%2F");
1402 path = &path[1..];
1403 }
1404 parser.parse_cannot_be_a_base_path(parser::Input::new(path));
1405 } else {
e74abb32 1406 let mut has_host = true; // FIXME
abe05a73
XL
1407 parser.parse_path_start(scheme_type, &mut has_host, parser::Input::new(path));
1408 }
1409 });
1410 self.restore_after_path(old_after_path_pos, &after_path);
1411 }
1412
1413 /// Return an object with methods to manipulate this URL’s path segments.
1414 ///
1415 /// Return `Err(())` if this URL is cannot-be-a-base.
1416 pub fn path_segments_mut(&mut self) -> Result<PathSegmentsMut, ()> {
1417 if self.cannot_be_a_base() {
1418 Err(())
1419 } else {
1420 Ok(path_segments::new(self))
1421 }
1422 }
1423
1424 fn restore_after_path(&mut self, old_after_path_position: u32, after_path: &str) {
1425 let new_after_path_position = to_u32(self.serialization.len()).unwrap();
1426 let adjust = |index: &mut u32| {
1427 *index -= old_after_path_position;
1428 *index += new_after_path_position;
1429 };
e74abb32
XL
1430 if let Some(ref mut index) = self.query_start {
1431 adjust(index)
1432 }
1433 if let Some(ref mut index) = self.fragment_start {
1434 adjust(index)
1435 }
abe05a73
XL
1436 self.serialization.push_str(after_path)
1437 }
1438
1439 /// Change this URL’s port number.
1440 ///
e74abb32
XL
1441 /// Note that default port numbers are not reflected in the serialization.
1442 ///
abe05a73
XL
1443 /// If this URL is cannot-be-a-base, does not have a host, or has the `file` scheme;
1444 /// do nothing and return `Err`.
1445 ///
1446 /// # Examples
1447 ///
1448 /// ```
1449 /// use url::Url;
1450 /// # use std::error::Error;
1451 ///
1452 /// # fn run() -> Result<(), Box<Error>> {
1453 /// let mut url = Url::parse("ssh://example.net:2048/")?;
1454 ///
1455 /// url.set_port(Some(4096)).map_err(|_| "cannot be base")?;
1456 /// assert_eq!(url.as_str(), "ssh://example.net:4096/");
1457 ///
1458 /// url.set_port(None).map_err(|_| "cannot be base")?;
1459 /// assert_eq!(url.as_str(), "ssh://example.net/");
1460 /// # Ok(())
1461 /// # }
1462 /// # run().unwrap();
1463 /// ```
1464 ///
e74abb32
XL
1465 /// Known default port numbers are not reflected:
1466 ///
1467 /// ```rust
1468 /// use url::Url;
1469 /// # use std::error::Error;
1470 ///
1471 /// # fn run() -> Result<(), Box<Error>> {
1472 /// let mut url = Url::parse("https://example.org/")?;
1473 ///
1474 /// url.set_port(Some(443)).map_err(|_| "cannot be base")?;
1475 /// assert!(url.port().is_none());
1476 /// # Ok(())
1477 /// # }
1478 /// # run().unwrap();
1479 /// ```
1480 ///
abe05a73
XL
1481 /// Cannot set port for cannot-be-a-base URLs:
1482 ///
1483 /// ```
1484 /// use url::Url;
1485 /// # use url::ParseError;
1486 ///
1487 /// # fn run() -> Result<(), ParseError> {
1488 /// let mut url = Url::parse("mailto:rms@example.net")?;
1489 ///
1490 /// let result = url.set_port(Some(80));
1491 /// assert!(result.is_err());
1492 ///
1493 /// let result = url.set_port(None);
1494 /// assert!(result.is_err());
1495 /// # Ok(())
1496 /// # }
1497 /// # run().unwrap();
1498 /// ```
1499 pub fn set_port(&mut self, mut port: Option<u16>) -> Result<(), ()> {
2c00a5a8
XL
1500 // has_host implies !cannot_be_a_base
1501 if !self.has_host() || self.host() == Some(Host::Domain("")) || self.scheme() == "file" {
e74abb32 1502 return Err(());
abe05a73
XL
1503 }
1504 if port.is_some() && port == parser::default_port(self.scheme()) {
1505 port = None
1506 }
1507 self.set_port_internal(port);
1508 Ok(())
1509 }
1510
1511 fn set_port_internal(&mut self, port: Option<u16>) {
1512 match (self.port, port) {
1513 (None, None) => {}
1514 (Some(_), None) => {
e74abb32
XL
1515 self.serialization
1516 .drain(self.host_end as usize..self.path_start as usize);
abe05a73
XL
1517 let offset = self.path_start - self.host_end;
1518 self.path_start = self.host_end;
e74abb32
XL
1519 if let Some(ref mut index) = self.query_start {
1520 *index -= offset
1521 }
1522 if let Some(ref mut index) = self.fragment_start {
1523 *index -= offset
1524 }
abe05a73
XL
1525 }
1526 (Some(old), Some(new)) if old == new => {}
1527 (_, Some(new)) => {
1528 let path_and_after = self.slice(self.path_start..).to_owned();
1529 self.serialization.truncate(self.host_end as usize);
1530 write!(&mut self.serialization, ":{}", new).unwrap();
1531 let old_path_start = self.path_start;
1532 let new_path_start = to_u32(self.serialization.len()).unwrap();
1533 self.path_start = new_path_start;
1534 let adjust = |index: &mut u32| {
1535 *index -= old_path_start;
1536 *index += new_path_start;
1537 };
e74abb32
XL
1538 if let Some(ref mut index) = self.query_start {
1539 adjust(index)
1540 }
1541 if let Some(ref mut index) = self.fragment_start {
1542 adjust(index)
1543 }
abe05a73
XL
1544 self.serialization.push_str(&path_and_after);
1545 }
1546 }
1547 self.port = port;
1548 }
1549
1550 /// Change this URL’s host.
1551 ///
1552 /// Removing the host (calling this with `None`)
1553 /// will also remove any username, password, and port number.
1554 ///
1555 /// # Examples
1556 ///
1557 /// Change host:
1558 ///
1559 /// ```
1560 /// use url::Url;
1561 /// # use url::ParseError;
1562 ///
1563 /// # fn run() -> Result<(), ParseError> {
1564 /// let mut url = Url::parse("https://example.net")?;
1565 /// let result = url.set_host(Some("rust-lang.org"));
1566 /// assert!(result.is_ok());
1567 /// assert_eq!(url.as_str(), "https://rust-lang.org/");
1568 /// # Ok(())
1569 /// # }
1570 /// # run().unwrap();
1571 /// ```
1572 ///
1573 /// Remove host:
1574 ///
1575 /// ```
1576 /// use url::Url;
1577 /// # use url::ParseError;
2c00a5a8 1578 ///
abe05a73
XL
1579 /// # fn run() -> Result<(), ParseError> {
1580 /// let mut url = Url::parse("foo://example.net")?;
1581 /// let result = url.set_host(None);
1582 /// assert!(result.is_ok());
1583 /// assert_eq!(url.as_str(), "foo:/");
1584 /// # Ok(())
1585 /// # }
1586 /// # run().unwrap();
1587 /// ```
1588 ///
1589 /// Cannot remove host for 'special' schemes (e.g. `http`):
1590 ///
1591 /// ```
1592 /// use url::Url;
1593 /// # use url::ParseError;
2c00a5a8 1594 ///
abe05a73
XL
1595 /// # fn run() -> Result<(), ParseError> {
1596 /// let mut url = Url::parse("https://example.net")?;
1597 /// let result = url.set_host(None);
1598 /// assert!(result.is_err());
1599 /// assert_eq!(url.as_str(), "https://example.net/");
1600 /// # Ok(())
1601 /// # }
1602 /// # run().unwrap();
1603 /// ```
1604 ///
1605 /// Cannot change or remove host for cannot-be-a-base URLs:
1606 ///
1607 /// ```
1608 /// use url::Url;
1609 /// # use url::ParseError;
2c00a5a8 1610 ///
abe05a73
XL
1611 /// # fn run() -> Result<(), ParseError> {
1612 /// let mut url = Url::parse("mailto:rms@example.net")?;
1613 ///
1614 /// let result = url.set_host(Some("rust-lang.org"));
1615 /// assert!(result.is_err());
1616 /// assert_eq!(url.as_str(), "mailto:rms@example.net");
1617 ///
1618 /// let result = url.set_host(None);
1619 /// assert!(result.is_err());
1620 /// assert_eq!(url.as_str(), "mailto:rms@example.net");
1621 /// # Ok(())
1622 /// # }
1623 /// # run().unwrap();
1624 /// ```
1625 ///
1626 /// # Errors
1627 ///
1628 /// If this URL is cannot-be-a-base or there is an error parsing the given `host`,
1629 /// a [`ParseError`] variant will be returned.
1630 ///
1631 /// [`ParseError`]: enum.ParseError.html
1632 pub fn set_host(&mut self, host: Option<&str>) -> Result<(), ParseError> {
1633 if self.cannot_be_a_base() {
e74abb32 1634 return Err(ParseError::SetHostOnCannotBeABaseUrl);
abe05a73
XL
1635 }
1636
1637 if let Some(host) = host {
1638 if host == "" && SchemeType::from(self.scheme()).is_special() {
1639 return Err(ParseError::EmptyHost);
1640 }
f035d41b
XL
1641 let mut host_substr = host;
1642 // Otherwise, if c is U+003A (:) and the [] flag is unset, then
1643 if !host.starts_with('[') || !host.ends_with(']') {
1644 match host.find(':') {
1645 Some(0) => {
1646 // If buffer is the empty string, validation error, return failure.
1647 return Err(ParseError::InvalidDomainCharacter);
1648 }
1649 // Let host be the result of host parsing buffer
1650 Some(colon_index) => {
1651 host_substr = &host[..colon_index];
1652 }
1653 None => {}
1654 }
1655 }
2c00a5a8 1656 if SchemeType::from(self.scheme()).is_special() {
f035d41b 1657 self.set_host_internal(Host::parse(host_substr)?, None);
2c00a5a8 1658 } else {
f035d41b 1659 self.set_host_internal(Host::parse_opaque(host_substr)?, None);
2c00a5a8 1660 }
abe05a73 1661 } else if self.has_host() {
f035d41b
XL
1662 let scheme_type = SchemeType::from(self.scheme());
1663 if scheme_type.is_special() {
e74abb32 1664 return Err(ParseError::EmptyHost);
f035d41b
XL
1665 } else {
1666 if self.serialization.len() == self.path_start as usize {
1667 self.serialization.push('/');
1668 }
abe05a73
XL
1669 }
1670 debug_assert!(self.byte_at(self.scheme_end) == b':');
1671 debug_assert!(self.byte_at(self.path_start) == b'/');
1672 let new_path_start = self.scheme_end + 1;
e74abb32
XL
1673 self.serialization
1674 .drain(new_path_start as usize..self.path_start as usize);
abe05a73
XL
1675 let offset = self.path_start - new_path_start;
1676 self.path_start = new_path_start;
1677 self.username_end = new_path_start;
1678 self.host_start = new_path_start;
1679 self.host_end = new_path_start;
1680 self.port = None;
e74abb32
XL
1681 if let Some(ref mut index) = self.query_start {
1682 *index -= offset
1683 }
1684 if let Some(ref mut index) = self.fragment_start {
1685 *index -= offset
1686 }
abe05a73
XL
1687 }
1688 Ok(())
1689 }
1690
1691 /// opt_new_port: None means leave unchanged, Some(None) means remove any port number.
1692 fn set_host_internal(&mut self, host: Host<String>, opt_new_port: Option<Option<u16>>) {
e74abb32
XL
1693 let old_suffix_pos = if opt_new_port.is_some() {
1694 self.path_start
1695 } else {
1696 self.host_end
1697 };
abe05a73
XL
1698 let suffix = self.slice(old_suffix_pos..).to_owned();
1699 self.serialization.truncate(self.host_start as usize);
1700 if !self.has_authority() {
1701 debug_assert!(self.slice(self.scheme_end..self.host_start) == ":");
1702 debug_assert!(self.username_end == self.host_start);
1703 self.serialization.push('/');
1704 self.serialization.push('/');
1705 self.username_end += 2;
1706 self.host_start += 2;
1707 }
1708 write!(&mut self.serialization, "{}", host).unwrap();
1709 self.host_end = to_u32(self.serialization.len()).unwrap();
1710 self.host = host.into();
1711
1712 if let Some(new_port) = opt_new_port {
1713 self.port = new_port;
1714 if let Some(port) = new_port {
1715 write!(&mut self.serialization, ":{}", port).unwrap();
1716 }
1717 }
1718 let new_suffix_pos = to_u32(self.serialization.len()).unwrap();
1719 self.serialization.push_str(&suffix);
1720
1721 let adjust = |index: &mut u32| {
1722 *index -= old_suffix_pos;
1723 *index += new_suffix_pos;
1724 };
1725 adjust(&mut self.path_start);
e74abb32
XL
1726 if let Some(ref mut index) = self.query_start {
1727 adjust(index)
1728 }
1729 if let Some(ref mut index) = self.fragment_start {
1730 adjust(index)
1731 }
abe05a73
XL
1732 }
1733
1734 /// Change this URL’s host to the given IP address.
1735 ///
1736 /// If this URL is cannot-be-a-base, do nothing and return `Err`.
1737 ///
1738 /// Compared to `Url::set_host`, this skips the host parser.
1739 ///
1740 /// # Examples
1741 ///
1742 /// ```rust
1743 /// use url::{Url, ParseError};
1744 ///
1745 /// # fn run() -> Result<(), ParseError> {
1746 /// let mut url = Url::parse("http://example.com")?;
1747 /// url.set_ip_host("127.0.0.1".parse().unwrap());
1748 /// assert_eq!(url.host_str(), Some("127.0.0.1"));
1749 /// assert_eq!(url.as_str(), "http://127.0.0.1/");
1750 /// # Ok(())
1751 /// # }
1752 /// # run().unwrap();
1753 /// ```
1754 ///
1755 /// Cannot change URL's from mailto(cannot-be-base) to ip:
1756 ///
1757 /// ```rust
1758 /// use url::{Url, ParseError};
1759 ///
1760 /// # fn run() -> Result<(), ParseError> {
1761 /// let mut url = Url::parse("mailto:rms@example.com")?;
1762 /// let result = url.set_ip_host("127.0.0.1".parse().unwrap());
1763 ///
1764 /// assert_eq!(url.as_str(), "mailto:rms@example.com");
1765 /// assert!(result.is_err());
1766 /// # Ok(())
1767 /// # }
1768 /// # run().unwrap();
1769 /// ```
1770 ///
1771 pub fn set_ip_host(&mut self, address: IpAddr) -> Result<(), ()> {
1772 if self.cannot_be_a_base() {
e74abb32 1773 return Err(());
abe05a73
XL
1774 }
1775
1776 let address = match address {
1777 IpAddr::V4(address) => Host::Ipv4(address),
1778 IpAddr::V6(address) => Host::Ipv6(address),
1779 };
1780 self.set_host_internal(address, None);
1781 Ok(())
1782 }
1783
1784 /// Change this URL’s password.
1785 ///
1786 /// If this URL is cannot-be-a-base or does not have a host, do nothing and return `Err`.
1787 ///
1788 /// # Examples
1789 ///
1790 /// ```rust
1791 /// use url::{Url, ParseError};
1792 ///
1793 /// # fn run() -> Result<(), ParseError> {
1794 /// let mut url = Url::parse("mailto:rmz@example.com")?;
1795 /// let result = url.set_password(Some("secret_password"));
1796 /// assert!(result.is_err());
1797 ///
1798 /// let mut url = Url::parse("ftp://user1:secret1@example.com")?;
1799 /// let result = url.set_password(Some("secret_password"));
1800 /// assert_eq!(url.password(), Some("secret_password"));
1801 ///
1802 /// let mut url = Url::parse("ftp://user2:@example.com")?;
1803 /// let result = url.set_password(Some("secret2"));
1804 /// assert!(result.is_ok());
1805 /// assert_eq!(url.password(), Some("secret2"));
1806 /// # Ok(())
1807 /// # }
1808 /// # run().unwrap();
1809 /// ```
1810 pub fn set_password(&mut self, password: Option<&str>) -> Result<(), ()> {
2c00a5a8
XL
1811 // has_host implies !cannot_be_a_base
1812 if !self.has_host() || self.host() == Some(Host::Domain("")) || self.scheme() == "file" {
e74abb32 1813 return Err(());
abe05a73
XL
1814 }
1815 if let Some(password) = password {
1816 let host_and_after = self.slice(self.host_start..).to_owned();
1817 self.serialization.truncate(self.username_end as usize);
1818 self.serialization.push(':');
e74abb32
XL
1819 self.serialization
1820 .extend(utf8_percent_encode(password, USERINFO));
abe05a73
XL
1821 self.serialization.push('@');
1822
1823 let old_host_start = self.host_start;
1824 let new_host_start = to_u32(self.serialization.len()).unwrap();
1825 let adjust = |index: &mut u32| {
1826 *index -= old_host_start;
1827 *index += new_host_start;
1828 };
1829 self.host_start = new_host_start;
1830 adjust(&mut self.host_end);
1831 adjust(&mut self.path_start);
e74abb32
XL
1832 if let Some(ref mut index) = self.query_start {
1833 adjust(index)
1834 }
1835 if let Some(ref mut index) = self.fragment_start {
1836 adjust(index)
1837 }
abe05a73
XL
1838
1839 self.serialization.push_str(&host_and_after);
e74abb32
XL
1840 } else if self.byte_at(self.username_end) == b':' {
1841 // If there is a password to remove
abe05a73
XL
1842 let has_username_or_password = self.byte_at(self.host_start - 1) == b'@';
1843 debug_assert!(has_username_or_password);
1844 let username_start = self.scheme_end + 3;
1845 let empty_username = username_start == self.username_end;
e74abb32 1846 let start = self.username_end; // Remove the ':'
abe05a73
XL
1847 let end = if empty_username {
1848 self.host_start // Remove the '@' as well
1849 } else {
e74abb32 1850 self.host_start - 1 // Keep the '@' to separate the username from the host
abe05a73 1851 };
e74abb32 1852 self.serialization.drain(start as usize..end as usize);
abe05a73
XL
1853 let offset = end - start;
1854 self.host_start -= offset;
1855 self.host_end -= offset;
1856 self.path_start -= offset;
e74abb32
XL
1857 if let Some(ref mut index) = self.query_start {
1858 *index -= offset
1859 }
1860 if let Some(ref mut index) = self.fragment_start {
1861 *index -= offset
1862 }
abe05a73
XL
1863 }
1864 Ok(())
1865 }
1866
1867 /// Change this URL’s username.
1868 ///
1869 /// If this URL is cannot-be-a-base or does not have a host, do nothing and return `Err`.
1870 /// # Examples
1871 ///
1872 /// Cannot setup username from mailto(cannot-be-base)
1873 ///
1874 /// ```rust
1875 /// use url::{Url, ParseError};
1876 ///
1877 /// # fn run() -> Result<(), ParseError> {
1878 /// let mut url = Url::parse("mailto:rmz@example.com")?;
1879 /// let result = url.set_username("user1");
1880 /// assert_eq!(url.as_str(), "mailto:rmz@example.com");
1881 /// assert!(result.is_err());
1882 /// # Ok(())
1883 /// # }
1884 /// # run().unwrap();
1885 /// ```
1886 ///
1887 /// Setup username to user1
1888 ///
1889 /// ```rust
1890 /// use url::{Url, ParseError};
1891 ///
1892 /// # fn run() -> Result<(), ParseError> {
1893 /// let mut url = Url::parse("ftp://:secre1@example.com/")?;
1894 /// let result = url.set_username("user1");
1895 /// assert!(result.is_ok());
1896 /// assert_eq!(url.username(), "user1");
1897 /// assert_eq!(url.as_str(), "ftp://user1:secre1@example.com/");
1898 /// # Ok(())
1899 /// # }
1900 /// # run().unwrap();
1901 /// ```
1902 pub fn set_username(&mut self, username: &str) -> Result<(), ()> {
2c00a5a8
XL
1903 // has_host implies !cannot_be_a_base
1904 if !self.has_host() || self.host() == Some(Host::Domain("")) || self.scheme() == "file" {
e74abb32 1905 return Err(());
abe05a73
XL
1906 }
1907 let username_start = self.scheme_end + 3;
1908 debug_assert!(self.slice(self.scheme_end..username_start) == "://");
1909 if self.slice(username_start..self.username_end) == username {
e74abb32 1910 return Ok(());
abe05a73
XL
1911 }
1912 let after_username = self.slice(self.username_end..).to_owned();
1913 self.serialization.truncate(username_start as usize);
e74abb32
XL
1914 self.serialization
1915 .extend(utf8_percent_encode(username, USERINFO));
abe05a73
XL
1916
1917 let mut removed_bytes = self.username_end;
1918 self.username_end = to_u32(self.serialization.len()).unwrap();
1919 let mut added_bytes = self.username_end;
1920
1921 let new_username_is_empty = self.username_end == username_start;
1922 match (new_username_is_empty, after_username.chars().next()) {
1923 (true, Some('@')) => {
1924 removed_bytes += 1;
1925 self.serialization.push_str(&after_username[1..]);
1926 }
1927 (false, Some('@')) | (_, Some(':')) | (true, _) => {
1928 self.serialization.push_str(&after_username);
1929 }
1930 (false, _) => {
1931 added_bytes += 1;
1932 self.serialization.push('@');
1933 self.serialization.push_str(&after_username);
1934 }
1935 }
1936
1937 let adjust = |index: &mut u32| {
1938 *index -= removed_bytes;
1939 *index += added_bytes;
1940 };
1941 adjust(&mut self.host_start);
1942 adjust(&mut self.host_end);
1943 adjust(&mut self.path_start);
e74abb32
XL
1944 if let Some(ref mut index) = self.query_start {
1945 adjust(index)
1946 }
1947 if let Some(ref mut index) = self.fragment_start {
1948 adjust(index)
1949 }
abe05a73
XL
1950 Ok(())
1951 }
1952
1953 /// Change this URL’s scheme.
1954 ///
1955 /// Do nothing and return `Err` if:
1956 ///
1957 /// * The new scheme is not in `[a-zA-Z][a-zA-Z0-9+.-]+`
1958 /// * This URL is cannot-be-a-base and the new scheme is one of
1959 /// `http`, `https`, `ws`, `wss`, `ftp`, or `gopher`
1960 ///
1961 /// # Examples
1962 ///
1963 /// Change the URL’s scheme from `https` to `foo`:
1964 ///
1965 /// ```
1966 /// use url::Url;
1967 /// # use url::ParseError;
2c00a5a8 1968 ///
abe05a73
XL
1969 /// # fn run() -> Result<(), ParseError> {
1970 /// let mut url = Url::parse("https://example.net")?;
f035d41b
XL
1971 /// let result = url.set_scheme("http");
1972 /// assert_eq!(url.as_str(), "http://example.net/");
abe05a73
XL
1973 /// assert!(result.is_ok());
1974 /// # Ok(())
1975 /// # }
1976 /// # run().unwrap();
1977 /// ```
f035d41b 1978 /// Change the URL’s scheme from `foo` to `bar`:
abe05a73 1979 ///
f035d41b
XL
1980 /// ```
1981 /// use url::Url;
1982 /// # use url::ParseError;
1983 ///
1984 /// # fn run() -> Result<(), ParseError> {
1985 /// let mut url = Url::parse("foo://example.net")?;
1986 /// let result = url.set_scheme("bar");
1987 /// assert_eq!(url.as_str(), "bar://example.net");
1988 /// assert!(result.is_ok());
1989 /// # Ok(())
1990 /// # }
1991 /// # run().unwrap();
1992 /// ```
abe05a73
XL
1993 ///
1994 /// Cannot change URL’s scheme from `https` to `foõ`:
1995 ///
1996 /// ```
1997 /// use url::Url;
1998 /// # use url::ParseError;
2c00a5a8 1999 ///
abe05a73
XL
2000 /// # fn run() -> Result<(), ParseError> {
2001 /// let mut url = Url::parse("https://example.net")?;
2002 /// let result = url.set_scheme("foõ");
2003 /// assert_eq!(url.as_str(), "https://example.net/");
2004 /// assert!(result.is_err());
2005 /// # Ok(())
2006 /// # }
2007 /// # run().unwrap();
2008 /// ```
2009 ///
2010 /// Cannot change URL’s scheme from `mailto` (cannot-be-a-base) to `https`:
2011 ///
2012 /// ```
2013 /// use url::Url;
2014 /// # use url::ParseError;
2c00a5a8 2015 ///
abe05a73
XL
2016 /// # fn run() -> Result<(), ParseError> {
2017 /// let mut url = Url::parse("mailto:rms@example.net")?;
2018 /// let result = url.set_scheme("https");
2019 /// assert_eq!(url.as_str(), "mailto:rms@example.net");
2020 /// assert!(result.is_err());
2021 /// # Ok(())
2022 /// # }
2023 /// # run().unwrap();
2024 /// ```
f035d41b
XL
2025 /// Cannot change the URL’s scheme from `foo` to `https`:
2026 ///
2027 /// ```
2028 /// use url::Url;
2029 /// # use url::ParseError;
2030 ///
2031 /// # fn run() -> Result<(), ParseError> {
2032 /// let mut url = Url::parse("foo://example.net")?;
2033 /// let result = url.set_scheme("https");
2034 /// assert_eq!(url.as_str(), "foo://example.net");
2035 /// assert!(result.is_err());
2036 /// # Ok(())
2037 /// # }
2038 /// # run().unwrap();
2039 /// ```
2040 /// Cannot change the URL’s scheme from `http` to `foo`:
2041 ///
2042 /// ```
2043 /// use url::Url;
2044 /// # use url::ParseError;
2045 ///
2046 /// # fn run() -> Result<(), ParseError> {
2047 /// let mut url = Url::parse("http://example.net")?;
2048 /// let result = url.set_scheme("foo");
2049 /// assert_eq!(url.as_str(), "http://example.net/");
2050 /// assert!(result.is_err());
2051 /// # Ok(())
2052 /// # }
2053 /// # run().unwrap();
2054 /// ```
abe05a73
XL
2055 pub fn set_scheme(&mut self, scheme: &str) -> Result<(), ()> {
2056 let mut parser = Parser::for_setter(String::new());
2057 let remaining = parser.parse_scheme(parser::Input::new(scheme))?;
f035d41b
XL
2058 let new_scheme_type = SchemeType::from(&parser.serialization);
2059 let old_scheme_type = SchemeType::from(self.scheme());
2060 // If url’s scheme is a special scheme and buffer is not a special scheme, then return.
2061 if (new_scheme_type.is_special() && !old_scheme_type.is_special()) ||
2062 // If url’s scheme is not a special scheme and buffer is a special scheme, then return.
2063 (!new_scheme_type.is_special() && old_scheme_type.is_special()) ||
2064 // If url includes credentials or has a non-null port, and buffer is "file", then return.
2065 // If url’s scheme is "file" and its host is an empty host or null, then return.
2066 (new_scheme_type.is_file() && self.has_authority())
e74abb32
XL
2067 {
2068 return Err(());
abe05a73 2069 }
f035d41b
XL
2070
2071 if !remaining.is_empty() || (!self.has_host() && new_scheme_type.is_special()) {
2072 return Err(());
2073 }
abe05a73
XL
2074 let old_scheme_end = self.scheme_end;
2075 let new_scheme_end = to_u32(parser.serialization.len()).unwrap();
2076 let adjust = |index: &mut u32| {
2077 *index -= old_scheme_end;
2078 *index += new_scheme_end;
2079 };
2080
2081 self.scheme_end = new_scheme_end;
2082 adjust(&mut self.username_end);
2083 adjust(&mut self.host_start);
2084 adjust(&mut self.host_end);
2085 adjust(&mut self.path_start);
e74abb32
XL
2086 if let Some(ref mut index) = self.query_start {
2087 adjust(index)
2088 }
2089 if let Some(ref mut index) = self.fragment_start {
2090 adjust(index)
2091 }
abe05a73
XL
2092
2093 parser.serialization.push_str(self.slice(old_scheme_end..));
2094 self.serialization = parser.serialization;
f035d41b
XL
2095
2096 // Update the port so it can be removed
2097 // If it is the scheme's default
2098 // we don't mind it silently failing
2099 // if there was no port in the first place
2100 let previous_port = self.port();
2101 let _ = self.set_port(previous_port);
2102
abe05a73
XL
2103 Ok(())
2104 }
2105
2106 /// Convert a file name as `std::path::Path` into an URL in the `file` scheme.
2107 ///
2108 /// This returns `Err` if the given path is not absolute or,
2109 /// on Windows, if the prefix is not a disk prefix (e.g. `C:`) or a UNC prefix (`\\`).
2110 ///
2111 /// # Examples
2112 ///
2113 /// On Unix-like platforms:
2114 ///
2115 /// ```
2116 /// # if cfg!(unix) {
2117 /// use url::Url;
2c00a5a8 2118 ///
abe05a73
XL
2119 /// # fn run() -> Result<(), ()> {
2120 /// let url = Url::from_file_path("/tmp/foo.txt")?;
2121 /// assert_eq!(url.as_str(), "file:///tmp/foo.txt");
2122 ///
2123 /// let url = Url::from_file_path("../foo.txt");
2124 /// assert!(url.is_err());
2125 ///
2126 /// let url = Url::from_file_path("https://google.com/");
2127 /// assert!(url.is_err());
2128 /// # Ok(())
2129 /// # }
2130 /// # run().unwrap();
2131 /// # }
2132 /// ```
e74abb32 2133 #[cfg(any(unix, windows, target_os = "redox"))]
abe05a73
XL
2134 pub fn from_file_path<P: AsRef<Path>>(path: P) -> Result<Url, ()> {
2135 let mut serialization = "file://".to_owned();
2136 let host_start = serialization.len() as u32;
2137 let (host_end, host) = path_to_file_url_segments(path.as_ref(), &mut serialization)?;
2138 Ok(Url {
e74abb32 2139 serialization,
abe05a73
XL
2140 scheme_end: "file".len() as u32,
2141 username_end: host_start,
e74abb32
XL
2142 host_start,
2143 host_end,
2144 host,
abe05a73
XL
2145 port: None,
2146 path_start: host_end,
2147 query_start: None,
2148 fragment_start: None,
2149 })
2150 }
2151
2152 /// Convert a directory name as `std::path::Path` into an URL in the `file` scheme.
2153 ///
2154 /// This returns `Err` if the given path is not absolute or,
2155 /// on Windows, if the prefix is not a disk prefix (e.g. `C:`) or a UNC prefix (`\\`).
2156 ///
2157 /// Compared to `from_file_path`, this ensure that URL’s the path has a trailing slash
2158 /// so that the entire path is considered when using this URL as a base URL.
2159 ///
2160 /// For example:
2161 ///
2162 /// * `"index.html"` parsed with `Url::from_directory_path(Path::new("/var/www"))`
2163 /// as the base URL is `file:///var/www/index.html`
2164 /// * `"index.html"` parsed with `Url::from_file_path(Path::new("/var/www"))`
2165 /// as the base URL is `file:///var/index.html`, which might not be what was intended.
2166 ///
2167 /// Note that `std::path` does not consider trailing slashes significant
2168 /// and usually does not include them (e.g. in `Path::parent()`).
e74abb32 2169 #[cfg(any(unix, windows, target_os = "redox"))]
abe05a73
XL
2170 pub fn from_directory_path<P: AsRef<Path>>(path: P) -> Result<Url, ()> {
2171 let mut url = Url::from_file_path(path)?;
2172 if !url.serialization.ends_with('/') {
2173 url.serialization.push('/')
2174 }
2175 Ok(url)
2176 }
2177
2178 /// Serialize with Serde using the internal representation of the `Url` struct.
2179 ///
2180 /// The corresponding `deserialize_internal` method sacrifices some invariant-checking
2181 /// for speed, compared to the `Deserialize` trait impl.
2182 ///
2183 /// This method is only available if the `serde` Cargo feature is enabled.
2184 #[cfg(feature = "serde")]
2185 #[deny(unused)]
e74abb32
XL
2186 pub fn serialize_internal<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2187 where
2188 S: serde::Serializer,
2189 {
abe05a73
XL
2190 use serde::Serialize;
2191 // Destructuring first lets us ensure that adding or removing fields forces this method
2192 // to be updated
e74abb32
XL
2193 let Url {
2194 ref serialization,
2195 ref scheme_end,
2196 ref username_end,
2197 ref host_start,
2198 ref host_end,
2199 ref host,
2200 ref port,
2201 ref path_start,
2202 ref query_start,
2203 ref fragment_start,
2204 } = *self;
2205 (
2206 serialization,
2207 scheme_end,
2208 username_end,
2209 host_start,
2210 host_end,
2211 host,
2212 port,
2213 path_start,
2214 query_start,
2215 fragment_start,
2216 )
2217 .serialize(serializer)
abe05a73
XL
2218 }
2219
2220 /// Serialize with Serde using the internal representation of the `Url` struct.
2221 ///
2222 /// The corresponding `deserialize_internal` method sacrifices some invariant-checking
2223 /// for speed, compared to the `Deserialize` trait impl.
2224 ///
2225 /// This method is only available if the `serde` Cargo feature is enabled.
2226 #[cfg(feature = "serde")]
2227 #[deny(unused)]
e74abb32
XL
2228 pub fn deserialize_internal<'de, D>(deserializer: D) -> Result<Self, D::Error>
2229 where
2230 D: serde::Deserializer<'de>,
2231 {
2232 use serde::de::{Deserialize, Error, Unexpected};
2233 let (
2234 serialization,
2235 scheme_end,
2236 username_end,
2237 host_start,
2238 host_end,
2239 host,
2240 port,
2241 path_start,
2242 query_start,
2243 fragment_start,
2244 ) = Deserialize::deserialize(deserializer)?;
abe05a73 2245 let url = Url {
e74abb32
XL
2246 serialization,
2247 scheme_end,
2248 username_end,
2249 host_start,
2250 host_end,
2251 host,
2252 port,
2253 path_start,
2254 query_start,
2255 fragment_start,
abe05a73
XL
2256 };
2257 if cfg!(debug_assertions) {
e74abb32
XL
2258 url.check_invariants().map_err(|reason| {
2259 let reason: &str = &reason;
2260 Error::invalid_value(Unexpected::Other("value"), &reason)
2261 })?
abe05a73
XL
2262 }
2263 Ok(url)
2264 }
2265
abe05a73
XL
2266 /// Assuming the URL is in the `file` scheme or similar,
2267 /// convert its path to an absolute `std::path::Path`.
2268 ///
2269 /// **Note:** This does not actually check the URL’s `scheme`,
2270 /// and may give nonsensical results for other schemes.
2271 /// It is the user’s responsibility to check the URL’s scheme before calling this.
2272 ///
2273 /// ```
2274 /// # use url::Url;
2275 /// # let url = Url::parse("file:///etc/passwd").unwrap();
2276 /// let path = url.to_file_path();
2277 /// ```
2278 ///
2279 /// Returns `Err` if the host is neither empty nor `"localhost"` (except on Windows, where
2280 /// `file:` URLs may have a non-local host),
2281 /// or if `Path::new_opt()` returns `None`.
2282 /// (That is, if the percent-decoded path contains a NUL byte or,
2283 /// for a Windows path, is not UTF-8.)
2284 #[inline]
e74abb32 2285 #[cfg(any(unix, windows, target_os = "redox"))]
abe05a73
XL
2286 pub fn to_file_path(&self) -> Result<PathBuf, ()> {
2287 if let Some(segments) = self.path_segments() {
2288 let host = match self.host() {
2289 None | Some(Host::Domain("localhost")) => None,
2290 Some(_) if cfg!(windows) && self.scheme() == "file" => {
e74abb32
XL
2291 Some(&self.serialization[self.host_start as usize..self.host_end as usize])
2292 }
2293 _ => return Err(()),
abe05a73
XL
2294 };
2295
2296 return file_url_segments_to_pathbuf(host, segments);
2297 }
2298 Err(())
2299 }
2300
2301 // Private helper methods:
2302
2303 #[inline]
e74abb32
XL
2304 fn slice<R>(&self, range: R) -> &str
2305 where
2306 R: RangeArg,
2307 {
abe05a73
XL
2308 range.slice_of(&self.serialization)
2309 }
2310
2311 #[inline]
2312 fn byte_at(&self, i: u32) -> u8 {
2313 self.serialization.as_bytes()[i as usize]
2314 }
2315}
2316
abe05a73
XL
2317/// Parse a string as an URL, without a base URL or encoding override.
2318impl str::FromStr for Url {
2319 type Err = ParseError;
2320
2321 #[inline]
2322 fn from_str(input: &str) -> Result<Url, ::ParseError> {
2323 Url::parse(input)
2324 }
2325}
2326
2327/// Display the serialization of this URL.
2328impl fmt::Display for Url {
2329 #[inline]
2330 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
2331 fmt::Display::fmt(&self.serialization, formatter)
2332 }
2333}
2334
2335/// Debug the serialization of this URL.
2336impl fmt::Debug for Url {
2337 #[inline]
2338 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
2339 fmt::Debug::fmt(&self.serialization, formatter)
2340 }
2341}
2342
2343/// URLs compare like their serialization.
2344impl Eq for Url {}
2345
2346/// URLs compare like their serialization.
2347impl PartialEq for Url {
2348 #[inline]
2349 fn eq(&self, other: &Self) -> bool {
2350 self.serialization == other.serialization
2351 }
2352}
2353
2354/// URLs compare like their serialization.
2355impl Ord for Url {
2356 #[inline]
2357 fn cmp(&self, other: &Self) -> cmp::Ordering {
2358 self.serialization.cmp(&other.serialization)
2359 }
2360}
2361
2362/// URLs compare like their serialization.
2363impl PartialOrd for Url {
2364 #[inline]
2365 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
2366 self.serialization.partial_cmp(&other.serialization)
2367 }
2368}
2369
2370/// URLs hash like their serialization.
2371impl hash::Hash for Url {
2372 #[inline]
e74abb32
XL
2373 fn hash<H>(&self, state: &mut H)
2374 where
2375 H: hash::Hasher,
2376 {
abe05a73
XL
2377 hash::Hash::hash(&self.serialization, state)
2378 }
2379}
2380
2381/// Return the serialization of this URL.
2382impl AsRef<str> for Url {
2383 #[inline]
2384 fn as_ref(&self) -> &str {
2385 &self.serialization
2386 }
2387}
2388
2389trait RangeArg {
2390 fn slice_of<'a>(&self, s: &'a str) -> &'a str;
2391}
2392
2393impl RangeArg for Range<u32> {
2394 #[inline]
2395 fn slice_of<'a>(&self, s: &'a str) -> &'a str {
e74abb32 2396 &s[self.start as usize..self.end as usize]
abe05a73
XL
2397 }
2398}
2399
2400impl RangeArg for RangeFrom<u32> {
2401 #[inline]
2402 fn slice_of<'a>(&self, s: &'a str) -> &'a str {
e74abb32 2403 &s[self.start as usize..]
abe05a73
XL
2404 }
2405}
2406
2407impl RangeArg for RangeTo<u32> {
2408 #[inline]
2409 fn slice_of<'a>(&self, s: &'a str) -> &'a str {
e74abb32 2410 &s[..self.end as usize]
abe05a73
XL
2411 }
2412}
2413
2414/// Serializes this URL into a `serde` stream.
2415///
2416/// This implementation is only available if the `serde` Cargo feature is enabled.
e74abb32 2417#[cfg(feature = "serde")]
abe05a73 2418impl serde::Serialize for Url {
e74abb32
XL
2419 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2420 where
2421 S: serde::Serializer,
2422 {
abe05a73
XL
2423 serializer.serialize_str(self.as_str())
2424 }
2425}
2426
2427/// Deserializes this URL from a `serde` stream.
2428///
2429/// This implementation is only available if the `serde` Cargo feature is enabled.
e74abb32
XL
2430#[cfg(feature = "serde")]
2431impl<'de> serde::Deserialize<'de> for Url {
2432 fn deserialize<D>(deserializer: D) -> Result<Url, D::Error>
2433 where
2434 D: serde::Deserializer<'de>,
2435 {
2436 use serde::de::{Error, Unexpected, Visitor};
2437
2438 struct UrlVisitor;
2439
2440 impl<'de> Visitor<'de> for UrlVisitor {
2441 type Value = Url;
2442
2443 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
2444 formatter.write_str("a string representing an URL")
2445 }
2446
2447 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
2448 where
2449 E: Error,
2450 {
2451 Url::parse(s)
2452 .map_err(|err| Error::invalid_value(Unexpected::Str(s), &err.description()))
2453 }
2454 }
2455
2456 deserializer.deserialize_str(UrlVisitor)
abe05a73
XL
2457 }
2458}
2459
2460#[cfg(any(unix, target_os = "redox"))]
e74abb32
XL
2461fn path_to_file_url_segments(
2462 path: &Path,
2463 serialization: &mut String,
2464) -> Result<(u32, HostInternal), ()> {
abe05a73
XL
2465 use std::os::unix::prelude::OsStrExt;
2466 if !path.is_absolute() {
e74abb32 2467 return Err(());
abe05a73
XL
2468 }
2469 let host_end = to_u32(serialization.len()).unwrap();
2470 let mut empty = true;
2471 // skip the root component
2472 for component in path.components().skip(1) {
2473 empty = false;
2474 serialization.push('/');
2475 serialization.extend(percent_encode(
e74abb32
XL
2476 component.as_os_str().as_bytes(),
2477 PATH_SEGMENT,
2478 ));
abe05a73
XL
2479 }
2480 if empty {
2481 // An URL’s path must not be empty.
2482 serialization.push('/');
2483 }
2484 Ok((host_end, HostInternal::None))
2485}
2486
2487#[cfg(windows)]
e74abb32
XL
2488fn path_to_file_url_segments(
2489 path: &Path,
2490 serialization: &mut String,
2491) -> Result<(u32, HostInternal), ()> {
abe05a73
XL
2492 path_to_file_url_segments_windows(path, serialization)
2493}
2494
2495// Build this unconditionally to alleviate https://github.com/servo/rust-url/issues/102
2496#[cfg_attr(not(windows), allow(dead_code))]
e74abb32
XL
2497fn path_to_file_url_segments_windows(
2498 path: &Path,
2499 serialization: &mut String,
2500) -> Result<(u32, HostInternal), ()> {
2501 use std::path::{Component, Prefix};
abe05a73 2502 if !path.is_absolute() {
e74abb32 2503 return Err(());
abe05a73
XL
2504 }
2505 let mut components = path.components();
2506
f035d41b 2507 let host_start = serialization.len() + 1;
abe05a73
XL
2508 let host_end;
2509 let host_internal;
2510 match components.next() {
2511 Some(Component::Prefix(ref p)) => match p.kind() {
2512 Prefix::Disk(letter) | Prefix::VerbatimDisk(letter) => {
2513 host_end = to_u32(serialization.len()).unwrap();
2514 host_internal = HostInternal::None;
2515 serialization.push('/');
2516 serialization.push(letter as char);
2517 serialization.push(':');
e74abb32 2518 }
abe05a73
XL
2519 Prefix::UNC(server, share) | Prefix::VerbatimUNC(server, share) => {
2520 let host = Host::parse(server.to_str().ok_or(())?).map_err(|_| ())?;
2521 write!(serialization, "{}", host).unwrap();
2522 host_end = to_u32(serialization.len()).unwrap();
2523 host_internal = host.into();
2524 serialization.push('/');
2525 let share = share.to_str().ok_or(())?;
e74abb32
XL
2526 serialization.extend(percent_encode(share.as_bytes(), PATH_SEGMENT));
2527 }
2528 _ => return Err(()),
abe05a73
XL
2529 },
2530
e74abb32 2531 _ => return Err(()),
abe05a73
XL
2532 }
2533
f035d41b 2534 let mut path_only_has_prefix = true;
abe05a73 2535 for component in components {
e74abb32
XL
2536 if component == Component::RootDir {
2537 continue;
2538 }
f035d41b 2539 path_only_has_prefix = false;
abe05a73
XL
2540 // FIXME: somehow work with non-unicode?
2541 let component = component.as_os_str().to_str().ok_or(())?;
2542 serialization.push('/');
e74abb32 2543 serialization.extend(percent_encode(component.as_bytes(), PATH_SEGMENT));
abe05a73 2544 }
f035d41b
XL
2545 // A windows drive letter must end with a slash.
2546 if serialization.len() > host_start
2547 && parser::is_windows_drive_letter(&serialization[host_start..])
2548 && path_only_has_prefix
2549 {
2550 serialization.push('/');
2551 }
abe05a73
XL
2552 Ok((host_end, host_internal))
2553}
2554
2555#[cfg(any(unix, target_os = "redox"))]
e74abb32
XL
2556fn file_url_segments_to_pathbuf(
2557 host: Option<&str>,
2558 segments: str::Split<char>,
2559) -> Result<PathBuf, ()> {
abe05a73
XL
2560 use std::ffi::OsStr;
2561 use std::os::unix::prelude::OsStrExt;
abe05a73
XL
2562
2563 if host.is_some() {
2564 return Err(());
2565 }
2566
e74abb32
XL
2567 let mut bytes = if cfg!(target_os = "redox") {
2568 b"file:".to_vec()
2569 } else {
2570 Vec::new()
2571 };
abe05a73
XL
2572 for segment in segments {
2573 bytes.push(b'/');
2574 bytes.extend(percent_decode(segment.as_bytes()));
2575 }
f035d41b
XL
2576 // A windows drive letter must end with a slash.
2577 if bytes.len() > 2 {
2578 if matches!(bytes[bytes.len() - 2], b'a'..=b'z' | b'A'..=b'Z')
2579 && matches!(bytes[bytes.len() - 1], b':' | b'|')
2580 {
2581 bytes.push(b'/');
2582 }
2583 }
abe05a73
XL
2584 let os_str = OsStr::from_bytes(&bytes);
2585 let path = PathBuf::from(os_str);
e74abb32
XL
2586 debug_assert!(
2587 path.is_absolute(),
2588 "to_file_path() failed to produce an absolute Path"
2589 );
abe05a73
XL
2590 Ok(path)
2591}
2592
2593#[cfg(windows)]
e74abb32
XL
2594fn file_url_segments_to_pathbuf(
2595 host: Option<&str>,
2596 segments: str::Split<char>,
2597) -> Result<PathBuf, ()> {
abe05a73
XL
2598 file_url_segments_to_pathbuf_windows(host, segments)
2599}
2600
2601// Build this unconditionally to alleviate https://github.com/servo/rust-url/issues/102
2602#[cfg_attr(not(windows), allow(dead_code))]
e74abb32
XL
2603fn file_url_segments_to_pathbuf_windows(
2604 host: Option<&str>,
2605 mut segments: str::Split<char>,
2606) -> Result<PathBuf, ()> {
abe05a73
XL
2607 let mut string = if let Some(host) = host {
2608 r"\\".to_owned() + host
2609 } else {
2610 let first = segments.next().ok_or(())?;
2611
2612 match first.len() {
2613 2 => {
2614 if !first.starts_with(parser::ascii_alpha) || first.as_bytes()[1] != b':' {
e74abb32 2615 return Err(());
abe05a73
XL
2616 }
2617
2618 first.to_owned()
e74abb32 2619 }
abe05a73
XL
2620
2621 4 => {
2622 if !first.starts_with(parser::ascii_alpha) {
e74abb32 2623 return Err(());
abe05a73
XL
2624 }
2625 let bytes = first.as_bytes();
2626 if bytes[1] != b'%' || bytes[2] != b'3' || (bytes[3] != b'a' && bytes[3] != b'A') {
e74abb32 2627 return Err(());
abe05a73
XL
2628 }
2629
2630 first[0..1].to_owned() + ":"
e74abb32 2631 }
abe05a73
XL
2632
2633 _ => return Err(()),
2634 }
2635 };
2636
2637 for segment in segments {
2638 string.push('\\');
2639
2640 // Currently non-unicode windows paths cannot be represented
2641 match String::from_utf8(percent_decode(segment.as_bytes()).collect()) {
2642 Ok(s) => string.push_str(&s),
2643 Err(..) => return Err(()),
2644 }
2645 }
2646 let path = PathBuf::from(string);
e74abb32
XL
2647 debug_assert!(
2648 path.is_absolute(),
2649 "to_file_path() failed to produce an absolute Path"
2650 );
abe05a73
XL
2651 Ok(path)
2652}
2653
abe05a73
XL
2654/// Implementation detail of `Url::query_pairs_mut`. Typically not used directly.
2655#[derive(Debug)]
2656pub struct UrlQuery<'a> {
8faf50e0 2657 url: Option<&'a mut Url>,
abe05a73
XL
2658 fragment: Option<String>,
2659}
2660
2661impl<'a> Drop for UrlQuery<'a> {
2662 fn drop(&mut self) {
8faf50e0
XL
2663 if let Some(url) = self.url.take() {
2664 url.restore_already_parsed_fragment(self.fragment.take())
2665 }
abe05a73
XL
2666 }
2667}