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