diff -Nru rust-url-2.1.1/appveyor.yml rust-url-2.2.2/appveyor.yml --- rust-url-2.1.1/appveyor.yml 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/appveyor.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ -install: - - ps: Start-FileDownload 'https://static.rust-lang.org/dist/rust-nightly-i686-pc-windows-gnu.exe' - - rust-nightly-i686-pc-windows-gnu.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" - - SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin - - rustc -V - - cargo -V - - git submodule update --init --recursive - -build: false - -test_script: - - cargo build - - cargo test --verbose diff -Nru rust-url-2.1.1/benches/parse_url.rs rust-url-2.2.2/benches/parse_url.rs --- rust-url-2.1.1/benches/parse_url.rs 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/benches/parse_url.rs 1970-01-01 00:00:00.000000000 +0000 @@ -1,18 +0,0 @@ -#[macro_use] -extern crate bencher; - -extern crate url; - -use bencher::{black_box, Bencher}; - -use url::Url; - -fn short(bench: &mut Bencher) { - let url = "https://example.com/bench"; - - bench.bytes = url.len() as u64; - bench.iter(|| black_box(url).parse::().unwrap()); -} - -benchmark_group!(benches, short); -benchmark_main!(benches); diff -Nru rust-url-2.1.1/Cargo.toml rust-url-2.2.2/Cargo.toml --- rust-url-2.1.1/Cargo.toml 1970-01-01 00:00:00.000000000 +0000 +++ rust-url-2.2.2/Cargo.toml 1970-01-01 00:00:00.000000000 +0000 @@ -11,30 +11,26 @@ # will likely look very different (and much more reasonable) [package] +edition = "2018" name = "url" -version = "2.1.1" +version = "2.2.2" authors = ["The rust-url developers"] +include = ["src/**/*", "LICENSE-*", "README.md", "tests/**"] description = "URL library for Rust, based on the WHATWG URL Standard" documentation = "https://docs.rs/url" -readme = "README.md" +readme = "../README.md" keywords = ["url", "parser"] categories = ["parser-implementations", "web-programming", "encoding"] license = "MIT/Apache-2.0" repository = "https://github.com/servo/rust-url" -[lib] -test = false - -[[test]] -name = "unit" - -[[test]] -name = "data" -harness = false - [[bench]] name = "parse_url" +path = "benches/parse_url.rs" harness = false +[dependencies.form_urlencoded] +version = "1.0.0" + [dependencies.idna] version = "0.2.0" @@ -51,9 +47,6 @@ [dev-dependencies.bencher] version = "0.1" -[dev-dependencies.rustc-test] -version = "0.3" - [dev-dependencies.serde_json] version = "1.0" [badges.appveyor] diff -Nru rust-url-2.1.1/Cargo.toml.orig rust-url-2.2.2/Cargo.toml.orig --- rust-url-2.1.1/Cargo.toml.orig 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/Cargo.toml.orig 1970-01-01 00:00:00.000000000 +0000 @@ -2,46 +2,35 @@ name = "url" # When updating version, also modify html_root_url in the lib.rs -version = "2.1.1" +version = "2.2.2" authors = ["The rust-url developers"] description = "URL library for Rust, based on the WHATWG URL Standard" documentation = "https://docs.rs/url" repository = "https://github.com/servo/rust-url" -readme = "README.md" +readme = "../README.md" keywords = ["url", "parser"] categories = ["parser-implementations", "web-programming", "encoding"] license = "MIT/Apache-2.0" +include = ["src/**/*", "LICENSE-*", "README.md", "tests/**"] +edition = "2018" [badges] travis-ci = { repository = "servo/rust-url" } appveyor = { repository = "Manishearth/rust-url" } -[workspace] -members = [".", "idna", "percent_encoding", "data-url"] - -[[test]] -name = "unit" - -[[test]] -name = "data" -harness = false - -[lib] -test = false - [dev-dependencies] -rustc-test = "0.3" serde_json = "1.0" - bencher = "0.1" [dependencies] -idna = { version = "0.2.0", path = "./idna" } +form_urlencoded = { version = "1.0.0", path = "../form_urlencoded" } +idna = { version = "0.2.0", path = "../idna" } matches = "0.1" -percent-encoding = { version = "2.1.0", path = "./percent_encoding" } +percent-encoding = { version = "2.1.0", path = "../percent_encoding" } serde = {version = "1.0", optional = true, features = ["derive"]} [[bench]] name = "parse_url" +path = "benches/parse_url.rs" harness = false diff -Nru rust-url-2.1.1/.cargo_vcs_info.json rust-url-2.2.2/.cargo_vcs_info.json --- rust-url-2.1.1/.cargo_vcs_info.json 1970-01-01 00:00:00.000000000 +0000 +++ rust-url-2.2.2/.cargo_vcs_info.json 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +1,5 @@ { "git": { - "sha1": "f091d2b45b79958f9a40e2b98b844f75df6723de" + "sha1": "6c22912c313064a8c5a6fa043882c9ad55dba162" } } diff -Nru rust-url-2.1.1/debian/cargo-checksum.json rust-url-2.2.2/debian/cargo-checksum.json --- rust-url-2.1.1/debian/cargo-checksum.json 2020-04-20 12:24:24.000000000 +0000 +++ rust-url-2.2.2/debian/cargo-checksum.json 2021-10-14 21:02:43.000000000 +0000 @@ -1 +1 @@ -{"package":"829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb","files":{}} +{"package":"Could not get crate checksum","files":{}} diff -Nru rust-url-2.1.1/debian/changelog rust-url-2.2.2/debian/changelog --- rust-url-2.1.1/debian/changelog 2020-04-20 12:24:24.000000000 +0000 +++ rust-url-2.2.2/debian/changelog 2021-10-14 21:02:43.000000000 +0000 @@ -1,3 +1,9 @@ +rust-url (2.2.2-1) unstable; urgency=medium + + * Package url 2.2.2 from crates.io using debcargo 2.4.4-alpha.0 + + -- Wolfgang Silbermayr Thu, 14 Oct 2021 23:02:43 +0200 + rust-url (2.1.1-2) unstable; urgency=medium * Team upload. diff -Nru rust-url-2.1.1/debian/compat rust-url-2.2.2/debian/compat --- rust-url-2.1.1/debian/compat 2020-04-20 12:24:24.000000000 +0000 +++ rust-url-2.2.2/debian/compat 2021-10-14 21:02:43.000000000 +0000 @@ -1 +1 @@ -11 +12 diff -Nru rust-url-2.1.1/debian/control rust-url-2.2.2/debian/control --- rust-url-2.1.1/debian/control 2020-04-20 12:24:24.000000000 +0000 +++ rust-url-2.2.2/debian/control 2021-10-14 21:02:43.000000000 +0000 @@ -1,27 +1,30 @@ Source: rust-url Section: rust Priority: optional -Build-Depends: debhelper (>= 11), - dh-cargo (>= 18), +Build-Depends: debhelper (>= 12), + dh-cargo (>= 24), cargo:native , rustc:native , libstd-rust-dev , + librust-form-urlencoded-1+default-dev , librust-idna-0.2+default-dev , librust-matches-0.1+default-dev , librust-percent-encoding-2+default-dev (>= 2.1.0-~~) Maintainer: Debian Rust Maintainers Uploaders: - Paride Legovini , + Paride Legovini , Wolfgang Silbermayr -Standards-Version: 4.4.1 +Standards-Version: 4.5.1 Vcs-Git: https://salsa.debian.org/rust-team/debcargo-conf.git [src/url] Vcs-Browser: https://salsa.debian.org/rust-team/debcargo-conf/tree/master/src/url +Rules-Requires-Root: no Package: librust-url-dev Architecture: any Multi-Arch: same Depends: ${misc:Depends}, + librust-form-urlencoded-1+default-dev, librust-idna-0.2+default-dev, librust-matches-0.1+default-dev, librust-percent-encoding-2+default-dev (>= 2.1.0-~~) @@ -31,11 +34,11 @@ librust-url+default-dev (= ${binary:Version}), librust-url-2-dev (= ${binary:Version}), librust-url-2+default-dev (= ${binary:Version}), - librust-url-2.1-dev (= ${binary:Version}), - librust-url-2.1+default-dev (= ${binary:Version}), - librust-url-2.1.1-dev (= ${binary:Version}), - librust-url-2.1.1+default-dev (= ${binary:Version}) -Description: URL library for Rust - Rust source code + librust-url-2.2-dev (= ${binary:Version}), + librust-url-2.2+default-dev (= ${binary:Version}), + librust-url-2.2.2-dev (= ${binary:Version}), + librust-url-2.2.2+default-dev (= ${binary:Version}) +Description: URL library for Rust, based on the WHATWG URL Standard - Rust source code This package contains the source for the Rust url crate, packaged by debcargo for use with cargo and dh-cargo. @@ -49,8 +52,8 @@ librust-serde-1+derive-dev Provides: librust-url-2+serde-dev (= ${binary:Version}), - librust-url-2.1+serde-dev (= ${binary:Version}), - librust-url-2.1.1+serde-dev (= ${binary:Version}) -Description: URL library for Rust - feature "serde" + librust-url-2.2+serde-dev (= ${binary:Version}), + librust-url-2.2.2+serde-dev (= ${binary:Version}) +Description: URL library for Rust, based on the WHATWG URL Standard - feature "serde" This metapackage enables feature "serde" for the Rust url crate, by pulling in any additional dependencies needed by that feature. diff -Nru rust-url-2.1.1/debian/copyright rust-url-2.2.2/debian/copyright --- rust-url-2.1.1/debian/copyright 2020-04-20 12:24:24.000000000 +0000 +++ rust-url-2.2.2/debian/copyright 2021-10-14 21:02:43.000000000 +0000 @@ -10,9 +10,9 @@ Files: debian/* Copyright: - 2018-2020 Debian Rust Maintainers - 2018-2020 Paride Legovini - 2018-2020 Wolfgang Silbermayr + 2018-2021 Debian Rust Maintainers + 2018-2021 Paride Legovini + 2018-2021 Wolfgang Silbermayr License: MIT or Apache-2.0 License: Apache-2.0 diff -Nru rust-url-2.1.1/debian/copyright.debcargo.hint rust-url-2.2.2/debian/copyright.debcargo.hint --- rust-url-2.1.1/debian/copyright.debcargo.hint 2020-04-20 12:24:24.000000000 +0000 +++ rust-url-2.2.2/debian/copyright.debcargo.hint 2021-10-14 21:02:43.000000000 +0000 @@ -19,13 +19,6 @@ FIXME (overlay): These notices are extracted from files. Please review them before uploading to the archive. -Files: ./src/form_urlencoded.rs -Copyright: 2013-2016 The rust-url developers. -License: UNKNOWN-LICENSE; FIXME (overlay) -Comment: - FIXME (overlay): These notices are extracted from files. Please review them - before uploading to the archive. - Files: ./src/host.rs Copyright: 2013-2016 The rust-url developers. License: UNKNOWN-LICENSE; FIXME (overlay) @@ -61,13 +54,6 @@ FIXME (overlay): These notices are extracted from files. Please review them before uploading to the archive. -Files: ./src/query_encoding.rs -Copyright: 2019 The rust-url developers. -License: UNKNOWN-LICENSE; FIXME (overlay) -Comment: - FIXME (overlay): These notices are extracted from files. Please review them - before uploading to the archive. - Files: ./src/quirks.rs Copyright: 2016 The rust-url developers. License: UNKNOWN-LICENSE; FIXME (overlay) @@ -98,9 +84,9 @@ Files: debian/* Copyright: - 2018-2020 Debian Rust Maintainers - 2018-2020 Paride Legovini - 2018-2020 Wolfgang Silbermayr + 2018-2021 Debian Rust Maintainers + 2018-2021 Paride Legovini + 2018-2021 Wolfgang Silbermayr License: MIT or Apache-2.0 License: Apache-2.0 diff -Nru rust-url-2.1.1/debian/debcargo.toml rust-url-2.2.2/debian/debcargo.toml --- rust-url-2.1.1/debian/debcargo.toml 2020-04-20 12:24:24.000000000 +0000 +++ rust-url-2.2.2/debian/debcargo.toml 2021-10-14 21:02:43.000000000 +0000 @@ -1,5 +1,5 @@ overlay = "." -uploaders = ["Paride Legovini ", +uploaders = ["Paride Legovini ", "Wolfgang Silbermayr "] # The auto-generated summary is too long. diff -Nru rust-url-2.1.1/debian/patches/remove-benchmarks.diff rust-url-2.2.2/debian/patches/remove-benchmarks.diff --- rust-url-2.1.1/debian/patches/remove-benchmarks.diff 1970-01-01 00:00:00.000000000 +0000 +++ rust-url-2.2.2/debian/patches/remove-benchmarks.diff 2021-10-14 21:02:43.000000000 +0000 @@ -0,0 +1,22 @@ +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -24,10 +24,6 @@ + license = "MIT/Apache-2.0" + repository = "https://github.com/servo/rust-url" + +-[[bench]] +-name = "parse_url" +-path = "benches/parse_url.rs" +-harness = false + [dependencies.form_urlencoded] + version = "1.0.0" + +@@ -44,8 +40,6 @@ + version = "1.0" + features = ["derive"] + optional = true +-[dev-dependencies.bencher] +-version = "0.1" + + [dev-dependencies.serde_json] + version = "1.0" diff -Nru rust-url-2.1.1/debian/patches/series rust-url-2.2.2/debian/patches/series --- rust-url-2.1.1/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 +++ rust-url-2.2.2/debian/patches/series 2021-10-14 21:02:43.000000000 +0000 @@ -0,0 +1 @@ +remove-benchmarks.diff diff -Nru rust-url-2.1.1/debian/tests/control rust-url-2.2.2/debian/tests/control --- rust-url-2.1.1/debian/tests/control 2020-04-20 12:24:24.000000000 +0000 +++ rust-url-2.2.2/debian/tests/control 2021-10-14 21:02:43.000000000 +0000 @@ -1,14 +1,19 @@ -Test-Command: /usr/share/cargo/bin/cargo-auto-test url 2.1.1 --all-targets --all-features -Features: test-name=@ -Depends: dh-cargo (>= 18), librust-bencher-0.1+default-dev, librust-rustc-test-0.3+default-dev, librust-serde-json-1+default-dev, @ +Test-Command: /usr/share/cargo/bin/cargo-auto-test url 2.2.2 --all-targets --all-features +Features: test-name=rust-url:@ +Depends: dh-cargo (>= 18), librust-serde-json-1+default-dev, @ Restrictions: allow-stderr, skip-not-installable -Test-Command: /usr/share/cargo/bin/cargo-auto-test url 2.1.1 --all-targets --no-default-features -Features: test-name=librust-url-dev -Depends: dh-cargo (>= 18), librust-bencher-0.1+default-dev, librust-rustc-test-0.3+default-dev, librust-serde-json-1+default-dev, @ +Test-Command: /usr/share/cargo/bin/cargo-auto-test url 2.2.2 --all-targets +Features: test-name=librust-url-dev:default +Depends: dh-cargo (>= 18), librust-serde-json-1+default-dev, @ Restrictions: allow-stderr, skip-not-installable -Test-Command: /usr/share/cargo/bin/cargo-auto-test url 2.1.1 --all-targets --features serde -Features: test-name=librust-url+serde-dev -Depends: dh-cargo (>= 18), librust-bencher-0.1+default-dev, librust-rustc-test-0.3+default-dev, librust-serde-json-1+default-dev, @ +Test-Command: /usr/share/cargo/bin/cargo-auto-test url 2.2.2 --all-targets --no-default-features +Features: test-name=librust-url-dev: +Depends: dh-cargo (>= 18), librust-serde-json-1+default-dev, @ +Restrictions: allow-stderr, skip-not-installable + +Test-Command: /usr/share/cargo/bin/cargo-auto-test url 2.2.2 --all-targets --no-default-features --features serde +Features: test-name=librust-url+serde-dev:serde +Depends: dh-cargo (>= 18), librust-serde-json-1+default-dev, @ Restrictions: allow-stderr, skip-not-installable diff -Nru rust-url-2.1.1/.gitignore rust-url-2.2.2/.gitignore --- rust-url-2.1.1/.gitignore 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/.gitignore 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -target -Cargo.lock -/.cargo/config diff -Nru rust-url-2.1.1/README.md rust-url-2.2.2/README.md --- rust-url-2.1.1/README.md 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/README.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -rust-url -======== - -[![Travis build Status](https://travis-ci.com/servo/rust-url.svg?branch=master)](https://travis-ci.com/servo/rust-url) [![Appveyor build status](https://ci.appveyor.com/api/projects/status/ulkqx2xcemyod6xa?svg=true)](https://ci.appveyor.com/project/Manishearth/rust-url) - -URL library for Rust, based on the [URL Standard](https://url.spec.whatwg.org/). - -[Documentation](https://docs.rs/url/) - -Please see [UPGRADING.md](https://github.com/servo/rust-url/blob/master/UPGRADING.md) if you are upgrading from 0.x to 1.x. diff -Nru rust-url-2.1.1/src/form_urlencoded.rs rust-url-2.2.2/src/form_urlencoded.rs --- rust-url-2.1.1/src/form_urlencoded.rs 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/src/form_urlencoded.rs 1970-01-01 00:00:00.000000000 +0000 @@ -1,347 +0,0 @@ -// Copyright 2013-2016 The rust-url developers. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Parser and serializer for the [`application/x-www-form-urlencoded` syntax]( -//! http://url.spec.whatwg.org/#application/x-www-form-urlencoded), -//! as used by HTML forms. -//! -//! Converts between a string (such as an URL’s query string) -//! and a sequence of (name, value) pairs. - -use percent_encoding::{percent_decode, percent_encode_byte}; -use query_encoding::{self, decode_utf8_lossy, EncodingOverride}; -use std::borrow::{Borrow, Cow}; -use std::str; - -/// Convert a byte string in the `application/x-www-form-urlencoded` syntax -/// into a iterator of (name, value) pairs. -/// -/// Use `parse(input.as_bytes())` to parse a `&str` string. -/// -/// The names and values are percent-decoded. For instance, `%23first=%25try%25` will be -/// converted to `[("#first", "%try%")]`. -#[inline] -pub fn parse(input: &[u8]) -> Parse { - Parse { input } -} -/// The return type of `parse()`. -#[derive(Copy, Clone)] -pub struct Parse<'a> { - input: &'a [u8], -} - -impl<'a> Iterator for Parse<'a> { - type Item = (Cow<'a, str>, Cow<'a, str>); - - fn next(&mut self) -> Option { - loop { - if self.input.is_empty() { - return None; - } - let mut split2 = self.input.splitn(2, |&b| b == b'&'); - let sequence = split2.next().unwrap(); - self.input = split2.next().unwrap_or(&[][..]); - if sequence.is_empty() { - continue; - } - let mut split2 = sequence.splitn(2, |&b| b == b'='); - let name = split2.next().unwrap(); - let value = split2.next().unwrap_or(&[][..]); - return Some((decode(name), decode(value))); - } - } -} - -fn decode(input: &[u8]) -> Cow { - let replaced = replace_plus(input); - decode_utf8_lossy(match percent_decode(&replaced).into() { - Cow::Owned(vec) => Cow::Owned(vec), - Cow::Borrowed(_) => replaced, - }) -} - -/// Replace b'+' with b' ' -fn replace_plus(input: &[u8]) -> Cow<[u8]> { - match input.iter().position(|&b| b == b'+') { - None => Cow::Borrowed(input), - Some(first_position) => { - let mut replaced = input.to_owned(); - replaced[first_position] = b' '; - for byte in &mut replaced[first_position + 1..] { - if *byte == b'+' { - *byte = b' '; - } - } - Cow::Owned(replaced) - } - } -} - -impl<'a> Parse<'a> { - /// Return a new iterator that yields pairs of `String` instead of pairs of `Cow`. - pub fn into_owned(self) -> ParseIntoOwned<'a> { - ParseIntoOwned { inner: self } - } -} - -/// Like `Parse`, but yields pairs of `String` instead of pairs of `Cow`. -pub struct ParseIntoOwned<'a> { - inner: Parse<'a>, -} - -impl<'a> Iterator for ParseIntoOwned<'a> { - type Item = (String, String); - - fn next(&mut self) -> Option { - self.inner - .next() - .map(|(k, v)| (k.into_owned(), v.into_owned())) - } -} - -/// The [`application/x-www-form-urlencoded` byte serializer]( -/// https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer). -/// -/// Return an iterator of `&str` slices. -pub fn byte_serialize(input: &[u8]) -> ByteSerialize { - ByteSerialize { bytes: input } -} - -/// Return value of `byte_serialize()`. -#[derive(Debug)] -pub struct ByteSerialize<'a> { - bytes: &'a [u8], -} - -fn byte_serialized_unchanged(byte: u8) -> bool { - matches!(byte, b'*' | b'-' | b'.' | b'0' ..= b'9' | b'A' ..= b'Z' | b'_' | b'a' ..= b'z') -} - -impl<'a> Iterator for ByteSerialize<'a> { - type Item = &'a str; - - fn next(&mut self) -> Option<&'a str> { - if let Some((&first, tail)) = self.bytes.split_first() { - if !byte_serialized_unchanged(first) { - self.bytes = tail; - return Some(if first == b' ' { - "+" - } else { - percent_encode_byte(first) - }); - } - let position = tail.iter().position(|&b| !byte_serialized_unchanged(b)); - let (unchanged_slice, remaining) = match position { - // 1 for first_byte + i unchanged in tail - Some(i) => self.bytes.split_at(1 + i), - None => (self.bytes, &[][..]), - }; - self.bytes = remaining; - Some(unsafe { str::from_utf8_unchecked(unchanged_slice) }) - } else { - None - } - } - - fn size_hint(&self) -> (usize, Option) { - if self.bytes.is_empty() { - (0, Some(0)) - } else { - (1, Some(self.bytes.len())) - } - } -} - -/// The [`application/x-www-form-urlencoded` serializer]( -/// https://url.spec.whatwg.org/#concept-urlencoded-serializer). -pub struct Serializer<'a, T: Target> { - target: Option, - start_position: usize, - encoding: EncodingOverride<'a>, -} - -pub trait Target { - fn as_mut_string(&mut self) -> &mut String; - fn finish(self) -> Self::Finished; - type Finished; -} - -impl Target for String { - fn as_mut_string(&mut self) -> &mut String { - self - } - fn finish(self) -> Self { - self - } - type Finished = Self; -} - -impl<'a> Target for &'a mut String { - fn as_mut_string(&mut self) -> &mut String { - &mut **self - } - fn finish(self) -> Self { - self - } - type Finished = Self; -} - -// `as_mut_string` string here exposes the internal serialization of an `Url`, -// which should not be exposed to users. -// We achieve that by not giving users direct access to `UrlQuery`: -// * Its fields are private -// (and so can not be constructed with struct literal syntax outside of this crate), -// * It has no constructor -// * It is only visible (on the type level) to users in the return type of -// `Url::query_pairs_mut` which is `Serializer` -// * `Serializer` keeps its target in a private field -// * Unlike in other `Target` impls, `UrlQuery::finished` does not return `Self`. -impl<'a> Target for ::UrlQuery<'a> { - fn as_mut_string(&mut self) -> &mut String { - &mut self.url.as_mut().unwrap().serialization - } - - fn finish(mut self) -> &'a mut ::Url { - let url = self.url.take().unwrap(); - url.restore_already_parsed_fragment(self.fragment.take()); - url - } - - type Finished = &'a mut ::Url; -} - -impl<'a, T: Target> Serializer<'a, T> { - /// Create a new `application/x-www-form-urlencoded` serializer for the given target. - /// - /// If the target is non-empty, - /// its content is assumed to already be in `application/x-www-form-urlencoded` syntax. - pub fn new(target: T) -> Self { - Self::for_suffix(target, 0) - } - - /// Create a new `application/x-www-form-urlencoded` serializer - /// for a suffix of the given target. - /// - /// If that suffix is non-empty, - /// its content is assumed to already be in `application/x-www-form-urlencoded` syntax. - pub fn for_suffix(mut target: T, start_position: usize) -> Self { - &target.as_mut_string()[start_position..]; // Panic if out of bounds - Serializer { - target: Some(target), - start_position, - encoding: None, - } - } - - /// Remove any existing name/value pair. - /// - /// Panics if called after `.finish()`. - pub fn clear(&mut self) -> &mut Self { - string(&mut self.target).truncate(self.start_position); - self - } - - /// Set the character encoding to be used for names and values before percent-encoding. - pub fn encoding_override(&mut self, new: EncodingOverride<'a>) -> &mut Self { - self.encoding = new; - self - } - - /// Serialize and append a name/value pair. - /// - /// Panics if called after `.finish()`. - pub fn append_pair(&mut self, name: &str, value: &str) -> &mut Self { - append_pair( - string(&mut self.target), - self.start_position, - self.encoding, - name, - value, - ); - self - } - - /// Serialize and append a number of name/value pairs. - /// - /// This simply calls `append_pair` repeatedly. - /// This can be more convenient, so the user doesn’t need to introduce a block - /// to limit the scope of `Serializer`’s borrow of its string. - /// - /// Panics if called after `.finish()`. - pub fn extend_pairs(&mut self, iter: I) -> &mut Self - where - I: IntoIterator, - I::Item: Borrow<(K, V)>, - K: AsRef, - V: AsRef, - { - { - let string = string(&mut self.target); - for pair in iter { - let &(ref k, ref v) = pair.borrow(); - append_pair( - string, - self.start_position, - self.encoding, - k.as_ref(), - v.as_ref(), - ); - } - } - self - } - - /// If this serializer was constructed with a string, take and return that string. - /// - /// ```rust - /// use url::form_urlencoded; - /// let encoded: String = form_urlencoded::Serializer::new(String::new()) - /// .append_pair("foo", "bar & baz") - /// .append_pair("saison", "Été+hiver") - /// .finish(); - /// assert_eq!(encoded, "foo=bar+%26+baz&saison=%C3%89t%C3%A9%2Bhiver"); - /// ``` - /// - /// Panics if called more than once. - pub fn finish(&mut self) -> T::Finished { - self.target - .take() - .expect("url::form_urlencoded::Serializer double finish") - .finish() - } -} - -fn append_separator_if_needed(string: &mut String, start_position: usize) { - if string.len() > start_position { - string.push('&') - } -} - -fn string(target: &mut Option) -> &mut String { - target - .as_mut() - .expect("url::form_urlencoded::Serializer finished") - .as_mut_string() -} - -fn append_pair( - string: &mut String, - start_position: usize, - encoding: EncodingOverride, - name: &str, - value: &str, -) { - append_separator_if_needed(string, start_position); - append_encoded(name, string, encoding); - string.push('='); - append_encoded(value, string, encoding); -} - -fn append_encoded(s: &str, string: &mut String, encoding: EncodingOverride) { - string.extend(byte_serialize(&query_encoding::encode(encoding, s.into()))) -} diff -Nru rust-url-2.1.1/src/host.rs rust-url-2.2.2/src/host.rs --- rust-url-2.1.1/src/host.rs 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/src/host.rs 1970-01-01 00:00:00.000000000 +0000 @@ -6,15 +6,16 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use idna; -use parser::{ParseError, ParseResult}; -use percent_encoding::{percent_decode, utf8_percent_encode, CONTROLS}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; use std::cmp; use std::fmt::{self, Formatter}; use std::net::{Ipv4Addr, Ipv6Addr}; +use percent_encoding::{percent_decode, utf8_percent_encode, CONTROLS}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::parser::{ParseError, ParseResult}; + #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub(crate) enum HostInternal { @@ -37,7 +38,7 @@ /// The host name of an URL. #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Clone, Debug, Eq, Ord, PartialOrd, Hash)] pub enum Host { /// A DNS domain name, as '.' dot-separated labels. /// Non-ASCII labels are encoded in punycode per IDNA if this is the host of @@ -82,33 +83,38 @@ } let domain = percent_decode(input.as_bytes()).decode_utf8_lossy(); let domain = idna::domain_to_ascii(&domain)?; - if domain - .find(|c| { - matches!( - c, - '\0' | '\t' - | '\n' - | '\r' - | ' ' - | '#' - | '%' - | '/' - | ':' - | '?' - | '@' - | '[' - | '\\' - | ']' - ) - }) - .is_some() - { - return Err(ParseError::InvalidDomainCharacter); + if domain.is_empty() { + return Err(ParseError::EmptyHost); } - if let Some(address) = parse_ipv4addr(&domain)? { + + let is_invalid_domain_char = |c| { + matches!( + c, + '\0' | '\t' + | '\n' + | '\r' + | ' ' + | '#' + | '%' + | '/' + | ':' + | '<' + | '>' + | '?' + | '@' + | '[' + | '\\' + | ']' + | '^' + ) + }; + + if domain.find(is_invalid_domain_char).is_some() { + Err(ParseError::InvalidDomainCharacter) + } else if let Some(address) = parse_ipv4addr(&domain)? { Ok(Host::Ipv4(address)) } else { - Ok(Host::Domain(domain.into())) + Ok(Host::Domain(domain)) } } @@ -120,35 +126,40 @@ } return parse_ipv6addr(&input[1..input.len() - 1]).map(Host::Ipv6); } - if input - .find(|c| { - matches!( - c, - '\0' | '\t' - | '\n' - | '\r' - | ' ' - | '#' - | '/' - | ':' - | '?' - | '@' - | '[' - | '\\' - | ']' - ) - }) - .is_some() - { - return Err(ParseError::InvalidDomainCharacter); + + let is_invalid_host_char = |c| { + matches!( + c, + '\0' | '\t' + | '\n' + | '\r' + | ' ' + | '#' + | '/' + | ':' + | '<' + | '>' + | '?' + | '@' + | '[' + | '\\' + | ']' + | '^' + ) + }; + + if input.find(is_invalid_host_char).is_some() { + Err(ParseError::InvalidDomainCharacter) + } else { + Ok(Host::Domain( + utf8_percent_encode(input, CONTROLS).to_string(), + )) } - let s = utf8_percent_encode(input, CONTROLS).to_string(); - Ok(Host::Domain(s)) } } impl> fmt::Display for Host { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match *self { Host::Domain(ref domain) => domain.as_ref().fmt(f), Host::Ipv4(ref addr) => addr.fmt(f), @@ -161,7 +172,21 @@ } } -fn write_ipv6(addr: &Ipv6Addr, f: &mut Formatter) -> fmt::Result { +impl PartialEq> for Host +where + S: PartialEq, +{ + fn eq(&self, other: &Host) -> bool { + match (self, other) { + (Host::Domain(a), Host::Domain(b)) => a == b, + (Host::Ipv4(a), Host::Ipv4(b)) => a == b, + (Host::Ipv6(a), Host::Ipv6(b)) => a == b, + (_, _) => false, + } + } +} + +fn write_ipv6(addr: &Ipv6Addr, f: &mut Formatter<'_>) -> fmt::Result { let segments = addr.segments(); let (compress_start, compress_end) = longest_zero_sequence(&segments); let mut i = 0; @@ -238,11 +263,11 @@ // So instead we check if the input looks like a real number and only return // an error when it's an overflow. let valid_number = match r { - 8 => input.chars().all(|c| c >= '0' && c <= '7'), - 10 => input.chars().all(|c| c >= '0' && c <= '9'), - 16 => input - .chars() - .all(|c| (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')), + 8 => input.chars().all(|c| ('0'..='7').contains(&c)), + 10 => input.chars().all(|c| ('0'..='9').contains(&c)), + 16 => input.chars().all(|c| { + ('0'..='9').contains(&c) || ('a'..='f').contains(&c) || ('A'..='F').contains(&c) + }), _ => false, }; @@ -277,7 +302,7 @@ let mut numbers: Vec = Vec::new(); let mut overflow = false; for part in parts { - if part == "" { + if part.is_empty() { return Ok(None); } match parse_ipv4number(part) { diff -Nru rust-url-2.1.1/src/lib.rs rust-url-2.2.2/src/lib.rs --- rust-url-2.1.1/src/lib.rs 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/src/lib.rs 1970-01-01 00:00:00.000000000 +0000 @@ -73,6 +73,9 @@ # run().unwrap(); ``` +## Serde + +Enable the `serde` feature to include `Deserialize` and `Serialize` implementations for `url::Url`. # Base URL @@ -103,24 +106,34 @@ # Ok(()) # } # run().unwrap(); +``` + +# Feature: `serde` + +If you enable the `serde` feature, [`Url`](struct.Url.html) will implement +[`serde::Serialize`](https://docs.rs/serde/1/serde/trait.Serialize.html) and +[`serde::Deserialize`](https://docs.rs/serde/1/serde/trait.Deserialize.html). +See [serde documentation](https://serde.rs) for more information. + +```toml +url = { version = "2", features = ["serde"] } +``` */ -#![doc(html_root_url = "https://docs.rs/url/2.1.1")] +#![doc(html_root_url = "https://docs.rs/url/2.2.2")] #[macro_use] extern crate matches; -extern crate idna; -extern crate percent_encoding; +pub use form_urlencoded; + #[cfg(feature = "serde")] extern crate serde; -use host::HostInternal; -use parser::{to_u32, Context, Parser, SchemeType, PATH_SEGMENT, USERINFO}; +use crate::host::HostInternal; +use crate::parser::{to_u32, Context, Parser, SchemeType, PATH_SEGMENT, USERINFO}; use percent_encoding::{percent_decode, percent_encode, utf8_percent_encode}; use std::borrow::Borrow; use std::cmp; -#[cfg(feature = "serde")] -use std::error::Error; use std::fmt::{self, Write}; use std::hash; use std::io; @@ -130,21 +143,21 @@ use std::path::{Path, PathBuf}; use std::str; -pub use host::Host; -pub use origin::{OpaqueOrigin, Origin}; -pub use parser::{ParseError, SyntaxViolation}; -pub use path_segments::PathSegmentsMut; -pub use query_encoding::EncodingOverride; -pub use slicing::Position; +use std::convert::TryFrom; + +pub use crate::host::Host; +pub use crate::origin::{OpaqueOrigin, Origin}; +pub use crate::parser::{ParseError, SyntaxViolation}; +pub use crate::path_segments::PathSegmentsMut; +pub use crate::slicing::Position; +pub use form_urlencoded::EncodingOverride; mod host; mod origin; mod parser; mod path_segments; -mod query_encoding; mod slicing; -pub mod form_urlencoded; #[doc(hidden)] pub mod quirks; @@ -224,7 +237,7 @@ } /// Parse an URL string with the configuration so far. - pub fn parse(self, input: &str) -> Result { + pub fn parse(self, input: &str) -> Result { Parser { serialization: String::with_capacity(input.len()), base_url: self.base_url, @@ -259,7 +272,7 @@ /// /// [`ParseError`]: enum.ParseError.html #[inline] - pub fn parse(input: &str) -> Result { + pub fn parse(input: &str) -> Result { Url::options().parse(input) } @@ -276,6 +289,7 @@ /// # fn run() -> Result<(), ParseError> { /// let url = Url::parse_with_params("https://example.net?dont=clobberme", /// &[("lang", "rust"), ("browser", "servo")])?; + /// assert_eq!("https://example.net/?dont=clobberme&lang=rust&browser=servo", url.as_str()); /// # Ok(()) /// # } /// # run().unwrap(); @@ -288,7 +302,7 @@ /// /// [`ParseError`]: enum.ParseError.html #[inline] - pub fn parse_with_params(input: &str, iter: I) -> Result + pub fn parse_with_params(input: &str, iter: I) -> Result where I: IntoIterator, I::Item: Borrow<(K, V)>, @@ -306,6 +320,8 @@ /// Parse a string as an URL, with this URL as the base URL. /// + /// The inverse of this is [`make_relative`]. + /// /// Note: a trailing slash is significant. /// Without it, the last path component is considered to be a “file” name /// to be removed to get at the “directory” that is used as the base: @@ -335,11 +351,144 @@ /// with this URL as the base URL, a [`ParseError`] variant will be returned. /// /// [`ParseError`]: enum.ParseError.html + /// [`make_relative`]: #method.make_relative #[inline] - pub fn join(&self, input: &str) -> Result { + pub fn join(&self, input: &str) -> Result { Url::options().base_url(Some(self)).parse(input) } + /// Creates a relative URL if possible, with this URL as the base URL. + /// + /// This is the inverse of [`join`]. + /// + /// # Examples + /// + /// ```rust + /// use url::Url; + /// # use url::ParseError; + /// + /// # fn run() -> Result<(), ParseError> { + /// let base = Url::parse("https://example.net/a/b.html")?; + /// let url = Url::parse("https://example.net/a/c.png")?; + /// let relative = base.make_relative(&url); + /// assert_eq!(relative.as_ref().map(|s| s.as_str()), Some("c.png")); + /// + /// let base = Url::parse("https://example.net/a/b/")?; + /// let url = Url::parse("https://example.net/a/b/c.png")?; + /// let relative = base.make_relative(&url); + /// assert_eq!(relative.as_ref().map(|s| s.as_str()), Some("c.png")); + /// + /// let base = Url::parse("https://example.net/a/b/")?; + /// let url = Url::parse("https://example.net/a/d/c.png")?; + /// let relative = base.make_relative(&url); + /// assert_eq!(relative.as_ref().map(|s| s.as_str()), Some("../d/c.png")); + /// + /// let base = Url::parse("https://example.net/a/b.html?c=d")?; + /// let url = Url::parse("https://example.net/a/b.html?e=f")?; + /// let relative = base.make_relative(&url); + /// assert_eq!(relative.as_ref().map(|s| s.as_str()), Some("?e=f")); + /// # Ok(()) + /// # } + /// # run().unwrap(); + /// ``` + /// + /// # Errors + /// + /// If this URL can't be a base for the given URL, `None` is returned. + /// This is for example the case if the scheme, host or port are not the same. + /// + /// [`join`]: #method.join + pub fn make_relative(&self, url: &Url) -> Option { + if self.cannot_be_a_base() { + return None; + } + + // Scheme, host and port need to be the same + if self.scheme() != url.scheme() || self.host() != url.host() || self.port() != url.port() { + return None; + } + + // We ignore username/password at this point + + // The path has to be transformed + let mut relative = String::new(); + + // Extract the filename of both URIs, these need to be handled separately + fn extract_path_filename(s: &str) -> (&str, &str) { + let last_slash_idx = s.rfind('/').unwrap_or(0); + let (path, filename) = s.split_at(last_slash_idx); + if filename.is_empty() { + (path, "") + } else { + (path, &filename[1..]) + } + } + + let (base_path, base_filename) = extract_path_filename(self.path()); + let (url_path, url_filename) = extract_path_filename(url.path()); + + let mut base_path = base_path.split('/').peekable(); + let mut url_path = url_path.split('/').peekable(); + + // Skip over the common prefix + while base_path.peek().is_some() && base_path.peek() == url_path.peek() { + base_path.next(); + url_path.next(); + } + + // Add `..` segments for the remainder of the base path + for base_path_segment in base_path { + // Skip empty last segments + if base_path_segment.is_empty() { + break; + } + + if !relative.is_empty() { + relative.push('/'); + } + + relative.push_str(".."); + } + + // Append the remainder of the other URI + for url_path_segment in url_path { + if !relative.is_empty() { + relative.push('/'); + } + + relative.push_str(url_path_segment); + } + + // Add the filename if they are not the same + if base_filename != url_filename { + // If the URIs filename is empty this means that it was a directory + // so we'll have to append a '/'. + // + // Otherwise append it directly as the new filename. + if url_filename.is_empty() { + relative.push('/'); + } else { + if !relative.is_empty() { + relative.push('/'); + } + relative.push_str(url_filename); + } + } + + // Query and fragment are only taken from the other URI + if let Some(query) = url.query() { + relative.push('?'); + relative.push_str(query); + } + + if let Some(fragment) = url.fragment() { + relative.push('#'); + relative.push_str(fragment); + } + + Some(relative) + } + /// Return a default `ParseOptions` that can fully configure the URL parser. /// /// # Examples @@ -403,14 +552,15 @@ /// # fn run() -> Result<(), ParseError> { /// let url_str = "https://example.net/"; /// let url = Url::parse(url_str)?; - /// assert_eq!(url.into_string(), url_str); + /// assert_eq!(String::from(url), url_str); /// # Ok(()) /// # } /// # run().unwrap(); /// ``` #[inline] + #[deprecated(since = "2.3.0", note = "use Into")] pub fn into_string(self) -> String { - self.serialization + self.into() } /// For internal testing, not part of the public API. @@ -716,8 +866,9 @@ /// # run().unwrap(); /// ``` pub fn username(&self) -> &str { - if self.has_authority() { - self.slice(self.scheme_end + ("://".len() as u32)..self.username_end) + let scheme_separator_len = "://".len() as u32; + if self.has_authority() && self.username_end > self.scheme_end + scheme_separator_len { + self.slice(self.scheme_end + scheme_separator_len..self.username_end) } else { "" } @@ -788,7 +939,8 @@ /// Return the string representation of the host (domain or IP address) for this URL, if any. /// - /// Non-ASCII domains are punycode-encoded per IDNA. + /// Non-ASCII domains are punycode-encoded per IDNA if this is the host + /// of a special URL, or percent encoded for non-special URLs. /// IPv6 addresses are given between `[` and `]` brackets. /// /// Cannot-be-a-base URLs (typical of `data:` and `mailto:`) and some `file:` URLs @@ -827,7 +979,8 @@ } /// Return the parsed representation of the host for this URL. - /// Non-ASCII domain labels are punycode-encoded per IDNA. + /// Non-ASCII domain labels are punycode-encoded per IDNA if this is the host + /// of a special URL, or percent encoded for non-special URLs. /// /// Cannot-be-a-base URLs (typical of `data:` and `mailto:`) and some `file:` URLs /// don’t have a host. @@ -866,6 +1019,8 @@ } /// If this URL has a host and it is a domain name (not an IP address), return it. + /// Non-ASCII domains are punycode-encoded per IDNA if this is the host + /// of a special URL, or percent encoded for non-special URLs. /// /// # Examples /// @@ -925,7 +1080,7 @@ /// Return the port number for this URL, or the default port number if it is known. /// /// This method only knows the default port number - /// of the `http`, `https`, `ws`, `wss`, `ftp`, and `gopher` schemes. + /// of the `http`, `https`, `ws`, `wss` and `ftp` schemes. /// /// For URLs in these schemes, this method always returns `Some(_)`. /// For other schemes, it is the same as `Url::port()`. @@ -1056,7 +1211,7 @@ /// use url::Url; /// # use std::error::Error; /// - /// # fn run() -> Result<(), Box> { + /// # fn run() -> Result<(), Box> { /// let url = Url::parse("https://example.com/foo/bar")?; /// let mut path_segments = url.path_segments().ok_or_else(|| "cannot be base")?; /// assert_eq!(path_segments.next(), Some("foo")); @@ -1079,7 +1234,8 @@ /// # } /// # run().unwrap(); /// ``` - pub fn path_segments(&self) -> Option> { + #[allow(clippy::manual_strip)] // introduced in 1.45, MSRV is 1.36 + pub fn path_segments(&self) -> Option> { let path = self.path(); if path.starts_with('/') { Some(path[1..].split('/')) @@ -1151,7 +1307,7 @@ /// #[inline] - pub fn query_pairs(&self) -> form_urlencoded::Parse { + pub fn query_pairs(&self) -> form_urlencoded::Parse<'_> { form_urlencoded::parse(self.query().unwrap_or("").as_bytes()) } @@ -1194,7 +1350,7 @@ }) } - fn mutate R, R>(&mut self, f: F) -> R { + fn mutate) -> R, R>(&mut self, f: F) -> R { let mut parser = Parser::for_setter(mem::replace(&mut self.serialization, String::new())); let result = f(&mut parser); self.serialization = parser.serialization; @@ -1336,7 +1492,7 @@ /// not `url.set_query(None)`. /// /// The state of `Url` is unspecified if this return value is leaked without being dropped. - pub fn query_pairs_mut(&mut self) -> form_urlencoded::Serializer { + pub fn query_pairs_mut(&mut self) -> form_urlencoded::Serializer<'_, UrlQuery<'_>> { let fragment = self.take_fragment(); let query_start; @@ -1413,7 +1569,8 @@ /// Return an object with methods to manipulate this URL’s path segments. /// /// Return `Err(())` if this URL is cannot-be-a-base. - pub fn path_segments_mut(&mut self) -> Result { + #[allow(clippy::result_unit_err)] + pub fn path_segments_mut(&mut self) -> Result, ()> { if self.cannot_be_a_base() { Err(()) } else { @@ -1449,7 +1606,7 @@ /// use url::Url; /// # use std::error::Error; /// - /// # fn run() -> Result<(), Box> { + /// # fn run() -> Result<(), Box> { /// let mut url = Url::parse("ssh://example.net:2048/")?; /// /// url.set_port(Some(4096)).map_err(|_| "cannot be base")?; @@ -1468,7 +1625,7 @@ /// use url::Url; /// # use std::error::Error; /// - /// # fn run() -> Result<(), Box> { + /// # fn run() -> Result<(), Box> { /// let mut url = Url::parse("https://example.org/")?; /// /// url.set_port(Some(443)).map_err(|_| "cannot be base")?; @@ -1496,6 +1653,7 @@ /// # } /// # run().unwrap(); /// ``` + #[allow(clippy::result_unit_err)] pub fn set_port(&mut self, mut port: Option) -> Result<(), ()> { // has_host implies !cannot_be_a_base if !self.has_host() || self.host() == Some(Host::Domain("")) || self.scheme() == "file" { @@ -1635,7 +1793,7 @@ } if let Some(host) = host { - if host == "" && SchemeType::from(self.scheme()).is_special() { + if host.is_empty() && SchemeType::from(self.scheme()).is_special() { return Err(ParseError::EmptyHost); } let mut host_substr = host; @@ -1662,10 +1820,8 @@ let scheme_type = SchemeType::from(self.scheme()); if scheme_type.is_special() { return Err(ParseError::EmptyHost); - } else { - if self.serialization.len() == self.path_start as usize { - self.serialization.push('/'); - } + } else if self.serialization.len() == self.path_start as usize { + self.serialization.push('/'); } debug_assert!(self.byte_at(self.scheme_end) == b':'); debug_assert!(self.byte_at(self.path_start) == b'/'); @@ -1768,6 +1924,7 @@ /// # run().unwrap(); /// ``` /// + #[allow(clippy::result_unit_err)] pub fn set_ip_host(&mut self, address: IpAddr) -> Result<(), ()> { if self.cannot_be_a_base() { return Err(()); @@ -1807,6 +1964,7 @@ /// # } /// # run().unwrap(); /// ``` + #[allow(clippy::result_unit_err)] pub fn set_password(&mut self, password: Option<&str>) -> Result<(), ()> { // has_host implies !cannot_be_a_base if !self.has_host() || self.host() == Some(Host::Domain("")) || self.scheme() == "file" { @@ -1899,6 +2057,7 @@ /// # } /// # run().unwrap(); /// ``` + #[allow(clippy::result_unit_err)] pub fn set_username(&mut self, username: &str) -> Result<(), ()> { // has_host implies !cannot_be_a_base if !self.has_host() || self.host() == Some(Host::Domain("")) || self.scheme() == "file" { @@ -1952,11 +2111,19 @@ /// Change this URL’s scheme. /// - /// Do nothing and return `Err` if: + /// Do nothing and return `Err` under the following circumstances: /// - /// * The new scheme is not in `[a-zA-Z][a-zA-Z0-9+.-]+` - /// * This URL is cannot-be-a-base and the new scheme is one of - /// `http`, `https`, `ws`, `wss`, `ftp`, or `gopher` + /// * If the new scheme is not in `[a-zA-Z][a-zA-Z0-9+.-]+` + /// * If this URL is cannot-be-a-base and the new scheme is one of + /// `http`, `https`, `ws`, `wss` or `ftp` + /// * If either the old or new scheme is `http`, `https`, `ws`, + /// `wss` or `ftp` and the other is not one of these + /// * If the new scheme is `file` and this URL includes credentials + /// or has a non-null port + /// * If this URL's scheme is `file` and its host is empty or null + /// + /// See also [the URL specification's section on legal scheme state + /// overrides](https://url.spec.whatwg.org/#scheme-state). /// /// # Examples /// @@ -2052,6 +2219,7 @@ /// # } /// # run().unwrap(); /// ``` + #[allow(clippy::result_unit_err, clippy::suspicious_operation_groupings)] pub fn set_scheme(&mut self, scheme: &str) -> Result<(), ()> { let mut parser = Parser::for_setter(String::new()); let remaining = parser.parse_scheme(parser::Input::new(scheme))?; @@ -2131,6 +2299,7 @@ /// # } /// ``` #[cfg(any(unix, windows, target_os = "redox"))] + #[allow(clippy::result_unit_err)] pub fn from_file_path>(path: P) -> Result { let mut serialization = "file://".to_owned(); let host_start = serialization.len() as u32; @@ -2167,6 +2336,7 @@ /// Note that `std::path` does not consider trailing slashes significant /// and usually does not include them (e.g. in `Path::parent()`). #[cfg(any(unix, windows, target_os = "redox"))] + #[allow(clippy::result_unit_err)] pub fn from_directory_path>(path: P) -> Result { let mut url = Url::from_file_path(path)?; if !url.serialization.ends_with('/') { @@ -2283,6 +2453,7 @@ /// for a Windows path, is not UTF-8.) #[inline] #[cfg(any(unix, windows, target_os = "redox"))] + #[allow(clippy::result_unit_err)] pub fn to_file_path(&self) -> Result { if let Some(segments) = self.path_segments() { let host = match self.host() { @@ -2319,24 +2490,50 @@ type Err = ParseError; #[inline] - fn from_str(input: &str) -> Result { + fn from_str(input: &str) -> Result { Url::parse(input) } } +impl<'a> TryFrom<&'a str> for Url { + type Error = ParseError; + + fn try_from(s: &'a str) -> Result { + Url::parse(s) + } +} + /// Display the serialization of this URL. impl fmt::Display for Url { #[inline] - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.serialization, formatter) } } +/// String converstion. +impl From for String { + fn from(value: Url) -> String { + value.serialization + } +} + /// Debug the serialization of this URL. impl fmt::Debug for Url { #[inline] fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&self.serialization, formatter) + formatter + .debug_struct("Url") + .field("scheme", &self.scheme()) + .field("cannot_be_a_base", &self.cannot_be_a_base()) + .field("username", &self.username()) + .field("password", &self.password()) + .field("host", &self.host()) + .field("port", &self.port()) + .field("path", &self.path()) + .field("query", &self.query()) + .field("fragment", &self.fragment()) + .finish() } } @@ -2448,8 +2645,10 @@ where E: Error, { - Url::parse(s) - .map_err(|err| Error::invalid_value(Unexpected::Str(s), &err.description())) + Url::parse(s).map_err(|err| { + let err_s = format!("{}", err); + Error::invalid_value(Unexpected::Str(s), &err_s.as_str()) + }) } } @@ -2555,7 +2754,7 @@ #[cfg(any(unix, target_os = "redox"))] fn file_url_segments_to_pathbuf( host: Option<&str>, - segments: str::Split, + segments: str::Split<'_, char>, ) -> Result { use std::ffi::OsStr; use std::os::unix::prelude::OsStrExt; @@ -2574,12 +2773,11 @@ bytes.extend(percent_decode(segment.as_bytes())); } // A windows drive letter must end with a slash. - if bytes.len() > 2 { - if matches!(bytes[bytes.len() - 2], b'a'..=b'z' | b'A'..=b'Z') - && matches!(bytes[bytes.len() - 1], b':' | b'|') - { - bytes.push(b'/'); - } + if bytes.len() > 2 + && matches!(bytes[bytes.len() - 2], b'a'..=b'z' | b'A'..=b'Z') + && matches!(bytes[bytes.len() - 1], b':' | b'|') + { + bytes.push(b'/'); } let os_str = OsStr::from_bytes(&bytes); let path = PathBuf::from(os_str); @@ -2602,7 +2800,7 @@ #[cfg_attr(not(windows), allow(dead_code))] fn file_url_segments_to_pathbuf_windows( host: Option<&str>, - mut segments: str::Split, + mut segments: str::Split<'_, char>, ) -> Result { let mut string = if let Some(host) = host { r"\\".to_owned() + host @@ -2658,6 +2856,30 @@ fragment: Option, } +// `as_mut_string` string here exposes the internal serialization of an `Url`, +// which should not be exposed to users. +// We achieve that by not giving users direct access to `UrlQuery`: +// * Its fields are private +// (and so can not be constructed with struct literal syntax outside of this crate), +// * It has no constructor +// * It is only visible (on the type level) to users in the return type of +// `Url::query_pairs_mut` which is `Serializer` +// * `Serializer` keeps its target in a private field +// * Unlike in other `Target` impls, `UrlQuery::finished` does not return `Self`. +impl<'a> form_urlencoded::Target for UrlQuery<'a> { + fn as_mut_string(&mut self) -> &mut String { + &mut self.url.as_mut().unwrap().serialization + } + + fn finish(mut self) -> &'a mut Url { + let url = self.url.take().unwrap(); + url.restore_already_parsed_fragment(self.fragment.take()); + url + } + + type Finished = &'a mut Url; +} + impl<'a> Drop for UrlQuery<'a> { fn drop(&mut self) { if let Some(url) = self.url.take() { diff -Nru rust-url-2.1.1/src/origin.rs rust-url-2.2.2/src/origin.rs --- rust-url-2.1.1/src/origin.rs 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/src/origin.rs 1970-01-01 00:00:00.000000000 +0000 @@ -6,11 +6,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use host::Host; +use crate::host::Host; +use crate::parser::default_port; +use crate::Url; use idna::domain_to_unicode; -use parser::default_port; use std::sync::atomic::{AtomicUsize, Ordering}; -use Url; pub fn url_origin(url: &Url) -> Origin { let scheme = url.scheme(); @@ -22,7 +22,7 @@ Err(_) => Origin::new_opaque(), } } - "ftp" | "gopher" | "http" | "https" | "ws" | "wss" => Origin::Tuple( + "ftp" | "http" | "https" | "ws" | "wss" => Origin::Tuple( scheme.to_owned(), url.host().unwrap().to_owned(), url.port_or_known_default().unwrap(), @@ -44,7 +44,7 @@ /// - If the scheme is "blob" the origin is the origin of the /// URL contained in the path component. If parsing fails, /// it is an opaque origin. -/// - If the scheme is "ftp", "gopher", "http", "https", "ws", or "wss", +/// - If the scheme is "ftp", "http", "https", "ws", or "wss", /// then the origin is a tuple of the scheme, host, and port. /// - If the scheme is anything else, the origin is opaque, meaning /// the URL does not have the same origin as any other URL. diff -Nru rust-url-2.1.1/src/parser.rs rust-url-2.2.2/src/parser.rs --- rust-url-2.1.1/src/parser.rs 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/src/parser.rs 1970-01-01 00:00:00.000000000 +0000 @@ -10,10 +10,10 @@ use std::fmt::{self, Formatter, Write}; use std::str; -use host::{Host, HostInternal}; +use crate::host::{Host, HostInternal}; +use crate::Url; +use form_urlencoded::EncodingOverride; use percent_encoding::{percent_encode, utf8_percent_encode, AsciiSet, CONTROLS}; -use query_encoding::EncodingOverride; -use Url; /// https://url.spec.whatwg.org/#fragment-percent-encode-set const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`'); @@ -52,6 +52,7 @@ /// /// This may be extended in the future so exhaustive matching is /// discouraged with an unused variant. + #[allow(clippy::manual_non_exhaustive)] // introduced in 1.40, MSRV is 1.36 #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum ParseError { $( @@ -62,11 +63,11 @@ __FutureProof, } - impl Error for ParseError { - fn description(&self) -> &str { + impl fmt::Display for ParseError { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { match *self { $( - ParseError::$name => $description, + ParseError::$name => fmt.write_str($description), )+ ParseError::__FutureProof => { unreachable!("Don't abuse the FutureProof!"); @@ -77,6 +78,8 @@ } } +impl Error for ParseError {} + simple_enum_error! { EmptyHost => "empty host", IdnaError => "invalid international domain name", @@ -90,12 +93,6 @@ Overflow => "URLs more than 4 GB are not supported", } -impl fmt::Display for ParseError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - fmt::Display::fmt(self.description(), f) - } -} - impl From<::idna::Errors> for ParseError { fn from(_: ::idna::Errors) -> ParseError { ParseError::IdnaError @@ -108,6 +105,7 @@ /// /// This may be extended in the future so exhaustive matching is /// discouraged with an unused variant. + #[allow(clippy::manual_non_exhaustive)] // introduced in 1.40, MSRV is 1.36 #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum SyntaxViolation { $( @@ -151,7 +149,7 @@ } impl fmt::Display for SyntaxViolation { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.description(), f) } } @@ -174,7 +172,7 @@ pub fn from(s: &str) -> Self { match s { - "http" | "https" | "ws" | "wss" | "ftp" | "gopher" => SchemeType::SpecialNotFile, + "http" | "https" | "ws" | "wss" | "ftp" => SchemeType::SpecialNotFile, "file" => SchemeType::File, _ => SchemeType::NotSpecial, } @@ -186,7 +184,6 @@ "http" | "ws" => Some(80), "https" | "wss" => Some(443), "ftp" => Some(21), - "gopher" => Some(70), _ => None, } } @@ -298,17 +295,17 @@ } pub trait Pattern { - fn split_prefix<'i>(self, input: &mut Input<'i>) -> bool; + fn split_prefix(self, input: &mut Input) -> bool; } impl Pattern for char { - fn split_prefix<'i>(self, input: &mut Input<'i>) -> bool { + fn split_prefix(self, input: &mut Input) -> bool { input.next() == Some(self) } } impl<'a> Pattern for &'a str { - fn split_prefix<'i>(self, input: &mut Input<'i>) -> bool { + fn split_prefix(self, input: &mut Input) -> bool { for c in self.chars() { if input.next() != Some(c) { return false; @@ -319,7 +316,7 @@ } impl bool> Pattern for F { - fn split_prefix<'i>(self, input: &mut Input<'i>) -> bool { + fn split_prefix(self, input: &mut Input) -> bool { input.next().map_or(false, self) } } @@ -425,8 +422,8 @@ } } - fn parse_with_scheme(mut self, input: Input) -> ParseResult { - use SyntaxViolation::{ExpectedDoubleSlash, ExpectedFileDoubleSlash}; + fn parse_with_scheme(mut self, input: Input<'_>) -> ParseResult { + use crate::SyntaxViolation::{ExpectedDoubleSlash, ExpectedFileDoubleSlash}; let scheme_end = to_u32(self.serialization.len())?; let scheme_type = SchemeType::from(&self.serialization); self.serialization.push(':'); @@ -470,10 +467,10 @@ } } - /// Scheme other than file, http, https, ws, ws, ftp, gopher. + /// Scheme other than file, http, https, ws, ws, ftp. fn parse_non_special( mut self, - input: Input, + input: Input<'_>, scheme_type: SchemeType, scheme_end: u32, ) -> ParseResult { @@ -510,11 +507,11 @@ fn parse_file( mut self, - input: Input, + input: Input<'_>, scheme_type: SchemeType, base_file_url: Option<&Url>, ) -> ParseResult { - use SyntaxViolation::Backslash; + use crate::SyntaxViolation::Backslash; // file state debug_assert!(self.serialization.is_empty()); let (first_char, input_after_first_char) = input.split_first(); @@ -552,15 +549,15 @@ self.parse_query_and_fragment(scheme_type, scheme_end, remaining)?; return Ok(Url { serialization: self.serialization, - scheme_end: scheme_end, + scheme_end, username_end: host_start, - host_start: host_start, - host_end: host_end, - host: host, + host_start, + host_end, + host, port: None, path_start: host_end, - query_start: query_start, - fragment_start: fragment_start, + query_start, + fragment_start, }); } else { self.serialization.push_str("file://"); @@ -577,7 +574,7 @@ } else if let Some(host_str) = base_url.host_str() { self.serialization.push_str(host_str); host_end = self.serialization.len(); - host = base_url.host.clone(); + host = base_url.host; } } } @@ -603,15 +600,15 @@ let host_end = host_end as u32; return Ok(Url { serialization: self.serialization, - scheme_end: scheme_end, + scheme_end, username_end: host_start, host_start, host_end, host, port: None, path_start: host_end, - query_start: query_start, - fragment_start: fragment_start, + query_start, + fragment_start, }); } } @@ -683,15 +680,15 @@ let path_start = path_start as u32; Ok(Url { serialization: self.serialization, - scheme_end: scheme_end, + scheme_end, username_end: path_start, host_start: path_start, host_end: path_start, host: HostInternal::None, port: None, - path_start: path_start, - query_start: query_start, - fragment_start: fragment_start, + path_start, + query_start, + fragment_start, }) } } @@ -706,22 +703,22 @@ let path_start = path_start as u32; Ok(Url { serialization: self.serialization, - scheme_end: scheme_end, + scheme_end, username_end: path_start, host_start: path_start, host_end: path_start, host: HostInternal::None, port: None, - path_start: path_start, - query_start: query_start, - fragment_start: fragment_start, + path_start, + query_start, + fragment_start, }) } } fn parse_relative( mut self, - input: Input, + input: Input<'_>, scheme_type: SchemeType, base_url: &Url, ) -> ParseResult { @@ -780,7 +777,7 @@ } let path_start = base_url.path_start; self.serialization.push_str(base_url.slice(..path_start)); - self.serialization.push_str("/"); + self.serialization.push('/'); let remaining = self.parse_path( scheme_type, &mut true, @@ -809,10 +806,10 @@ self.pop_path(scheme_type, base_url.path_start as usize); // A special url always has a path. // A path always starts with '/' - if self.serialization.len() == base_url.path_start as usize { - if SchemeType::from(base_url.scheme()).is_special() || !input.is_empty() { - self.serialization.push('/'); - } + if self.serialization.len() == base_url.path_start as usize + && (SchemeType::from(base_url.scheme()).is_special() || !input.is_empty()) + { + self.serialization.push('/'); } let remaining = match input.split_first() { (Some('/'), remaining) => self.parse_path( @@ -842,7 +839,7 @@ fn after_double_slash( mut self, - input: Input, + input: Input<'_>, scheme_type: SchemeType, scheme_end: u32, ) -> ParseResult { @@ -984,9 +981,9 @@ } pub fn parse_host( - mut input: Input, + mut input: Input<'_>, scheme_type: SchemeType, - ) -> ParseResult<(Host, Input)> { + ) -> ParseResult<(Host, Input<'_>)> { if scheme_type.is_file() { return Parser::get_file_host(input); } @@ -1040,7 +1037,7 @@ Ok((host, input)) } - fn get_file_host<'i>(input: Input<'i>) -> ParseResult<(Host, Input)> { + fn get_file_host(input: Input<'_>) -> ParseResult<(Host, Input<'_>)> { let (_, host_str, remaining) = Parser::file_host(input)?; let host = match Host::parse(&host_str)? { Host::Domain(ref d) if d == "localhost" => Host::Domain("".to_string()), @@ -1074,7 +1071,7 @@ Ok((has_host, host, remaining)) } - pub fn file_host<'i>(input: Input<'i>) -> ParseResult<(bool, String, Input<'i>)> { + pub fn file_host(input: Input) -> ParseResult<(bool, String, Input)> { // Undo the Input abstraction here to avoid allocating in the common case // where the host part of the input does not contain any tab or newline let input_str = input.chars.as_str(); @@ -1109,10 +1106,10 @@ } pub fn parse_port

