aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Vander Stoep <jeffv@google.com>2021-05-07 17:07:20 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-05-07 17:07:20 +0000
commit6099cd2db6bc59f39c880e7eab64fd7fb0baab9c (patch)
tree1a6ded2c2ce9ce49062f522b36b4915644506edb
parent288c2c63c01613336f6f6573a03b5899f51cf299 (diff)
parent3a826f53a618e1632a22ca7f1bc6c495a9c2ecc5 (diff)
downloadurl-android12-qpr3-s6-release.tar.gz
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/url/+/1700025 Change-Id: Ib37a1fc94e92473aaa93dc238122ee4e18179374
-rw-r--r--.cargo_vcs_info.json2
-rw-r--r--Cargo.toml2
-rw-r--r--Cargo.toml.orig2
-rw-r--r--METADATA8
-rw-r--r--TEST_MAPPING11
-rw-r--r--src/lib.rs166
-rw-r--r--src/quirks.rs12
-rw-r--r--tests/unit.rs439
8 files changed, 613 insertions, 29 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index da07783..fb5f61f 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,5 +1,5 @@
{
"git": {
- "sha1": "7bb95a183d0b236fa756dbd1e0f0ee210bbd2b35"
+ "sha1": "6c22912c313064a8c5a6fa043882c9ad55dba162"
}
}
diff --git a/Cargo.toml b/Cargo.toml
index 6e0b2ba..108b149 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@
[package]
edition = "2018"
name = "url"
-version = "2.2.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"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index 5f7a0fe..e384659 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -2,7 +2,7 @@
name = "url"
# When updating version, also modify html_root_url in the lib.rs
-version = "2.2.1"
+version = "2.2.2"
authors = ["The rust-url developers"]
description = "URL library for Rust, based on the WHATWG URL Standard"
diff --git a/METADATA b/METADATA
index 14ae2a5..742a22a 100644
--- a/METADATA
+++ b/METADATA
@@ -7,13 +7,13 @@ third_party {
}
url {
type: ARCHIVE
- value: "https://static.crates.io/crates/url/url-2.2.1.crate"
+ value: "https://static.crates.io/crates/url/url-2.2.2.crate"
}
- version: "2.2.1"
+ version: "2.2.2"
license_type: NOTICE
last_upgrade_date {
year: 2021
- month: 2
- day: 18
+ month: 5
+ day: 7
}
}
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 39f2e10..be39865 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -2,16 +2,19 @@
{
"presubmit": [
{
- "name": "url_device_test_tests_unit"
- },
- {
- "name": "url_device_test_tests_data"
+ "name": "doh_unit_test"
},
{
"name": "quiche_device_test_src_lib"
},
{
"name": "url_device_test_src_lib"
+ },
+ {
+ "name": "url_device_test_tests_data"
+ },
+ {
+ "name": "url_device_test_tests_unit"
}
]
}
diff --git a/src/lib.rs b/src/lib.rs
index afe511e..42793cf 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -120,7 +120,7 @@ url = { version = "2", features = ["serde"] }
```
*/
-#![doc(html_root_url = "https://docs.rs/url/2.2.1")]
+#![doc(html_root_url = "https://docs.rs/url/2.2.2")]
#[macro_use]
extern crate matches;
@@ -320,6 +320,8 @@ impl Url {
/// 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:
@@ -349,11 +351,144 @@ impl Url {
/// 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<Url, crate::ParseError> {
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<String> {
+ 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
@@ -417,14 +552,15 @@ impl Url {
/// # 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<String>")]
pub fn into_string(self) -> String {
- self.serialization
+ self.into()
}
/// For internal testing, not part of the public API.
@@ -1433,7 +1569,7 @@ impl Url {
/// Return an object with methods to manipulate this URL’s path segments.
///
/// Return `Err(())` if this URL is cannot-be-a-base.
- #[allow(clippy::clippy::result_unit_err)]
+ #[allow(clippy::result_unit_err)]
pub fn path_segments_mut(&mut self) -> Result<PathSegmentsMut<'_>, ()> {
if self.cannot_be_a_base() {
Err(())
@@ -1517,7 +1653,7 @@ impl Url {
/// # }
/// # run().unwrap();
/// ```
- #[allow(clippy::clippy::result_unit_err)]
+ #[allow(clippy::result_unit_err)]
pub fn set_port(&mut self, mut port: Option<u16>) -> Result<(), ()> {
// has_host implies !cannot_be_a_base
if !self.has_host() || self.host() == Some(Host::Domain("")) || self.scheme() == "file" {
@@ -1788,7 +1924,7 @@ impl Url {
/// # run().unwrap();
/// ```
///
- #[allow(clippy::clippy::result_unit_err)]
+ #[allow(clippy::result_unit_err)]
pub fn set_ip_host(&mut self, address: IpAddr) -> Result<(), ()> {
if self.cannot_be_a_base() {
return Err(());
@@ -1828,7 +1964,7 @@ impl Url {
/// # }
/// # run().unwrap();
/// ```
- #[allow(clippy::clippy::result_unit_err)]
+ #[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" {
@@ -1921,7 +2057,7 @@ impl Url {
/// # }
/// # run().unwrap();
/// ```
- #[allow(clippy::clippy::result_unit_err)]
+ #[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" {
@@ -2163,7 +2299,7 @@ impl Url {
/// # }
/// ```
#[cfg(any(unix, windows, target_os = "redox"))]
- #[allow(clippy::clippy::result_unit_err)]
+ #[allow(clippy::result_unit_err)]
pub fn from_file_path<P: AsRef<Path>>(path: P) -> Result<Url, ()> {
let mut serialization = "file://".to_owned();
let host_start = serialization.len() as u32;
@@ -2200,7 +2336,7 @@ impl Url {
/// 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::clippy::result_unit_err)]
+ #[allow(clippy::result_unit_err)]
pub fn from_directory_path<P: AsRef<Path>>(path: P) -> Result<Url, ()> {
let mut url = Url::from_file_path(path)?;
if !url.serialization.ends_with('/') {
@@ -2317,7 +2453,7 @@ impl Url {
/// for a Windows path, is not UTF-8.)
#[inline]
#[cfg(any(unix, windows, target_os = "redox"))]
- #[allow(clippy::clippy::result_unit_err)]
+ #[allow(clippy::result_unit_err)]
pub fn to_file_path(&self) -> Result<PathBuf, ()> {
if let Some(segments) = self.path_segments() {
let host = match self.host() {
@@ -2375,6 +2511,13 @@ impl fmt::Display for Url {
}
}
+/// String converstion.
+impl From<Url> for String {
+ fn from(value: Url) -> String {
+ value.serialization
+ }
+}
+
/// Debug the serialization of this URL.
impl fmt::Debug for Url {
#[inline]
@@ -2382,6 +2525,7 @@ impl fmt::Debug for Url {
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())
diff --git a/src/quirks.rs b/src/quirks.rs
index 72dadaa..0dbc6eb 100644
--- a/src/quirks.rs
+++ b/src/quirks.rs
@@ -56,7 +56,7 @@ pub fn protocol(url: &Url) -> &str {
}
/// Setter for https://url.spec.whatwg.org/#dom-url-protocol
-#[allow(clippy::clippy::result_unit_err)]
+#[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.
@@ -73,7 +73,7 @@ pub fn username(url: &Url) -> &str {
}
/// Setter for https://url.spec.whatwg.org/#dom-url-username
-#[allow(clippy::clippy::result_unit_err)]
+#[allow(clippy::result_unit_err)]
pub fn set_username(url: &mut Url, new_username: &str) -> Result<(), ()> {
url.set_username(new_username)
}
@@ -85,7 +85,7 @@ pub fn password(url: &Url) -> &str {
}
/// Setter for https://url.spec.whatwg.org/#dom-url-password
-#[allow(clippy::clippy::result_unit_err)]
+#[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
@@ -101,7 +101,7 @@ pub fn host(url: &Url) -> &str {
}
/// Setter for https://url.spec.whatwg.org/#dom-url-host
-#[allow(clippy::clippy::result_unit_err)]
+#[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() {
@@ -158,7 +158,7 @@ pub fn hostname(url: &Url) -> &str {
}
/// Setter for https://url.spec.whatwg.org/#dom-url-hostname
-#[allow(clippy::clippy::result_unit_err)]
+#[allow(clippy::result_unit_err)]
pub fn set_hostname(url: &mut Url, new_hostname: &str) -> Result<(), ()> {
if url.cannot_be_a_base() {
return Err(());
@@ -200,7 +200,7 @@ pub fn port(url: &Url) -> &str {
}
/// Setter for https://url.spec.whatwg.org/#dom-url-port
-#[allow(clippy::clippy::result_unit_err)]
+#[allow(clippy::result_unit_err)]
pub fn set_port(url: &mut Url, new_port: &str) -> Result<(), ()> {
let result;
{
diff --git a/tests/unit.rs b/tests/unit.rs
index 4c25198..13055a4 100644
--- a/tests/unit.rs
+++ b/tests/unit.rs
@@ -12,7 +12,7 @@ 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() {
@@ -518,6 +518,209 @@ fn test_origin_hash() {
}
#[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;
@@ -582,6 +785,38 @@ fn test_syntax_violation_callback_lifetimes() {
}
#[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());
@@ -679,3 +914,205 @@ fn pop_if_empty_in_bounds() {
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);
+ }
+}