Coverage Report

Created: 2025-11-16 06:42

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