( - mut input: Input, + mut input: Input<'_>, default_port: P, context: Context, - ) -> ParseResult<(Option, Input)> + ) -> ParseResult<(Option, Input<'_>)> where P: Fn() -> Option, { @@ -1154,7 +1151,7 @@ self.log_violation(SyntaxViolation::Backslash); } // A special URL always has a non-empty path. - if !self.serialization.ends_with("/") { + if !self.serialization.ends_with('/') { self.serialization.push('/'); // We have already made sure the forward slash is present. if maybe_c == Some('/') || maybe_c == Some('\\') { @@ -1170,6 +1167,10 @@ // The query and path states will be handled by the caller. return input; } + + if maybe_c != None && maybe_c != Some('/') { + self.serialization.push('/'); + } // Otherwise, if c is not the EOF code point: self.parse_path(scheme_type, has_host, path_start, input) } @@ -1239,7 +1240,7 @@ | ".%2E" => { debug_assert!(self.serialization.as_bytes()[segment_start - 1] == b'/'); self.serialization.truncate(segment_start); - if self.serialization.ends_with("/") + if self.serialization.ends_with('/') && Parser::last_slash_can_be_removed(&self.serialization, path_start) { self.serialization.pop(); @@ -1247,7 +1248,7 @@ self.shorten_path(scheme_type, path_start); // and then if neither c is U+002F (/), nor url is special and c is U+005C (\), append the empty string to url’s path. - if ends_with_slash && !self.serialization.ends_with("/") { + if ends_with_slash && !self.serialization.ends_with('/') { self.serialization.push('/'); } } @@ -1255,7 +1256,7 @@ // nor url is special and c is U+005C (\), append the empty string to url’s path. "." | "%2e" | "%2E" => { self.serialization.truncate(segment_start); - if !self.serialization.ends_with("/") { + if !self.serialization.ends_with('/') { self.serialization.push('/'); } } @@ -1263,7 +1264,7 @@ // If url’s scheme is "file", url’s path is empty, and buffer is a Windows drive letter, then if scheme_type.is_file() && is_windows_drive_letter(segment_before_slash) { // Replace the second code point in buffer with U+003A (:). - if let Some(c) = segment_before_slash.chars().nth(0) { + if let Some(c) = segment_before_slash.chars().next() { self.serialization.truncate(segment_start); self.serialization.push(c); self.serialization.push(':'); @@ -1291,14 +1292,15 @@ //FIXME: log violation let path = self.serialization.split_off(path_start); self.serialization.push('/'); - self.serialization.push_str(&path.trim_start_matches("/")); + self.serialization.push_str(&path.trim_start_matches('/')); } + input } - fn last_slash_can_be_removed(serialization: &String, path_start: usize) -> bool { + fn last_slash_can_be_removed(serialization: &str, path_start: usize) -> bool { let url_before_segment = &serialization[..serialization.len() - 1]; - if let Some(segment_before_start) = url_before_segment.rfind("/") { + if let Some(segment_before_start) = url_before_segment.rfind('/') { // Do not remove the root slash segment_before_start >= path_start // Or a windows drive letter slash @@ -1356,6 +1358,7 @@ } } + #[allow(clippy::too_many_arguments)] fn with_query_and_fragment( mut self, scheme_type: SchemeType, @@ -1366,7 +1369,7 @@ host: HostInternal, port: Option, path_start: u32, - remaining: Input, + remaining: Input<'_>, ) -> ParseResult { let (query_start, fragment_start) = self.parse_query_and_fragment(scheme_type, scheme_end, remaining)?; @@ -1389,7 +1392,7 @@ &mut self, scheme_type: SchemeType, scheme_end: u32, - mut input: Input, + mut input: Input<'_>, ) -> ParseResult<(Option, Option)> { let mut query_start = None; match input.next() { @@ -1433,10 +1436,14 @@ } let encoding = match &self.serialization[..scheme_end as usize] { - "http" | "https" | "file" | "ftp" | "gopher" => self.query_encoding_override, + "http" | "https" | "file" | "ftp" => self.query_encoding_override, _ => None, }; - let query_bytes = ::query_encoding::encode(encoding, &query); + let query_bytes = if let Some(o) = encoding { + o(&query) + } else { + query.as_bytes().into() + }; let set = if scheme_type.is_special() { SPECIAL_QUERY } else { @@ -1446,7 +1453,7 @@ remaining } - fn fragment_only(mut self, base_url: &Url, mut input: Input) -> ParseResult { + fn fragment_only(mut self, base_url: &Url, mut input: Input<'_>) -> ParseResult { let before_fragment = match base_url.fragment_start { Some(i) => base_url.slice(..i), None => &*base_url.serialization, @@ -1466,19 +1473,19 @@ }) } - pub fn parse_fragment(&mut self, mut input: Input) { + pub fn parse_fragment(&mut self, mut input: Input<'_>) { while let Some((c, utf8_c)) = input.next_utf8() { if c == '\0' { self.log_violation(SyntaxViolation::NullInFragment) } else { self.check_url_code_point(c, &input); - self.serialization - .extend(utf8_percent_encode(utf8_c, FRAGMENT)); } + self.serialization + .extend(utf8_percent_encode(utf8_c, FRAGMENT)); } } - fn check_url_code_point(&self, c: char, input: &Input) { + fn check_url_code_point(&self, c: char, input: &Input<'_>) { if let Some(vfn) = self.violation_fn { if c == '%' { let mut input = input.clone(); @@ -1581,7 +1588,7 @@ } /// https://url.spec.whatwg.org/#start-with-a-windows-drive-letter -fn starts_with_windows_drive_letter_segment(input: &Input) -> bool { +fn starts_with_windows_drive_letter_segment(input: &Input<'_>) -> bool { let mut input = input.clone(); match (input.next(), input.next(), input.next()) { // its first two code points are a Windows drive letter diff -Nru rust-url-2.1.1/src/path_segments.rs rust-url-2.2.2/src/path_segments.rs --- rust-url-2.1.1/src/path_segments.rs 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/src/path_segments.rs 1970-01-01 00:00:00.000000000 +0000 @@ -6,9 +6,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use parser::{self, to_u32, SchemeType}; +use crate::parser::{self, to_u32, SchemeType}; +use crate::Url; use std::str; -use Url; /// Exposes methods to manipulate the path of an URL that is not cannot-be-base. /// @@ -21,7 +21,7 @@ /// use url::Url; /// # use std::error::Error; /// -/// # fn run() -> Result<(), Box> { +/// # fn run() -> Result<(), Box> { /// let mut url = Url::parse("mailto:me@example.com")?; /// assert!(url.path_segments_mut().is_err()); /// @@ -42,7 +42,7 @@ } // Not re-exported outside the crate -pub fn new(url: &mut Url) -> PathSegmentsMut { +pub fn new(url: &mut Url) -> PathSegmentsMut<'_> { let after_path = url.take_after_path(); let old_after_path_position = to_u32(url.serialization.len()).unwrap(); // Special urls always have a non empty path @@ -80,7 +80,7 @@ /// use url::Url; /// # use std::error::Error; /// - /// # fn run() -> Result<(), Box> { + /// # fn run() -> Result<(), Box> { /// let mut url = Url::parse("https://github.com/servo/rust-url/")?; /// url.path_segments_mut().map_err(|_| "cannot be base")? /// .clear().push("logout"); @@ -108,7 +108,7 @@ /// use url::Url; /// # use std::error::Error; /// - /// # fn run() -> Result<(), Box> { + /// # fn run() -> Result<(), Box> { /// let mut url = Url::parse("https://github.com/servo/rust-url/")?; /// url.path_segments_mut().map_err(|_| "cannot be base")? /// .push("pulls"); @@ -123,6 +123,9 @@ /// # run().unwrap(); /// ``` pub fn pop_if_empty(&mut self) -> &mut Self { + if self.after_first_slash >= self.url.serialization.len() { + return self; + } if self.url.serialization[self.after_first_slash..].ends_with('/') { self.url.serialization.pop(); } @@ -135,6 +138,9 @@ /// /// Returns `&mut Self` so that method calls can be chained. pub fn pop(&mut self) -> &mut Self { + if self.after_first_slash >= self.url.serialization.len() { + return self; + } let last_slash = self.url.serialization[self.after_first_slash..] .rfind('/') .unwrap_or(0); @@ -177,7 +183,7 @@ /// use url::Url; /// # use std::error::Error; /// - /// # fn run() -> Result<(), Box> { + /// # fn run() -> Result<(), Box> { /// let mut url = Url::parse("https://github.com/")?; /// let org = "servo"; /// let repo = "rust-url"; @@ -197,7 +203,7 @@ /// use url::Url; /// # use std::error::Error; /// - /// # fn run() -> Result<(), Box> { + /// # fn run() -> Result<(), Box> { /// let mut url = Url::parse("https://github.com/servo")?; /// url.path_segments_mut().map_err(|_| "cannot be base")? /// .extend(&["..", "rust-url", ".", "pulls"]); diff -Nru rust-url-2.1.1/src/query_encoding.rs rust-url-2.2.2/src/query_encoding.rs --- rust-url-2.1.1/src/query_encoding.rs 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/src/query_encoding.rs 1970-01-01 00:00:00.000000000 +0000 @@ -1,35 +0,0 @@ -// Copyright 2019 The rust-url developers. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::borrow::Cow; - -pub type EncodingOverride<'a> = Option<&'a dyn Fn(&str) -> Cow<[u8]>>; - -pub(crate) fn encode<'a>(encoding_override: EncodingOverride, input: &'a str) -> Cow<'a, [u8]> { - if let Some(o) = encoding_override { - return o(input); - } - input.as_bytes().into() -} - -pub(crate) fn decode_utf8_lossy(input: Cow<[u8]>) -> Cow { - match input { - Cow::Borrowed(bytes) => String::from_utf8_lossy(bytes), - Cow::Owned(bytes) => { - let raw_utf8: *const [u8]; - match String::from_utf8_lossy(&bytes) { - Cow::Borrowed(utf8) => raw_utf8 = utf8.as_bytes(), - Cow::Owned(s) => return s.into(), - } - // from_utf8_lossy returned a borrow of `bytes` unchanged. - debug_assert!(raw_utf8 == &*bytes as *const [u8]); - // Reuse the existing `Vec` allocation. - unsafe { String::from_utf8_unchecked(bytes) }.into() - } - } -} diff -Nru rust-url-2.1.1/src/quirks.rs rust-url-2.2.2/src/quirks.rs --- rust-url-2.1.1/src/quirks.rs 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/src/quirks.rs 1970-01-01 00:00:00.000000000 +0000 @@ -11,8 +11,8 @@ //! Unless you need to be interoperable with web browsers, //! you probably want to use `Url` method instead. -use parser::{default_port, Context, Input, Parser, SchemeType}; -use {idna, Host, ParseError, Position, Url}; +use crate::parser::{default_port, Context, Input, Parser, SchemeType}; +use crate::{Host, ParseError, Position, Url}; /// https://url.spec.whatwg.org/#dom-url-domaintoascii pub fn domain_to_ascii(domain: &str) -> String { @@ -56,6 +56,7 @@ } /// Setter for https://url.spec.whatwg.org/#dom-url-protocol +#[allow(clippy::result_unit_err)] pub fn set_protocol(url: &mut Url, mut new_protocol: &str) -> Result<(), ()> { // The scheme state in the spec ignores everything after the first `:`, // but `set_scheme` errors if there is more. @@ -72,6 +73,7 @@ } /// Setter for https://url.spec.whatwg.org/#dom-url-username +#[allow(clippy::result_unit_err)] pub fn set_username(url: &mut Url, new_username: &str) -> Result<(), ()> { url.set_username(new_username) } @@ -83,6 +85,7 @@ } /// Setter for https://url.spec.whatwg.org/#dom-url-password +#[allow(clippy::result_unit_err)] pub fn set_password(url: &mut Url, new_password: &str) -> Result<(), ()> { url.set_password(if new_password.is_empty() { None @@ -98,6 +101,7 @@ } /// Setter for https://url.spec.whatwg.org/#dom-url-host +#[allow(clippy::result_unit_err)] pub fn set_host(url: &mut Url, new_host: &str) -> Result<(), ()> { // If context object’s url’s cannot-be-a-base-URL flag is set, then return. if url.cannot_be_a_base() { @@ -111,6 +115,11 @@ { let scheme = url.scheme(); let scheme_type = SchemeType::from(scheme); + if scheme_type == SchemeType::File && new_host.is_empty() { + url.set_host_internal(Host::Domain(String::new()), None); + return Ok(()); + } + if let Ok((h, remaining)) = Parser::parse_host(input, scheme_type) { host = h; opt_port = if let Some(remaining) = remaining.split_prefix(':') { @@ -132,13 +141,9 @@ if host == Host::Domain("".to_string()) { if !username(&url).is_empty() { return Err(()); - } - if let Some(p) = opt_port { - if let Some(_) = p { - return Err(()); - } - } - if url.port().is_some() { + } else if let Some(Some(_)) = opt_port { + return Err(()); + } else if url.port().is_some() { return Err(()); } } @@ -153,6 +158,7 @@ } /// Setter for https://url.spec.whatwg.org/#dom-url-hostname +#[allow(clippy::result_unit_err)] pub fn set_hostname(url: &mut Url, new_hostname: &str) -> Result<(), ()> { if url.cannot_be_a_base() { return Err(()); @@ -160,6 +166,11 @@ // Host parsing rules are strict we don't want to trim the input let input = Input::no_trim(new_hostname); let scheme_type = SchemeType::from(url.scheme()); + if scheme_type == SchemeType::File && new_hostname.is_empty() { + url.set_host_internal(Host::Domain(String::new()), None); + return Ok(()); + } + if let Ok((host, _remaining)) = Parser::parse_host(input, scheme_type) { if let Host::Domain(h) = &host { if h.is_empty() { @@ -189,6 +200,7 @@ } /// Setter for https://url.spec.whatwg.org/#dom-url-port +#[allow(clippy::result_unit_err)] pub fn set_port(url: &mut Url, new_port: &str) -> Result<(), ()> { let result; { @@ -222,10 +234,10 @@ if url.cannot_be_a_base() { return; } - if Some('/') == new_pathname.chars().nth(0) + if new_pathname.starts_with('/') || (SchemeType::from(url.scheme()).is_special() // \ is a segment delimiter for 'special' URLs" - && Some('\\') == new_pathname.chars().nth(0)) + && new_pathname.starts_with('\\')) { url.set_path(new_pathname) } else { diff -Nru rust-url-2.1.1/src/slicing.rs rust-url-2.2.2/src/slicing.rs --- rust-url-2.1.1/src/slicing.rs 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/src/slicing.rs 1970-01-01 00:00:00.000000000 +0000 @@ -6,8 +6,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use crate::Url; use std::ops::{Index, Range, RangeFrom, RangeFull, RangeTo}; -use Url; impl Index for Url { type Output = str; diff -Nru rust-url-2.1.1/tests/data.rs rust-url-2.2.2/tests/data.rs --- rust-url-2.1.1/tests/data.rs 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/tests/data.rs 1970-01-01 00:00:00.000000000 +0000 @@ -8,83 +8,146 @@ //! Data-driven tests -extern crate rustc_test as test; -extern crate serde_json; -extern crate url; +use std::ops::Deref; +use std::str::FromStr; use serde_json::Value; -use std::str::FromStr; use url::{quirks, Url}; -fn check_invariants(url: &Url) { - url.check_invariants().unwrap(); - #[cfg(feature = "serde")] - { - let bytes = serde_json::to_vec(url).unwrap(); - let new_url: Url = serde_json::from_slice(&bytes).unwrap(); - assert_eq!(url, &new_url); - } -} +#[test] +fn urltestdata() { + // Copied form https://github.com/w3c/web-platform-tests/blob/master/url/ + let mut json = Value::from_str(include_str!("urltestdata.json")) + .expect("JSON parse error in urltestdata.json"); -fn run_parsing(input: &str, base: &str, expected: Result) { - let base = match Url::parse(&base) { - Ok(base) => base, - Err(_) if expected.is_err() => return, - Err(message) => panic!("Error parsing base {:?}: {}", base, message), - }; - let (url, expected) = match (base.join(&input), expected) { - (Ok(url), Ok(expected)) => (url, expected), - (Err(_), Err(())) => return, - (Err(message), Ok(_)) => panic!("Error parsing URL {:?}: {}", input, message), - (Ok(_), Err(())) => panic!("Expected a parse error for URL {:?}", input), - }; + let mut passed = true; + for entry in json.as_array_mut().unwrap() { + if entry.is_string() { + continue; // ignore comments + } - check_invariants(&url); + let base = entry.take_string("base"); + let input = entry.take_string("input"); + let failure = entry.take_key("failure").is_some(); + + let base = match Url::parse(&base) { + Ok(base) => base, + Err(_) if failure => continue, + Err(message) => { + eprint_failure( + format!(" failed: error parsing base {:?}: {}", base, message), + &format!("parse base for {:?}", input), + None, + ); + passed = false; + continue; + } + }; + + let url = match (base.join(&input), failure) { + (Ok(url), false) => url, + (Err(_), true) => continue, + (Err(message), false) => { + eprint_failure( + format!(" failed: {}", message), + &format!("parse URL for {:?}", input), + None, + ); + passed = false; + continue; + } + (Ok(_), true) => { + eprint_failure( + format!(" failed: expected parse error for URL {:?}", input), + &format!("parse URL for {:?}", input), + None, + ); + passed = false; + continue; + } + }; + + passed &= check_invariants(&url, &format!("invariants for {:?}", input), None); - macro_rules! assert_eq { - ($expected: expr, $got: expr) => {{ - let expected = $expected; - let got = $got; - assert!( - expected == got, - "\n{:?}\n!= {}\n{:?}\nfor URL {:?}\n", - got, - stringify!($expected), - expected, - url + for &attr in ATTRIBS { + passed &= test_eq_eprint( + entry.take_string(attr), + get(&url, attr), + &format!("{:?} - {}", input, attr), + None, ); - }}; + } + + if let Some(expected_origin) = entry.take_key("origin").map(|s| s.string()) { + passed &= test_eq_eprint( + expected_origin, + &quirks::origin(&url), + &format!("origin for {:?}", input), + None, + ); + } } - macro_rules! assert_attributes { - ($($attr: ident)+) => { - { - $( - assert_eq!(expected.$attr, quirks::$attr(&url)); - )+; + assert!(passed) +} + +#[allow(clippy::option_as_ref_deref)] // introduced in 1.40, MSRV is 1.36 +#[test] +fn setters_tests() { + let mut json = Value::from_str(include_str!("setters_tests.json")) + .expect("JSON parse error in setters_tests.json"); + + let mut passed = true; + for &attr in ATTRIBS { + if attr == "href" { + continue; + } + + let mut tests = json.take_key(attr).unwrap(); + for mut test in tests.as_array_mut().unwrap().drain(..) { + let comment = test.take_key("comment").map(|s| s.string()); + let href = test.take_string("href"); + let new_value = test.take_string("new_value"); + let name = format!("{:?}.{} = {:?}", href, attr, new_value); + let mut expected = test.take_key("expected").unwrap(); + + let mut url = Url::parse(&href).unwrap(); + let comment_ref = comment.as_ref().map(|s| s.deref()); + passed &= check_invariants(&url, &name, comment_ref); + let _ = set(&mut url, attr, &new_value); + + for attr in ATTRIBS { + if let Some(value) = expected.take_key(attr) { + passed &= test_eq_eprint(value.string(), get(&url, attr), &name, comment_ref); + }; } + + passed &= check_invariants(&url, &name, comment_ref); } } - assert_attributes!(href protocol username password host hostname port pathname search hash); + assert!(passed); +} - if let Some(expected_origin) = expected.origin { - assert_eq!(expected_origin, quirks::origin(&url)); +fn check_invariants(url: &Url, name: &str, comment: Option<&str>) -> bool { + let mut passed = true; + if let Err(e) = url.check_invariants() { + passed = false; + eprint_failure( + format!(" failed: invariants checked -> {:?}", e), + name, + comment, + ); } -} -struct ExpectedAttributes { - href: String, - origin: Option, - protocol: String, - username: String, - password: String, - host: String, - hostname: String, - port: String, - pathname: String, - search: String, - hash: String, + #[cfg(feature = "serde")] + { + let bytes = serde_json::to_vec(url).unwrap(); + let new_url: Url = serde_json::from_slice(&bytes).unwrap(); + passed &= test_eq_eprint(url.to_string(), &new_url.to_string(), name, comment); + } + + passed } trait JsonExt { @@ -111,100 +174,60 @@ } } -fn collect_parsing(add_test: &mut F) { - // Copied form https://github.com/w3c/web-platform-tests/blob/master/url/ - let mut json = Value::from_str(include_str!("urltestdata.json")) - .expect("JSON parse error in urltestdata.json"); - for entry in json.as_array_mut().unwrap() { - if entry.is_string() { - continue; // ignore comments - } - let base = entry.take_string("base"); - let input = entry.take_string("input"); - let expected = if entry.take_key("failure").is_some() { - Err(()) - } else { - Ok(ExpectedAttributes { - href: entry.take_string("href"), - origin: entry.take_key("origin").map(|s| s.string()), - protocol: entry.take_string("protocol"), - username: entry.take_string("username"), - password: entry.take_string("password"), - host: entry.take_string("host"), - hostname: entry.take_string("hostname"), - port: entry.take_string("port"), - pathname: entry.take_string("pathname"), - search: entry.take_string("search"), - hash: entry.take_string("hash"), - }) - }; - add_test( - format!("{:?} @ base {:?}", input, base), - test::TestFn::dyn_test_fn(move || run_parsing(&input, &base, expected)), - ); +fn get<'a>(url: &'a Url, attr: &str) -> &'a str { + match attr { + "href" => quirks::href(url), + "protocol" => quirks::protocol(url), + "username" => quirks::username(url), + "password" => quirks::password(url), + "hostname" => quirks::hostname(url), + "host" => quirks::host(url), + "port" => quirks::port(url), + "pathname" => quirks::pathname(url), + "search" => quirks::search(url), + "hash" => quirks::hash(url), + _ => unreachable!(), } } -fn collect_setters(add_test: &mut F) -where - F: FnMut(String, test::TestFn), -{ - let mut json = Value::from_str(include_str!("setters_tests.json")) - .expect("JSON parse error in setters_tests.json"); - - macro_rules! setter { - ($attr: expr, $setter: ident) => {{ - let mut tests = json.take_key($attr).unwrap(); - for mut test in tests.as_array_mut().unwrap().drain(..) { - let comment = test.take_key("comment") - .map(|s| s.string()) - .unwrap_or(String::new()); - let href = test.take_string("href"); - let new_value = test.take_string("new_value"); - let name = format!("{:?}.{} = {:?} {}", href, $attr, new_value, comment); - let mut expected = test.take_key("expected").unwrap(); - add_test(name, test::TestFn::dyn_test_fn(move || { - let mut url = Url::parse(&href).unwrap(); - check_invariants(&url); - let _ = quirks::$setter(&mut url, &new_value); - assert_attributes!(url, expected, - href protocol username password host hostname port pathname search hash); - check_invariants(&url); - })) - } - }} - } - macro_rules! assert_attributes { - ($url: expr, $expected: expr, $($attr: ident)+) => { - $( - if let Some(value) = $expected.take_key(stringify!($attr)) { - assert_eq!(quirks::$attr(&$url), value.string()) - } - )+ - } - } - setter!("protocol", set_protocol); - setter!("username", set_username); - setter!("password", set_password); - setter!("hostname", set_hostname); - setter!("host", set_host); - setter!("port", set_port); - setter!("pathname", set_pathname); - setter!("search", set_search); - setter!("hash", set_hash); +#[allow(clippy::unit_arg)] +fn set<'a>(url: &'a mut Url, attr: &str, new: &str) { + let _ = match attr { + "protocol" => quirks::set_protocol(url, new), + "username" => quirks::set_username(url, new), + "password" => quirks::set_password(url, new), + "hostname" => quirks::set_hostname(url, new), + "host" => quirks::set_host(url, new), + "port" => quirks::set_port(url, new), + "pathname" => Ok(quirks::set_pathname(url, new)), + "search" => Ok(quirks::set_search(url, new)), + "hash" => Ok(quirks::set_hash(url, new)), + _ => unreachable!(), + }; } -fn main() { - let mut tests = Vec::new(); - { - let mut add_one = |name: String, run: test::TestFn| { - tests.push(test::TestDescAndFn { - desc: test::TestDesc::new(test::DynTestName(name)), - testfn: run, - }) - }; - collect_parsing(&mut add_one); - collect_setters(&mut add_one); +fn test_eq_eprint(expected: String, actual: &str, name: &str, comment: Option<&str>) -> bool { + if expected == actual { + return true; + } + eprint_failure( + format!("expected: {}\n actual: {}", expected, actual), + name, + comment, + ); + false +} + +fn eprint_failure(err: String, name: &str, comment: Option<&str>) { + eprintln!(" test: {}\n{}", name, err); + if let Some(comment) = comment { + eprintln!("{}\n", comment); + } else { + eprintln!(); } - test::test_main(&std::env::args().collect::>(), tests) } + +const ATTRIBS: &[&str] = &[ + "href", "protocol", "username", "password", "host", "hostname", "port", "pathname", "search", + "hash", +]; diff -Nru rust-url-2.1.1/tests/setters_tests.json rust-url-2.2.2/tests/setters_tests.json --- rust-url-2.1.1/tests/setters_tests.json 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/tests/setters_tests.json 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +1,6 @@ { "comment": [ + "AS OF https://github.com/jsdom/whatwg-url/commit/35f04dfd3048cf6362f4398745bb13375c5020c2", "## Tests for setters of https://url.spec.whatwg.org/#urlutils-members", "", "This file contains a JSON object.", @@ -122,7 +123,7 @@ "href": "gopher://example.net:1234", "new_value": "file", "expected": { - "href": "gopher://example.net:1234/", + "href": "gopher://example.net:1234", "protocol": "gopher:" } }, @@ -212,7 +213,7 @@ }, { "href": "ssh://me@example.net", - "new_value": "gopher", + "new_value": "https", "expected": { "href": "ssh://me@example.net", "protocol": "ssh:" @@ -597,7 +598,7 @@ } }, { - "comment": "Cannot-be-a-base means no password", + "comment": "Cannot-be-a-base means no host", "href": "data:text/plain,Stuff", "new_value": "example.net", "expected": { @@ -687,6 +688,17 @@ } }, { + "comment": "IPv6 literal address with port, crbug.com/1012416", + "href": "http://example.net", + "new_value": "[2001:db8::2]:4002", + "expected": { + "href": "http://[2001:db8::2]:4002/", + "host": "[2001:db8::2]:4002", + "hostname": "[2001:db8::2]", + "port": "4002" + } + }, + { "comment": "Default port number is removed", "href": "http://example.net", "new_value": "example.com:80", @@ -951,16 +963,6 @@ } }, { - "href": "file://hi/x", - "new_value": "", - "expected": { - "href": "file:///x", - "host": "", - "hostname": "", - "port": "" - } - }, - { "href": "sc://test@test/", "new_value": "", "expected": { @@ -1074,7 +1076,7 @@ } }, { - "comment": "Cannot-be-a-base means no password", + "comment": "Cannot-be-a-base means no host", "href": "data:text/plain,Stuff", "new_value": "example.net", "expected": { @@ -1285,16 +1287,6 @@ } }, { - "href": "file://hi/x", - "new_value": "", - "expected": { - "href": "file:///x", - "host": "", - "hostname": "", - "port": "" - } - }, - { "href": "sc://test@test/", "new_value": "", "expected": { @@ -1837,12 +1829,30 @@ } }, { - "comment": "Simple percent-encoding; nuls, tabs, and newlines are removed", + "comment": "Simple percent-encoding; tabs and newlines are removed", "href": "a:/", "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé", "expected": { - "href": "a:/#%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9", - "hash": "#%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" + "href": "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9", + "hash": "#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" + } + }, + { + "comment": "Percent-encode NULLs in fragment", + "href": "http://example.net", + "new_value": "a\u0000b", + "expected": { + "href": "http://example.net/#a%00b", + "hash": "#a%00b" + } + }, + { + "comment": "Percent-encode NULLs in fragment", + "href": "non-spec:/", + "new_value": "a\u0000b", + "expected": { + "href": "non-spec:/#a%00b", + "hash": "#a%00b" } }, { diff -Nru rust-url-2.1.1/tests/unit.rs rust-url-2.2.2/tests/unit.rs --- rust-url-2.1.1/tests/unit.rs 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/tests/unit.rs 1970-01-01 00:00:00.000000000 +0000 @@ -8,14 +8,11 @@ //! Unit tests -extern crate percent_encoding; -extern crate url; - use std::borrow::Cow; use std::cell::{Cell, RefCell}; use std::net::{Ipv4Addr, Ipv6Addr}; use std::path::{Path, PathBuf}; -use url::{form_urlencoded, Host, Url}; +use url::{form_urlencoded, Host, Origin, Url}; #[test] fn size() { @@ -277,6 +274,9 @@ assert_host("http://2..2.3", Host::Domain("2..2.3")); assert!(Url::parse("http://42.0x1232131").is_err()); assert!(Url::parse("http://192.168.0.257").is_err()); + + assert_eq!(Host::Domain("foo"), Host::Domain("foo").to_owned()); + assert_ne!(Host::Domain("foo"), Host::Domain("bar").to_owned()); } #[test] @@ -337,7 +337,7 @@ #[test] fn test_form_urlencoded() { - let pairs: &[(Cow, Cow)] = &[ + let pairs: &[(Cow<'_, str>, Cow<'_, str>)] = &[ ("foo".into(), "é&".into()), ("bar".into(), "".into()), ("foo".into(), "#".into()), @@ -358,8 +358,9 @@ .append_pair("foo", "é&") .append_pair("bar", "") .append_pair("foo", "#") + .append_key_only("json") .finish(); - assert_eq!(encoded, "foo=%C3%A9%26&bar=&foo=%23"); + assert_eq!(encoded, "foo=%C3%A9%26&bar=&foo=%23&json"); } #[test] @@ -367,8 +368,9 @@ let encoded = form_urlencoded::Serializer::new(String::new()) .encoding_override(Some(&|s| s.as_bytes().to_ascii_uppercase().into())) .append_pair("foo", "bar") + .append_key_only("xml") .finish(); - assert_eq!(encoded, "FOO=BAR"); + assert_eq!(encoded, "FOO=BAR&XML"); } #[test] @@ -516,6 +518,209 @@ } #[test] +fn test_origin_blob_equality() { + let origin = &Url::parse("http://example.net/").unwrap().origin(); + let blob_origin = &Url::parse("blob:http://example.net/").unwrap().origin(); + + assert_eq!(origin, blob_origin); +} + +#[test] +fn test_origin_opaque() { + assert!(!Origin::new_opaque().is_tuple()); + assert!(!&Url::parse("blob:malformed//").unwrap().origin().is_tuple()) +} + +#[test] +fn test_origin_unicode_serialization() { + let data = [ + ("http://😅.com", "http://😅.com"), + ("ftp://😅:🙂@🙂.com", "ftp://🙂.com"), + ("https://user@😅.com", "https://😅.com"), + ("http://😅.🙂:40", "http://😅.🙂:40"), + ]; + for &(unicode_url, expected_serialization) in &data { + let origin = Url::parse(unicode_url).unwrap().origin(); + assert_eq!(origin.unicode_serialization(), *expected_serialization); + } + + let ascii_origins = [ + Url::parse("http://example.net/").unwrap().origin(), + Url::parse("http://example.net:80/").unwrap().origin(), + Url::parse("http://example.net:81/").unwrap().origin(), + Url::parse("http://example.net").unwrap().origin(), + Url::parse("http://example.net/hello").unwrap().origin(), + Url::parse("https://example.net").unwrap().origin(), + Url::parse("ftp://example.net").unwrap().origin(), + Url::parse("file://example.net").unwrap().origin(), + Url::parse("http://user@example.net/").unwrap().origin(), + Url::parse("http://user:pass@example.net/") + .unwrap() + .origin(), + Url::parse("http://127.0.0.1").unwrap().origin(), + ]; + for ascii_origin in &ascii_origins { + assert_eq!( + ascii_origin.ascii_serialization(), + ascii_origin.unicode_serialization() + ); + } +} + +#[test] +fn test_socket_addrs() { + use std::net::ToSocketAddrs; + + let data = [ + ("https://127.0.0.1/", "127.0.0.1", 443), + ("https://127.0.0.1:9742/", "127.0.0.1", 9742), + ("custom-protocol://127.0.0.1:9742/", "127.0.0.1", 9742), + ("custom-protocol://127.0.0.1/", "127.0.0.1", 9743), + ("https://[::1]/", "::1", 443), + ("https://[::1]:9742/", "::1", 9742), + ("custom-protocol://[::1]:9742/", "::1", 9742), + ("custom-protocol://[::1]/", "::1", 9743), + ("https://localhost/", "localhost", 443), + ("https://localhost:9742/", "localhost", 9742), + ("custom-protocol://localhost:9742/", "localhost", 9742), + ("custom-protocol://localhost/", "localhost", 9743), + ]; + + for (url_string, host, port) in &data { + let url = url::Url::parse(url_string).unwrap(); + let addrs = url + .socket_addrs(|| match url.scheme() { + "custom-protocol" => Some(9743), + _ => None, + }) + .unwrap(); + assert_eq!( + Some(addrs[0]), + (*host, *port).to_socket_addrs().unwrap().next() + ); + } +} + +#[test] +fn test_no_base_url() { + let mut no_base_url = Url::parse("mailto:test@example.net").unwrap(); + + assert!(no_base_url.cannot_be_a_base()); + assert!(no_base_url.path_segments().is_none()); + assert!(no_base_url.path_segments_mut().is_err()); + assert!(no_base_url.set_host(Some("foo")).is_err()); + assert!(no_base_url + .set_ip_host("127.0.0.1".parse().unwrap()) + .is_err()); + + no_base_url.set_path("/foo"); + assert_eq!(no_base_url.path(), "%2Ffoo"); +} + +#[test] +fn test_domain() { + let url = Url::parse("https://127.0.0.1/").unwrap(); + assert_eq!(url.domain(), None); + + let url = Url::parse("mailto:test@example.net").unwrap(); + assert_eq!(url.domain(), None); + + let url = Url::parse("https://example.com/").unwrap(); + assert_eq!(url.domain(), Some("example.com")); +} + +#[test] +fn test_query() { + let url = Url::parse("https://example.com/products?page=2#fragment").unwrap(); + assert_eq!(url.query(), Some("page=2")); + assert_eq!( + url.query_pairs().next(), + Some((Cow::Borrowed("page"), Cow::Borrowed("2"))) + ); + + let url = Url::parse("https://example.com/products").unwrap(); + assert!(url.query().is_none()); + assert_eq!(url.query_pairs().count(), 0); + + let url = Url::parse("https://example.com/?country=español").unwrap(); + assert_eq!(url.query(), Some("country=espa%C3%B1ol")); + assert_eq!( + url.query_pairs().next(), + Some((Cow::Borrowed("country"), Cow::Borrowed("español"))) + ); + + let url = Url::parse("https://example.com/products?page=2&sort=desc").unwrap(); + assert_eq!(url.query(), Some("page=2&sort=desc")); + let mut pairs = url.query_pairs(); + assert_eq!(pairs.count(), 2); + assert_eq!( + pairs.next(), + Some((Cow::Borrowed("page"), Cow::Borrowed("2"))) + ); + assert_eq!( + pairs.next(), + Some((Cow::Borrowed("sort"), Cow::Borrowed("desc"))) + ); +} + +#[test] +fn test_fragment() { + let url = Url::parse("https://example.com/#fragment").unwrap(); + assert_eq!(url.fragment(), Some("fragment")); + + let url = Url::parse("https://example.com/").unwrap(); + assert_eq!(url.fragment(), None); +} + +#[test] +fn test_set_ip_host() { + let mut url = Url::parse("http://example.com").unwrap(); + + url.set_ip_host("127.0.0.1".parse().unwrap()).unwrap(); + assert_eq!(url.host_str(), Some("127.0.0.1")); + + url.set_ip_host("::1".parse().unwrap()).unwrap(); + assert_eq!(url.host_str(), Some("[::1]")); +} + +#[test] +fn test_set_href() { + use url::quirks::set_href; + + let mut url = Url::parse("https://existing.url").unwrap(); + + assert!(set_href(&mut url, "mal//formed").is_err()); + + assert!(set_href( + &mut url, + "https://user:pass@domain.com:9742/path/file.ext?key=val&key2=val2#fragment" + ) + .is_ok()); + assert_eq!( + url, + Url::parse("https://user:pass@domain.com:9742/path/file.ext?key=val&key2=val2#fragment") + .unwrap() + ); +} + +#[test] +fn test_domain_encoding_quirks() { + use url::quirks::{domain_to_ascii, domain_to_unicode}; + + let data = [ + ("http://example.com", "", ""), + ("😅.🙂", "xn--j28h.xn--938h", "😅.🙂"), + ("example.com", "example.com", "example.com"), + ("mailto:test@example.net", "", ""), + ]; + + for url in &data { + assert_eq!(domain_to_ascii(url.0), url.1); + assert_eq!(domain_to_unicode(url.0), url.2); + } +} + +#[test] fn test_windows_unc_path() { if !cfg!(windows) { return; @@ -580,6 +785,38 @@ } #[test] +fn test_syntax_violation_callback_types() { + use url::SyntaxViolation::*; + + let data = [ + ("http://mozilla.org/\\foo", Backslash, "backslash"), + (" http://mozilla.org", C0SpaceIgnored, "leading or trailing control or space character are ignored in URLs"), + ("http://user:pass@mozilla.org", EmbeddedCredentials, "embedding authentication information (username or password) in an URL is not recommended"), + ("http:///mozilla.org", ExpectedDoubleSlash, "expected //"), + ("file:/foo.txt", ExpectedFileDoubleSlash, "expected // after file:"), + ("file://mozilla.org/c:/file.txt", FileWithHostAndWindowsDrive, "file: with host and Windows drive letter"), + ("http://mozilla.org/^", NonUrlCodePoint, "non-URL code point"), + ("http://mozilla.org/#\00", NullInFragment, "NULL characters are ignored in URL fragment identifiers"), + ("http://mozilla.org/%1", PercentDecode, "expected 2 hex digits after %"), + ("http://mozilla.org\t/foo", TabOrNewlineIgnored, "tabs or newlines are ignored in URLs"), + ("http://user@:pass@mozilla.org", UnencodedAtSign, "unencoded @ sign in username or password") + ]; + + for test_case in &data { + let violation = Cell::new(None); + Url::options() + .syntax_violation_callback(Some(&|v| violation.set(Some(v)))) + .parse(test_case.0) + .unwrap(); + + let v = violation.take(); + assert_eq!(v, Some(test_case.1)); + assert_eq!(v.unwrap().description(), test_case.2); + assert_eq!(v.unwrap().to_string(), test_case.2); + } +} + +#[test] fn test_options_reuse() { use url::SyntaxViolation::*; let violations = RefCell::new(Vec::new()); @@ -619,3 +856,263 @@ let path = u.to_file_path().unwrap(); assert_eq!("/c:/", path.to_str().unwrap()); } + +#[test] +fn test_non_special_path() { + let mut db_url = url::Url::parse("postgres://postgres@localhost/").unwrap(); + assert_eq!(db_url.as_str(), "postgres://postgres@localhost/"); + db_url.set_path("diesel_foo"); + assert_eq!(db_url.as_str(), "postgres://postgres@localhost/diesel_foo"); + assert_eq!(db_url.path(), "/diesel_foo"); +} + +#[test] +fn test_non_special_path2() { + let mut db_url = url::Url::parse("postgres://postgres@localhost/").unwrap(); + assert_eq!(db_url.as_str(), "postgres://postgres@localhost/"); + db_url.set_path(""); + assert_eq!(db_url.path(), ""); + assert_eq!(db_url.as_str(), "postgres://postgres@localhost"); + db_url.set_path("foo"); + assert_eq!(db_url.path(), "/foo"); + assert_eq!(db_url.as_str(), "postgres://postgres@localhost/foo"); + db_url.set_path("/bar"); + assert_eq!(db_url.path(), "/bar"); + assert_eq!(db_url.as_str(), "postgres://postgres@localhost/bar"); +} + +#[test] +fn test_non_special_path3() { + let mut db_url = url::Url::parse("postgres://postgres@localhost/").unwrap(); + assert_eq!(db_url.as_str(), "postgres://postgres@localhost/"); + db_url.set_path("/"); + assert_eq!(db_url.as_str(), "postgres://postgres@localhost/"); + assert_eq!(db_url.path(), "/"); + db_url.set_path("/foo"); + assert_eq!(db_url.as_str(), "postgres://postgres@localhost/foo"); + assert_eq!(db_url.path(), "/foo"); +} + +#[test] +fn test_set_scheme_to_file_with_host() { + let mut url: Url = "http://localhost:6767/foo/bar".parse().unwrap(); + let result = url.set_scheme("file"); + assert_eq!(url.to_string(), "http://localhost:6767/foo/bar"); + assert_eq!(result, Err(())); +} + +#[test] +fn no_panic() { + let mut url = Url::parse("arhttpsps:/.//eom/dae.com/\\\\t\\:").unwrap(); + url::quirks::set_hostname(&mut url, "//eom/datcom/\\\\t\\://eom/data.cs").unwrap(); +} + +#[test] +fn pop_if_empty_in_bounds() { + let mut url = Url::parse("m://").unwrap(); + let mut segments = url.path_segments_mut().unwrap(); + segments.pop_if_empty(); + segments.pop(); +} + +#[test] +fn test_slicing() { + use url::Position::*; + + #[derive(Default)] + struct ExpectedSlices<'a> { + full: &'a str, + scheme: &'a str, + username: &'a str, + password: &'a str, + host: &'a str, + port: &'a str, + path: &'a str, + query: &'a str, + fragment: &'a str, + } + + let data = [ + ExpectedSlices { + full: "https://user:pass@domain.com:9742/path/file.ext?key=val&key2=val2#fragment", + scheme: "https", + username: "user", + password: "pass", + host: "domain.com", + port: "9742", + path: "/path/file.ext", + query: "key=val&key2=val2", + fragment: "fragment", + }, + ExpectedSlices { + full: "https://domain.com:9742/path/file.ext#fragment", + scheme: "https", + host: "domain.com", + port: "9742", + path: "/path/file.ext", + fragment: "fragment", + ..Default::default() + }, + ExpectedSlices { + full: "https://domain.com:9742/path/file.ext", + scheme: "https", + host: "domain.com", + port: "9742", + path: "/path/file.ext", + ..Default::default() + }, + ExpectedSlices { + full: "blob:blob-info", + scheme: "blob", + path: "blob-info", + ..Default::default() + }, + ]; + + for expected_slices in &data { + let url = Url::parse(expected_slices.full).unwrap(); + assert_eq!(&url[..], expected_slices.full); + assert_eq!(&url[BeforeScheme..AfterScheme], expected_slices.scheme); + assert_eq!( + &url[BeforeUsername..AfterUsername], + expected_slices.username + ); + assert_eq!( + &url[BeforePassword..AfterPassword], + expected_slices.password + ); + assert_eq!(&url[BeforeHost..AfterHost], expected_slices.host); + assert_eq!(&url[BeforePort..AfterPort], expected_slices.port); + assert_eq!(&url[BeforePath..AfterPath], expected_slices.path); + assert_eq!(&url[BeforeQuery..AfterQuery], expected_slices.query); + assert_eq!( + &url[BeforeFragment..AfterFragment], + expected_slices.fragment + ); + assert_eq!(&url[..AfterFragment], expected_slices.full); + } +} + +#[test] +fn test_make_relative() { + let tests = [ + ( + "http://127.0.0.1:8080/test", + "http://127.0.0.1:8080/test", + "", + ), + ( + "http://127.0.0.1:8080/test", + "http://127.0.0.1:8080/test/", + "test/", + ), + ( + "http://127.0.0.1:8080/test/", + "http://127.0.0.1:8080/test", + "../test", + ), + ( + "http://127.0.0.1:8080/", + "http://127.0.0.1:8080/?foo=bar#123", + "?foo=bar#123", + ), + ( + "http://127.0.0.1:8080/", + "http://127.0.0.1:8080/test/video", + "test/video", + ), + ( + "http://127.0.0.1:8080/test", + "http://127.0.0.1:8080/test/video", + "test/video", + ), + ( + "http://127.0.0.1:8080/test/", + "http://127.0.0.1:8080/test/video", + "video", + ), + ( + "http://127.0.0.1:8080/test", + "http://127.0.0.1:8080/test2/video", + "test2/video", + ), + ( + "http://127.0.0.1:8080/test/", + "http://127.0.0.1:8080/test2/video", + "../test2/video", + ), + ( + "http://127.0.0.1:8080/test/bla", + "http://127.0.0.1:8080/test2/video", + "../test2/video", + ), + ( + "http://127.0.0.1:8080/test/bla/", + "http://127.0.0.1:8080/test2/video", + "../../test2/video", + ), + ( + "http://127.0.0.1:8080/test/?foo=bar#123", + "http://127.0.0.1:8080/test/video", + "video", + ), + ( + "http://127.0.0.1:8080/test/", + "http://127.0.0.1:8080/test/video?baz=meh#456", + "video?baz=meh#456", + ), + ( + "http://127.0.0.1:8080/test", + "http://127.0.0.1:8080/test?baz=meh#456", + "?baz=meh#456", + ), + ( + "http://127.0.0.1:8080/test/", + "http://127.0.0.1:8080/test?baz=meh#456", + "../test?baz=meh#456", + ), + ( + "http://127.0.0.1:8080/test/", + "http://127.0.0.1:8080/test/?baz=meh#456", + "?baz=meh#456", + ), + ( + "http://127.0.0.1:8080/test/?foo=bar#123", + "http://127.0.0.1:8080/test/video?baz=meh#456", + "video?baz=meh#456", + ), + ]; + + for (base, uri, relative) in &tests { + let base_uri = url::Url::parse(base).unwrap(); + let relative_uri = url::Url::parse(uri).unwrap(); + let make_relative = base_uri.make_relative(&relative_uri).unwrap(); + assert_eq!( + make_relative, *relative, + "base: {}, uri: {}, relative: {}", + base, uri, relative + ); + assert_eq!( + base_uri.join(&relative).unwrap().as_str(), + *uri, + "base: {}, uri: {}, relative: {}", + base, + uri, + relative + ); + } + + let error_tests = [ + ("http://127.0.0.1:8080/", "https://127.0.0.1:8080/test/"), + ("http://127.0.0.1:8080/", "http://127.0.0.1:8081/test/"), + ("http://127.0.0.1:8080/", "http://127.0.0.2:8080/test/"), + ("mailto:a@example.com", "mailto:b@example.com"), + ]; + + for (base, uri) in &error_tests { + let base_uri = url::Url::parse(base).unwrap(); + let relative_uri = url::Url::parse(uri).unwrap(); + let make_relative = base_uri.make_relative(&relative_uri); + assert_eq!(make_relative, None, "base: {}, uri: {}", base, uri); + } +} diff -Nru rust-url-2.1.1/tests/urltestdata.json rust-url-2.2.2/tests/urltestdata.json --- rust-url-2.1.1/tests/urltestdata.json 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/tests/urltestdata.json 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +1,6 @@ [ "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/segments.js", + "# AS OF https://github.com/jsdom/whatwg-url/commit/35f04dfd3048cf6362f4398745bb13375c5020c2", { "input": "http://example\t.\norg", "base": "http://example.org/foo/bar", @@ -1076,15 +1077,15 @@ { "input": "gopher:/example.com/", "base": "http://example.org/foo/bar", - "href": "gopher://example.com/", - "origin": "gopher://example.com", + "href": "gopher:/example.com/", + "origin": "null", "protocol": "gopher:", "username": "", "password": "", - "host": "example.com", - "hostname": "example.com", + "host": "", + "hostname": "", "port": "", - "pathname": "/", + "pathname": "/example.com/", "search": "", "hash": "" }, @@ -1241,15 +1242,15 @@ { "input": "gopher:example.com/", "base": "http://example.org/foo/bar", - "href": "gopher://example.com/", - "origin": "gopher://example.com", + "href": "gopher:example.com/", + "origin": "null", "protocol": "gopher:", "username": "", "password": "", - "host": "example.com", - "hostname": "example.com", + "host": "", + "hostname": "", "port": "", - "pathname": "/", + "pathname": "example.com/", "search": "", "hash": "" }, @@ -2511,14 +2512,14 @@ { "input": "gopher://foo:70/", "base": "about:blank", - "href": "gopher://foo/", - "origin": "gopher://foo", + "href": "gopher://foo:70/", + "origin": "null", "protocol": "gopher:", "username": "", "password": "", - "host": "foo", + "host": "foo:70", "hostname": "foo", - "port": "", + "port": "70", "pathname": "/", "search": "", "hash": "" @@ -2527,7 +2528,7 @@ "input": "gopher://foo:443/", "base": "about:blank", "href": "gopher://foo:443/", - "origin": "gopher://foo:443", + "origin": "null", "protocol": "gopher:", "username": "", "password": "", @@ -2750,15 +2751,15 @@ { "input": "gopher:/example.com/", "base": "about:blank", - "href": "gopher://example.com/", - "origin": "gopher://example.com", + "href": "gopher:/example.com/", + "origin": "null", "protocol": "gopher:", "username": "", "password": "", - "host": "example.com", - "hostname": "example.com", + "host": "", + "hostname": "", "port": "", - "pathname": "/", + "pathname": "/example.com/", "search": "", "hash": "" }, @@ -2915,15 +2916,15 @@ { "input": "gopher:example.com/", "base": "about:blank", - "href": "gopher://example.com/", - "origin": "gopher://example.com", + "href": "gopher:example.com/", + "origin": "null", "protocol": "gopher:", "username": "", "password": "", - "host": "example.com", - "hostname": "example.com", + "host": "", + "hostname": "", "port": "", - "pathname": "/", + "pathname": "example.com/", "search": "", "hash": "" }, @@ -4482,21 +4483,6 @@ "hash": "" }, { - "input": "sc://\u001F!\"$&'()*+,-.;<=>^_`{|}~/", - "base": "about:blank", - "href": "sc://%1F!\"$&'()*+,-.;<=>^_`{|}~/", - "origin": "null", - "protocol": "sc:", - "username": "", - "password": "", - "host": "%1F!\"$&'()*+,-.;<=>^_`{|}~", - "hostname": "%1F!\"$&'()*+,-.;<=>^_`{|}~", - "port": "", - "pathname": "/", - "search": "", - "hash": "" - }, - { "input": "sc://\u0000/", "base": "about:blank", "failure": true @@ -4649,6 +4635,68 @@ "search": "", "hash": "" }, + "Forbidden host code points", + { + "input": "http://ab", + "base": "about:blank", + "failure": true + }, + { + "input": "http://a^b", + "base": "about:blank", + "failure": true + }, + { + "input": "non-special://ab", + "base": "about:blank", + "failure": true + }, + { + "input": "non-special://a^b", + "base": "about:blank", + "failure": true + }, + "Allowed host code points", + { + "input": "http://\u001F!\"$&'()*+,-.;=_`{|}~/", + "base": "about:blank", + "href": "http://\u001F!\"$&'()*+,-.;=_`{|}~/", + "origin": "http://\u001F!\"$&'()*+,-.;=_`{|}~", + "protocol": "http:", + "username": "", + "password": "", + "host": "\u001F!\"$&'()*+,-.;=_`{|}~", + "hostname": "\u001F!\"$&'()*+,-.;=_`{|}~", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "sc://\u001F!\"$&'()*+,-.;=_`{|}~/", + "base": "about:blank", + "href": "sc://%1F!\"$&'()*+,-.;=_`{|}~/", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%1F!\"$&'()*+,-.;=_`{|}~", + "hostname": "%1F!\"$&'()*+,-.;=_`{|}~", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, "# Hosts and percent-encoding", { "input": "ftp://example.com%80/", @@ -5863,6 +5911,130 @@ "search": "", "hash": "#frag" }, + "# file: drive letter cases from https://crbug.com/1078698", + { + "input": "file:///Y:", + "base": "about:blank", + "href": "file:///Y:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/Y:", + "search": "", + "hash": "" + }, + { + "input": "file:///Y:/", + "base": "about:blank", + "href": "file:///Y:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/Y:/", + "search": "", + "hash": "" + }, + { + "input": "file:///./Y", + "base": "about:blank", + "href": "file:///Y", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/Y", + "search": "", + "hash": "" + }, + { + "input": "file:///./Y:", + "base": "about:blank", + "href": "file:///Y:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/Y:", + "search": "", + "hash": "" + }, + { + "input": "\\\\\\.\\Y:", + "base": "about:blank", + "failure": true + }, + "# file: drive letter cases from https://crbug.com/1078698 but lowercased", + { + "input": "file:///y:", + "base": "about:blank", + "href": "file:///y:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/y:", + "search": "", + "hash": "" + }, + { + "input": "file:///y:/", + "base": "about:blank", + "href": "file:///y:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/y:/", + "search": "", + "hash": "" + }, + { + "input": "file:///./y", + "base": "about:blank", + "href": "file:///y", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/y", + "search": "", + "hash": "" + }, + { + "input": "file:///./y:", + "base": "about:blank", + "href": "file:///y:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/y:", + "search": "", + "hash": "" + }, + { + "input": "\\\\\\.\\y:", + "base": "about:blank", + "failure": true + }, "# IPv6 tests", { "input": "http://[1:0::]", @@ -6672,7 +6844,7 @@ { "input": "http://example.org/test?a#b\u0000c", "base": "about:blank", - "href": "http://example.org/test?a#bc", + "href": "http://example.org/test?a#b%00c", "protocol": "http:", "username": "", "password": "", @@ -6681,6 +6853,139 @@ "port": "", "pathname": "/test", "search": "?a", - "hash": "#bc" + "hash": "#b%00c" + }, + { + "input": "non-spec://example.org/test?a#b\u0000c", + "base": "about:blank", + "href": "non-spec://example.org/test?a#b%00c", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#b%00c" + }, + { + "input": "non-spec:/test?a#b\u0000c", + "base": "about:blank", + "href": "non-spec:/test?a#b%00c", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#b%00c" + }, + "First scheme char - not allowed: https://github.com/whatwg/url/issues/464", + { + "input": "10.0.0.7:8080/foo.html", + "base": "file:///some/dir/bar.html", + "href": "file:///some/dir/10.0.0.7:8080/foo.html", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/some/dir/10.0.0.7:8080/foo.html", + "search": "", + "hash": "" + }, + "Subsequent scheme chars - not allowed", + { + "input": "a!@$*=/foo.html", + "base": "file:///some/dir/bar.html", + "href": "file:///some/dir/a!@$*=/foo.html", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/some/dir/a!@$*=/foo.html", + "search": "", + "hash": "" + }, + "First and subsequent scheme chars - allowed", + { + "input": "a1234567890-+.:foo/bar", + "base": "http://example.com/dir/file", + "href": "a1234567890-+.:foo/bar", + "protocol": "a1234567890-+.:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "foo/bar", + "search": "", + "hash": "" + }, + "IDNA ignored code points in file URLs hosts", + { + "input": "file://a\u00ADb/p", + "base": "about:blank", + "href": "file://ab/p", + "protocol": "file:", + "username": "", + "password": "", + "host": "ab", + "hostname": "ab", + "port": "", + "pathname": "/p", + "search": "", + "hash": "" + }, + { + "input": "file://a%C2%ADb/p", + "base": "about:blank", + "href": "file://ab/p", + "protocol": "file:", + "username": "", + "password": "", + "host": "ab", + "hostname": "ab", + "port": "", + "pathname": "/p", + "search": "", + "hash": "" + }, + "Empty host after the domain to ASCII", + { + "input": "file://\u00ad/p", + "base": "about:blank", + "failure": true + }, + { + "input": "file://%C2%AD/p", + "base": "about:blank", + "failure": true + }, + { + "input": "file://xn--/p", + "base": "about:blank", + "failure": true + }, + "https://bugzilla.mozilla.org/show_bug.cgi?id=1647058", + { + "input": "#link", + "base": "https://example.org/##link", + "href": "https://example.org/#link", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/", + "search": "", + "hash": "#link" } ] diff -Nru rust-url-2.1.1/.travis.yml rust-url-2.2.2/.travis.yml --- rust-url-2.1.1/.travis.yml 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/.travis.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ -language: rust -script: cargo test --all-features --all - -jobs: - include: - - rust: 1.36.0 - - rust: stable - - rust: beta - - rust: nightly - - rust: nightly - env: TARGET=WASM32 # For job list UI - install: rustup target add wasm32-unknown-unknown - script: cargo build --all --target=wasm32-unknown-unknown diff -Nru rust-url-2.1.1/UPGRADING.md rust-url-2.2.2/UPGRADING.md --- rust-url-2.1.1/UPGRADING.md 2020-01-09 10:15:31.000000000 +0000 +++ rust-url-2.2.2/UPGRADING.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,367 +0,0 @@ -# Upgrade guide - -This guide contains steps for upgrading crates in this project between major -versions. - -## Upgrading from url 1.x to 2.1+ - -* The minimum supported Rust version is now v1.33.0. Verify that you can bump - your library or application to the same MSRV. - -* `Url` no longer implements `std::net::ToSocketAddrs`. You will instead need to - explicitly call `socket_addrs` to convert your `Url` to a type that implements - `ToSocketAddrs`. - - Note that v2.0 removed support for `std::net::ToSocketAddrs` with no - replacement; the `socket_addrs` method was not added until v2.1. - - Before upgrading: - - ```rust - let url = Url::parse("http://github.com:80").unwrap(); - let stream = TcpStream::connect(url).unwrap(); - ``` - - After upgrading: - - ```rust - let url = Url::parse("http://github.com:80").unwrap(); - let addrs = url.socket_addrs(|| None).unwrap(); - let stream = TcpStream::connect(addrs).unwrap(); - ``` - - Before upgrading: - - ```rust - let url = Url::parse("socks5://localhost").unwrap(); - let stream = TcpStream::connect(url.with_default_port(|url| match url.scheme() { - "socks5" => Ok(1080), - _ => Err(()), - })).unwrap(); - ``` - - After upgrading: - - ```rust - let url = Url::parse("http://github.com:80").unwrap(); - let stream = TcpStream::connect(url.socket_addrs(|| match url.scheme() { - "socks5" => Some(1080), - _ => Err(()), - })).unwrap(); - ``` - -* `url_serde` is no longer required to use `Url` with Serde 1.x. Remove - references to `url_serde` and enable the `serde` feature instead. - - ```toml - # Cargo.toml - [dependencies] - url = { version = "2.0", features = ["serde"] } - ``` - -* The `idna` and `percent_export` crates are no longer exported by the `url` - crate. Depend on those crates directly instead. See below for additional - breaking changes in the percent-export package. - - Before upgrading: - - ```rust - use url::percent_encoding::percent_decode; - ``` - - After upgrading: - - ```rust - use percent_encoding::percent_decode; - ``` - -## Upgrading from percent-encoding 1.x to 2.x - -* Prepackaged encoding sets, like `QUERY_ENCODE_SET` and - `PATH_SEGMENT_ENCODE_SET`, are no longer provided. You - will need to read the specifications relevant to your domain and construct - your own encoding sets by using the `percent_encoding::AsciiSet` builder - methods on either of the base encoding sets, `percent_encoding::CONTROLS` or - `percent_encoding::NON_ALPHANUMERIC`. - - Before upgrading: - - ```rust - use percent_encoding::QUERY_ENCODE_SET; - - percent_encoding::utf8_percent_encode(value, QUERY_ENCODE_SET); - ``` - - After upgrading: - - ```rust - /// https://url.spec.whatwg.org/#query-state - const QUERY: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'#').add(b'<').add(b'>'); - - percent_encoding::utf8_percent_encode(value, QUERY); - ``` - - -## Upgrading from url 0.x to 1.x - -* The fields of `Url` are now private because the `Url` constructor, parser, - and setters maintain invariants that could be violated if you were to set the fields directly. - Instead of accessing, for example, `url.scheme`, use the getter method, such as `url.scheme()`. - Instead of assigning directly to a field, for example `url.scheme = "https".to_string()`, - use the setter method, such as `url.set_scheme("https").unwrap()`. - (Some setters validate the new value and return a `Result` that must be used). - -* The methods of `Url` now return `&str` instead of `String`, - thus reducing allocations and making serialization cheap. - -* The `path()` method on `url::Url` instances used to return `Option<&[String]>`; - now it returns `&str`. - If you would like functionality more similar to the old behavior of `path()`, - use `path_segments()` that returns `Option>`. - - Before upgrading: - - ```rust - let issue_list_url = Url::parse( - "https://github.com/rust-lang/rust/issues?labels=E-easy&state=open" - ).unwrap(); - assert_eq!(issue_list_url.path(), Some(&["rust-lang".to_string(), - "rust".to_string(), - "issues".to_string()][..])); - ``` - - After upgrading: - - ```rust - let issue_list_url = Url::parse( - "https://github.com/rust-lang/rust/issues?labels=E-easy&state=open" - ).unwrap(); - assert_eq!(issue_list_url.path(), "/rust-lang/rust/issues"); - assert_eq!(issue_list_url.path_segments().map(|c| c.collect::>()), - Some(vec!["rust-lang", "rust", "issues"])); - ``` - -* The `path_mut()` method on `url::Url` instances that allowed modification of a URL's path - has been replaced by `path_segments_mut()`. - - Before upgrading: - - ```rust - let mut url = Url::parse("https://github.com/rust-lang/rust").unwrap(); - url.path_mut().unwrap().push("issues"); - ``` - - After upgrading: - - ```rust - let mut url = Url::parse("https://github.com/rust-lang/rust").unwrap(); - url.path_segments_mut().unwrap().push("issues"); - ``` - -* The `domain_mut()` method on `url::Url` instances that allowed modification of a URL's domain - has been replaced by `set_host()` and `set_ip_host()`. - -* The `host()` method on `url::Url` instances used to return `Option<&Host>`; - now it returns `Option>`. - The `serialize_host()` method that returned `Option` - has been replaced by the `host_str()` method that returns `Option<&str>`. - -* The `serialize()` method on `url::Url` instances that returned `String` - has been replaced by an `as_str()` method that returns `&str`. - - Before upgrading: - - ```rust - let this_document = Url::parse("http://servo.github.io/rust-url/url/index.html").unwrap(); - assert_eq!(this_document.serialize(), "http://servo.github.io/rust-url/url/index.html".to_string()); - ``` - - After upgrading: - - ```rust - let this_document = Url::parse("http://servo.github.io/rust-url/url/index.html").unwrap(); - assert_eq!(this_document.as_str(), "http://servo.github.io/rust-url/url/index.html"); - ``` - -* `url::UrlParser` has been replaced by `url::Url::parse()` and `url::Url::join()`. - - Before upgrading: - - ```rust - let this_document = Url::parse("http://servo.github.io/rust-url/url/index.html").unwrap(); - let css_url = UrlParser::new().base_url(&this_document).parse("../main.css").unwrap(); - assert_eq!(css_url.serialize(), "http://servo.github.io/rust-url/main.css".to_string()); - ``` - - After upgrading: - - ```rust - let this_document = Url::parse("http://servo.github.io/rust-url/url/index.html").unwrap(); - let css_url = this_document.join("../main.css").unwrap(); - assert_eq!(css_url.as_str(), "http://servo.github.io/rust-url/main.css"); - ``` - -* `url::parse_path()` and `url::UrlParser::parse_path()` have been removed without replacement. - As a workaround, you can give a base URL that you then ignore too `url::Url::parse()`. - - Before upgrading: - - ```rust - let (path, query, fragment) = url::parse_path("/foo/bar/../baz?q=42").unwrap(); - assert_eq!(path, vec!["foo".to_string(), "baz".to_string()]); - assert_eq!(query, Some("q=42".to_string())); - assert_eq!(fragment, None); - ``` - - After upgrading: - - ```rust - let base = Url::parse("http://example.com").unwrap(); - let with_path = base.join("/foo/bar/../baz?q=42").unwrap(); - assert_eq!(with_path.path(), "/foo/baz"); - assert_eq!(with_path.query(), Some("q=42")); - assert_eq!(with_path.fragment(), None); - ``` - -* The `url::form_urlencoded::serialize()` method - has been replaced with the `url::form_urlencoded::Serializer` struct. - Instead of calling `serialize()` with key/value pairs, - create a new `Serializer` with a new string, - call the `extend_pairs()` method on the `Serializer` instance with the key/value pairs as the argument, - then call `finish()`. - - Before upgrading: - - ```rust - let form = url::form_urlencoded::serialize(form.iter().map(|(k, v)| { - (&k[..], &v[..]) - })); - ``` - - After upgrading: - - ```rust - let form = url::form_urlencoded::Serializer::new(String::new()).extend_pairs( - form.iter().map(|(k, v)| { (&k[..], &v[..]) }) - ).finish(); - ``` - -* The `set_query_from_pairs()` method on `url::Url` instances that took key/value pairs - has been replaced with `query_pairs_mut()`, which allows you to modify the `url::Url`'s query pairs. - - Before upgrading: - - ```rust - let mut url = Url::parse("https://duckduckgo.com/").unwrap(); - let pairs = vec![ - ("q", "test"), - ("ia", "images"), - ]; - url.set_query_from_pairs(pairs.iter().map(|&(k, v)| { - (&k[..], &v[..]) - })); - ``` - - After upgrading: - - ```rust - let mut url = Url::parse("https://duckduckgo.com/").unwrap(); - let pairs = vec![ - ("q", "test"), - ("ia", "images"), - ]; - url.query_pairs_mut().clear().extend_pairs( - pairs.iter().map(|&(k, v)| { (&k[..], &v[..]) }) - ); - ``` - -* `url::SchemeData`, its variants `Relative` and `NonRelative`, - and the struct `url::RelativeSchemeData` have been removed. - Instead of matching on these variants - to determine if you have a URL in a relative scheme such as HTTP - versus a URL in a non-relative scheme as data, - use the `cannot_be_a_base()` method to determine which kind you have. - - Before upgrading: - - ```rust - match url.scheme_data { - url::SchemeData::Relative(..) => {} - url::SchemeData::NonRelative(..) => { - return Err(human(format!("`{}` must have relative scheme \ - data: {}", field, url))) - } - } - ``` - - After upgrading: - - ```rust - if url.cannot_be_a_base() { - return Err(human(format!("`{}` must have relative scheme \ - data: {}", field, url))) - } - ``` - -* The functions `url::whatwg_scheme_type_mapper()`, the `SchemeType` enum, - and the `scheme_type_mapper()` method on `url::UrlParser` instances have been removed. - `SchemeType` had a method for getting the `default_port()`; - to replicate this functionality, use the method `port_or_known_default()` on `url::Url` instances. - The `port_or_default()` method on `url::Url` instances has been removed; - use `port_or_known_default()` instead. - - Before upgrading: - - ```rust - let port = match whatwg_scheme_type_mapper(&url.scheme) { - SchemeType::Relative(port) => port, - _ => return Err(format!("Invalid special scheme: `{}`", - raw_url.scheme)), - }; - ``` - - After upgrading: - - ```rust - let port = match url.port_or_known_default() { - Some(port) => port, - _ => return Err(format!("Invalid special scheme: `{}`", - url.scheme())), - }; - ``` - -* The following formatting utilities have been removed without replacement; - look at their linked previous implementations - if you would like to replicate the functionality in your code: - * [`url::format::PathFormatter`](https://github.com/servo/rust-url/pull/176/commits/9e759f18726c8e1343162922b87163d4dd08fe3c#diff-0bb16ac13b75e9b568fa4aff61b0e71dL24) - * [`url::format::UserInfoFormatter`](https://github.com/servo/rust-url/pull/176/commits/9e759f18726c8e1343162922b87163d4dd08fe3c#diff-0bb16ac13b75e9b568fa4aff61b0e71dL50) - * [`url::format::UrlNoFragmentFormatter`](https://github.com/servo/rust-url/pull/176/commits/9e759f18726c8e1343162922b87163d4dd08fe3c#diff-0bb16ac13b75e9b568fa4aff61b0e71dL70) - -* `url::percent_encoding::percent_decode()` used to have a return type of `Vec`; - now it returns an iterator of decoded `u8` bytes that also implements `Into>`. - Use `.into().to_owned()` to obtain a `Vec`. - (`.collect()` also works but might not be as efficient.) - -* The `url::percent_encoding::EncodeSet` struct and constant instances - used with `url::percent_encoding::percent_encode()` - have been changed to structs that implement the trait `url::percent_encoding::EncodeSet`. - * `SIMPLE_ENCODE_SET`, `QUERY_ENCODE_SET`, `DEFAULT_ENCODE_SET`, - and `USERINFO_ENCODE_SET` have the same behavior. - * `USERNAME_ENCODE_SET` and `PASSWORD_ENCODE_SET` have been removed; - use `USERINFO_ENCODE_SET` instead. - * `HTTP_VALUE_ENCODE_SET` has been removed; - an implementation of it in the new types can be found [in hyper's source]( - https://github.com/hyperium/hyper/blob/67436c5bf615cf5a55a71e32b788afef5985570e/src/header/parsing.rs#L131-L138) - if you need to replicate this functionality in your code. - * `FORM_URLENCODED_ENCODE_SET` has been removed; - instead, use the functionality in `url::form_urlencoded`. - * `PATH_SEGMENT_ENCODE_SET` has been added for use on '/'-separated path segments. - -* `url::percent_encoding::percent_decode_to()` has been removed. - Use `url::percent_encoding::percent_decode()` which returns an iterator. - You can then use the iterator’s `collect()` method - or give it to some data structure’s `extend()` method. -* A number of `ParseError` variants have changed. - [See the documentation for the current set](http://servo.github.io/rust-url/url/enum.ParseError.html). -* `url::OpaqueOrigin::new()` and `url::Origin::UID(OpaqueOrigin)` - have been replaced by `url::Origin::new_opaque()` and `url::Origin::Opaque(OpaqueOrigin)`, respectively.