зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1744668 - Update url crate to v2.2.2. r=markh
The upcoming update of viaduct wants at least v2.2. Use Into<String> instead of Url::into_string, the latter is deprecated and causes a warning we turn into an error. Differential Revision: https://phabricator.services.mozilla.com/D133024
This commit is contained in:
Родитель
ff66cb8dea
Коммит
dacc08f158
|
@ -1624,6 +1624,16 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "freetype"
|
||||
version = "0.7.0"
|
||||
|
@ -5325,10 +5335,11 @@ checksum = "2ace0b4755d0a2959962769239d56267f8a024fef2d9b32666b3dcd0946b0906"
|
|||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.1.0"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61"
|
||||
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"files":{"Cargo.toml":"aadc4e4ba33e86861d8d1d8b848ac11a27b6f87340d082b47f762387464c61ed","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e","src/lib.rs":"5d30edec687843447c97e4ea87583983eb9fc06135ae718c8ecc0fa8cebef2df"},"package":"5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"}
|
|
@ -0,0 +1,28 @@
|
|||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies
|
||||
#
|
||||
# If you believe there's an error in this file please file an
|
||||
# issue against the rust-lang/cargo repository. If you're
|
||||
# editing this file be aware that the upstream Cargo.toml
|
||||
# will likely look very different (and much more reasonable)
|
||||
|
||||
[package]
|
||||
edition = "2018"
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.1"
|
||||
authors = ["The rust-url developers"]
|
||||
description = "Parser and serializer for the application/x-www-form-urlencoded syntax, as used by HTML forms."
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/servo/rust-url"
|
||||
|
||||
[lib]
|
||||
test = false
|
||||
[dependencies.matches]
|
||||
version = "0.1"
|
||||
|
||||
[dependencies.percent-encoding]
|
||||
version = "2.1.0"
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,25 @@
|
|||
Copyright (c) 2013-2016 The rust-url developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
|
@ -13,8 +13,10 @@
|
|||
//! Converts between a string (such as an URL’s query string)
|
||||
//! and a sequence of (name, value) pairs.
|
||||
|
||||
#[macro_use]
|
||||
extern crate matches;
|
||||
|
||||
use percent_encoding::{percent_decode, percent_encode_byte};
|
||||
use query_encoding::{self, decode_utf8_lossy, EncodingOverride};
|
||||
use std::borrow::{Borrow, Cow};
|
||||
use std::str;
|
||||
|
||||
|
@ -26,7 +28,7 @@ use std::str;
|
|||
/// 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 {
|
||||
pub fn parse(input: &[u8]) -> Parse<'_> {
|
||||
Parse { input }
|
||||
}
|
||||
/// The return type of `parse()`.
|
||||
|
@ -57,7 +59,7 @@ impl<'a> Iterator for Parse<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn decode(input: &[u8]) -> Cow<str> {
|
||||
fn decode(input: &[u8]) -> Cow<'_, str> {
|
||||
let replaced = replace_plus(input);
|
||||
decode_utf8_lossy(match percent_decode(&replaced).into() {
|
||||
Cow::Owned(vec) => Cow::Owned(vec),
|
||||
|
@ -66,7 +68,7 @@ fn decode(input: &[u8]) -> Cow<str> {
|
|||
}
|
||||
|
||||
/// Replace b'+' with b' '
|
||||
fn replace_plus(input: &[u8]) -> Cow<[u8]> {
|
||||
fn replace_plus(input: &[u8]) -> Cow<'_, [u8]> {
|
||||
match input.iter().position(|&b| b == b'+') {
|
||||
None => Cow::Borrowed(input),
|
||||
Some(first_position) => {
|
||||
|
@ -108,7 +110,7 @@ impl<'a> Iterator for ParseIntoOwned<'a> {
|
|||
/// https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer).
|
||||
///
|
||||
/// Return an iterator of `&str` slices.
|
||||
pub fn byte_serialize(input: &[u8]) -> ByteSerialize {
|
||||
pub fn byte_serialize(input: &[u8]) -> ByteSerialize<'_> {
|
||||
ByteSerialize { bytes: input }
|
||||
}
|
||||
|
||||
|
@ -142,6 +144,10 @@ impl<'a> Iterator for ByteSerialize<'a> {
|
|||
None => (self.bytes, &[][..]),
|
||||
};
|
||||
self.bytes = remaining;
|
||||
// This unsafe is appropriate because we have already checked these
|
||||
// bytes in byte_serialized_unchanged, which checks for a subset
|
||||
// of UTF-8. So we know these bytes are valid UTF-8, and doing
|
||||
// another UTF-8 check would be wasteful.
|
||||
Some(unsafe { str::from_utf8_unchecked(unchanged_slice) })
|
||||
} else {
|
||||
None
|
||||
|
@ -191,30 +197,6 @@ impl<'a> Target for &'a mut String {
|
|||
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<UrlQuery>`
|
||||
// * `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.
|
||||
///
|
||||
|
@ -230,7 +212,14 @@ impl<'a, T: Target> Serializer<'a, T> {
|
|||
/// 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
|
||||
if target.as_mut_string().len() < start_position {
|
||||
panic!(
|
||||
"invalid length {} for target of length {}",
|
||||
start_position,
|
||||
target.as_mut_string().len()
|
||||
);
|
||||
}
|
||||
|
||||
Serializer {
|
||||
target: Some(target),
|
||||
start_position,
|
||||
|
@ -266,6 +255,19 @@ impl<'a, T: Target> Serializer<'a, T> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Serialize and append a name of parameter without any value.
|
||||
///
|
||||
/// Panics if called after `.finish()`.
|
||||
pub fn append_key_only(&mut self, name: &str) -> &mut Self {
|
||||
append_key_only(
|
||||
string(&mut self.target),
|
||||
self.start_position,
|
||||
self.encoding,
|
||||
name,
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
/// Serialize and append a number of name/value pairs.
|
||||
///
|
||||
/// This simply calls `append_pair` repeatedly.
|
||||
|
@ -296,10 +298,33 @@ impl<'a, T: Target> Serializer<'a, T> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Serialize and append a number of names without values.
|
||||
///
|
||||
/// This simply calls `append_key_only` 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_keys_only<I, K>(&mut self, iter: I) -> &mut Self
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Borrow<K>,
|
||||
K: AsRef<str>,
|
||||
{
|
||||
{
|
||||
let string = string(&mut self.target);
|
||||
for key in iter {
|
||||
let k = key.borrow().as_ref();
|
||||
append_key_only(string, self.start_position, self.encoding, k);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// If this serializer was constructed with a string, take and return that string.
|
||||
///
|
||||
/// ```rust
|
||||
/// use url::form_urlencoded;
|
||||
/// use form_urlencoded;
|
||||
/// let encoded: String = form_urlencoded::Serializer::new(String::new())
|
||||
/// .append_pair("foo", "bar & baz")
|
||||
/// .append_pair("saison", "Été+hiver")
|
||||
|
@ -332,7 +357,7 @@ fn string<T: Target>(target: &mut Option<T>) -> &mut String {
|
|||
fn append_pair(
|
||||
string: &mut String,
|
||||
start_position: usize,
|
||||
encoding: EncodingOverride,
|
||||
encoding: EncodingOverride<'_>,
|
||||
name: &str,
|
||||
value: &str,
|
||||
) {
|
||||
|
@ -342,6 +367,54 @@ fn append_pair(
|
|||
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())))
|
||||
fn append_key_only(
|
||||
string: &mut String,
|
||||
start_position: usize,
|
||||
encoding: EncodingOverride,
|
||||
name: &str,
|
||||
) {
|
||||
append_separator_if_needed(string, start_position);
|
||||
append_encoded(name, string, encoding);
|
||||
}
|
||||
|
||||
fn append_encoded(s: &str, string: &mut String, encoding: EncodingOverride<'_>) {
|
||||
string.extend(byte_serialize(&encode(encoding, s)))
|
||||
}
|
||||
|
||||
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<'_, str> {
|
||||
// Note: This function is duplicated in `percent_encoding/lib.rs`.
|
||||
match input {
|
||||
Cow::Borrowed(bytes) => String::from_utf8_lossy(bytes),
|
||||
Cow::Owned(bytes) => {
|
||||
match String::from_utf8_lossy(&bytes) {
|
||||
Cow::Borrowed(utf8) => {
|
||||
// If from_utf8_lossy returns a Cow::Borrowed, then we can
|
||||
// be sure our original bytes were valid UTF-8. This is because
|
||||
// if the bytes were invalid UTF-8 from_utf8_lossy would have
|
||||
// to allocate a new owned string to back the Cow so it could
|
||||
// replace invalid bytes with a placeholder.
|
||||
|
||||
// First we do a debug_assert to confirm our description above.
|
||||
let raw_utf8: *const [u8];
|
||||
raw_utf8 = utf8.as_bytes();
|
||||
debug_assert!(raw_utf8 == &*bytes as *const [u8]);
|
||||
|
||||
// Given we know the original input bytes are valid UTF-8,
|
||||
// and we have ownership of those bytes, we re-use them and
|
||||
// return a Cow::Owned here.
|
||||
Cow::Owned(unsafe { String::from_utf8_unchecked(bytes) })
|
||||
}
|
||||
Cow::Owned(s) => Cow::Owned(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type EncodingOverride<'a> = Option<&'a dyn Fn(&str) -> Cow<'_, [u8]>>;
|
|
@ -1 +1 @@
|
|||
{"files":{"Cargo.toml":"911d5d7758567d81f098c351b005ca2b9a9963dce59b2e4c9601990b23d32bc1","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e","README.md":"8f7f1699e885e867f16e2beacf161751735582fbc080ada0762ba512244f28fc","UPGRADING.md":"fbcc2d39bdf17db0745793db6626fcd5c909dddd4ce13b27566cfabece22c368","appveyor.yml":"c78486dbfbe6ebbf3d808afb9a19f7ec18c4704ce451c6305f0716999b70a1a6","benches/parse_url.rs":"821ecb051c3c6c40eb3b268ba7337b2988333627d0af0c8e1afc84734ffbbf2b","src/form_urlencoded.rs":"97b948b959460ef1323bdbb9147eef3edac9db2e236f75cb49668cbab8d1f708","src/host.rs":"8401138bbda58771e0377e5e45b695844a1ad3320584115d931e933c2097e4d1","src/lib.rs":"f822ca47da43bb283af3b590d541321ff1ec828045e12a8d97fb688e7e3f8610","src/origin.rs":"5ee6d1dc360a191362e2bee9840bdc26df8f20cb1b70282024cebd3e14490e92","src/parser.rs":"912733b8f62bf765e077dde2bc21918bef39f6f41d262059cd1738f3d6a8824c","src/path_segments.rs":"c322c048a075db47dd73289e83876cbb25b69a3b17cad6054ddb47a93fab85dd","src/query_encoding.rs":"88d31936327461af1382393117fc07bbdf064c6930aaff3cd8b38d2343e41b51","src/quirks.rs":"a5be1ade22b29e86b432d0340cc5737614b28c7db0df36d7c1b6ea84e60e3c83","src/slicing.rs":"a59ec10a3c3a6b454f66014ca7fd949ea48159a13b100fca2712c784021ccdc3","tests/data.rs":"3c8c1255d86c1d1cfb17d4c979c18dd13d4f7386512b95be9a47dd755502fe68","tests/setters_tests.json":"08ddaa632ad19c81e83b904bfaa94bc971f26e2bdfcef27d2f93fd033ad57340","tests/unit.rs":"6068d8386d56d7a4495eb81bbd9b21bca7696755cf5b14efc3d7b33cfad24a19","tests/urltestdata.json":"1b0c7c727d8d7e79dfb0d0aa347ff05675ddb68bc4ead38f83fd8e89bc59cc32"},"package":"75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61"}
|
||||
{"files":{"Cargo.toml":"9814ea7672535568bb8c84b23512fe0119f85a14a2cc84d2f7f0b3848c61a65c","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e","src/host.rs":"88adbe3a8dc691a4e32d7ed664dc7f17f8a1a863ffa4cde4d618032a00ec6653","src/lib.rs":"30372556e2352f93fa56178b85174eb0c3a819e87b12c288b67a101f7535a712","src/origin.rs":"2b967dd04fa5852b4a89f13cc628f3bfd84d1432e9447b2d3cbc6fee8aba3738","src/parser.rs":"eab4f6c7bffc021e9722375447a259683b8257dc6dff784c715032c39d4b4923","src/path_segments.rs":"dd6b637245b2ad77ce96221df3f80c8b4ad858cd52aecc86b97166dec386882a","src/quirks.rs":"8a504a2eeb74429e3282f2628014f3af9a567284c2b08bd0360bfd72d3a17e43","src/slicing.rs":"25425fc5c4100a3a5da49d1e0b497f6536066afcc91c4ba4dff5ace0860acd40","tests/data.rs":"dace394aed466df0e8afea0037bb8c90306d30ce75efdaff39feb00dbd07fa79","tests/setters_tests.json":"486f6d129960d0d0d99b533caf9bef21113b31adcdb83296dfc4a59cd8431715","tests/unit.rs":"61e014d600aa558f4913ac7bc268d652eda67daead72b0b0cf288524b1d71d13","tests/urltestdata.json":"afe55dd9583d125305d31e152478de9cc19801006b413c5f4fb7d430c50b1d11"},"package":"a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"}
|
|
@ -11,30 +11,26 @@
|
|||
# will likely look very different (and much more reasonable)
|
||||
|
||||
[package]
|
||||
edition = "2018"
|
||||
name = "url"
|
||||
version = "2.1.0"
|
||||
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"
|
||||
|
||||
|
@ -42,7 +38,7 @@ version = "0.2.0"
|
|||
version = "0.1"
|
||||
|
||||
[dependencies.percent-encoding]
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0"
|
||||
|
@ -51,9 +47,6 @@ optional = true
|
|||
[dev-dependencies.bencher]
|
||||
version = "0.1"
|
||||
|
||||
[dev-dependencies.rustc-test]
|
||||
version = "0.3"
|
||||
|
||||
[dev-dependencies.serde_json]
|
||||
version = "1.0"
|
||||
[badges.appveyor]
|
||||
|
|
|
@ -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.
|
|
@ -1,263 +0,0 @@
|
|||
# Guide to 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<str::Split<char>>`.
|
||||
|
||||
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::<Vec<_>>()),
|
||||
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<Host<&str>>`.
|
||||
The `serialize_host()` method that returned `Option<String>`
|
||||
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<u8>`;
|
||||
now it returns an iterator of decoded `u8` bytes that also implements `Into<Cow<u8>>`.
|
||||
Use `.into().to_owned()` to obtain a `Vec<u8>`.
|
||||
(`.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.
|
|
@ -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
|
|
@ -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::<Url>().unwrap());
|
||||
}
|
||||
|
||||
benchmark_group!(benches, short);
|
||||
benchmark_main!(benches);
|
|
@ -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 {
|
||||
|
@ -24,9 +25,10 @@ pub(crate) enum HostInternal {
|
|||
Ipv6(Ipv6Addr),
|
||||
}
|
||||
|
||||
impl<S> From<Host<S>> for HostInternal {
|
||||
fn from(host: Host<S>) -> HostInternal {
|
||||
impl From<Host<String>> for HostInternal {
|
||||
fn from(host: Host<String>) -> HostInternal {
|
||||
match host {
|
||||
Host::Domain(ref s) if s.is_empty() => HostInternal::None,
|
||||
Host::Domain(_) => HostInternal::Domain,
|
||||
Host::Ipv4(address) => HostInternal::Ipv4(address),
|
||||
Host::Ipv6(address) => HostInternal::Ipv6(address),
|
||||
|
@ -36,7 +38,7 @@ impl<S> From<Host<S>> for HostInternal {
|
|||
|
||||
/// 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<S = String> {
|
||||
/// A DNS domain name, as '.' dot-separated labels.
|
||||
/// Non-ASCII labels are encoded in punycode per IDNA if this is the host of
|
||||
|
@ -81,33 +83,38 @@ impl Host<String> {
|
|||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,35 +126,40 @@ impl Host<String> {
|
|||
}
|
||||
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<S: AsRef<str>> fmt::Display for Host<S> {
|
||||
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),
|
||||
|
@ -160,7 +172,21 @@ impl<S: AsRef<str>> fmt::Display for Host<S> {
|
|||
}
|
||||
}
|
||||
|
||||
fn write_ipv6(addr: &Ipv6Addr, f: &mut Formatter) -> fmt::Result {
|
||||
impl<S, T> PartialEq<Host<T>> for Host<S>
|
||||
where
|
||||
S: PartialEq<T>,
|
||||
{
|
||||
fn eq(&self, other: &Host<T>) -> 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;
|
||||
|
@ -237,11 +263,11 @@ fn parse_ipv4number(mut input: &str) -> Result<Option<u32>, ()> {
|
|||
// 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,
|
||||
};
|
||||
|
||||
|
@ -276,7 +302,7 @@ fn parse_ipv4addr(input: &str) -> ParseResult<Option<Ipv4Addr>> {
|
|||
let mut numbers: Vec<u32> = Vec::new();
|
||||
let mut overflow = false;
|
||||
for part in parts {
|
||||
if part == "" {
|
||||
if part.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
match parse_ipv4number(part) {
|
||||
|
|
|
@ -73,6 +73,9 @@ assert!(data_url.fragment() == Some(""));
|
|||
# run().unwrap();
|
||||
```
|
||||
|
||||
## Serde
|
||||
|
||||
Enable the `serde` feature to include `Deserialize` and `Serialize` implementations for `url::Url`.
|
||||
|
||||
# Base URL
|
||||
|
||||
|
@ -103,24 +106,34 @@ assert_eq!(css_url.as_str(), "http://servo.github.io/rust-url/main.css");
|
|||
# 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.0.0")]
|
||||
#![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::ops::{Range, RangeFrom, RangeTo};
|
|||
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 @@ impl<'a> ParseOptions<'a> {
|
|||
}
|
||||
|
||||
/// Parse an URL string with the configuration so far.
|
||||
pub fn parse(self, input: &str) -> Result<Url, ::ParseError> {
|
||||
pub fn parse(self, input: &str) -> Result<Url, crate::ParseError> {
|
||||
Parser {
|
||||
serialization: String::with_capacity(input.len()),
|
||||
base_url: self.base_url,
|
||||
|
@ -259,7 +272,7 @@ impl Url {
|
|||
///
|
||||
/// [`ParseError`]: enum.ParseError.html
|
||||
#[inline]
|
||||
pub fn parse(input: &str) -> Result<Url, ::ParseError> {
|
||||
pub fn parse(input: &str) -> Result<Url, crate::ParseError> {
|
||||
Url::options().parse(input)
|
||||
}
|
||||
|
||||
|
@ -276,6 +289,7 @@ impl Url {
|
|||
/// # 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 @@ impl Url {
|
|||
///
|
||||
/// [`ParseError`]: enum.ParseError.html
|
||||
#[inline]
|
||||
pub fn parse_with_params<I, K, V>(input: &str, iter: I) -> Result<Url, ::ParseError>
|
||||
pub fn parse_with_params<I, K, V>(input: &str, iter: I) -> Result<Url, crate::ParseError>
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Borrow<(K, V)>,
|
||||
|
@ -306,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:
|
||||
|
@ -335,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, ::ParseError> {
|
||||
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
|
||||
|
@ -403,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.
|
||||
|
@ -456,13 +606,15 @@ impl Url {
|
|||
|
||||
if self.slice(self.scheme_end + 1..).starts_with("//") {
|
||||
// URL with authority
|
||||
match self.byte_at(self.username_end) {
|
||||
b':' => {
|
||||
assert!(self.host_start >= self.username_end + 2);
|
||||
assert_eq!(self.byte_at(self.host_start - 1), b'@');
|
||||
if self.username_end != self.serialization.len() as u32 {
|
||||
match self.byte_at(self.username_end) {
|
||||
b':' => {
|
||||
assert!(self.host_start >= self.username_end + 2);
|
||||
assert_eq!(self.byte_at(self.host_start - 1), b'@');
|
||||
}
|
||||
b'@' => assert!(self.host_start == self.username_end + 1),
|
||||
_ => assert_eq!(self.username_end, self.scheme_end + 3),
|
||||
}
|
||||
b'@' => assert!(self.host_start == self.username_end + 1),
|
||||
_ => assert_eq!(self.username_end, self.scheme_end + 3),
|
||||
}
|
||||
assert!(self.host_start >= self.username_end);
|
||||
assert!(self.host_end >= self.host_start);
|
||||
|
@ -490,7 +642,10 @@ impl Url {
|
|||
Some(port_str.parse::<u16>().expect("Couldn't parse port?"))
|
||||
);
|
||||
}
|
||||
assert_eq!(self.byte_at(self.path_start), b'/');
|
||||
assert!(
|
||||
self.path_start as usize == self.serialization.len()
|
||||
|| matches!(self.byte_at(self.path_start), b'/' | b'#' | b'?')
|
||||
);
|
||||
} else {
|
||||
// Anarchist URL (no authority)
|
||||
assert_eq!(self.username_end, self.scheme_end + 1);
|
||||
|
@ -501,11 +656,11 @@ impl Url {
|
|||
assert_eq!(self.path_start, self.scheme_end + 1);
|
||||
}
|
||||
if let Some(start) = self.query_start {
|
||||
assert!(start > self.path_start);
|
||||
assert!(start >= self.path_start);
|
||||
assert_eq!(self.byte_at(start), b'?');
|
||||
}
|
||||
if let Some(start) = self.fragment_start {
|
||||
assert!(start > self.path_start);
|
||||
assert!(start >= self.path_start);
|
||||
assert_eq!(self.byte_at(start), b'#');
|
||||
}
|
||||
if let (Some(query_start), Some(fragment_start)) = (self.query_start, self.fragment_start) {
|
||||
|
@ -685,7 +840,7 @@ impl Url {
|
|||
/// ```
|
||||
#[inline]
|
||||
pub fn cannot_be_a_base(&self) -> bool {
|
||||
!self.slice(self.path_start..).starts_with('/')
|
||||
!self.slice(self.scheme_end + 1..).starts_with('/')
|
||||
}
|
||||
|
||||
/// Return the username for this URL (typically the empty string)
|
||||
|
@ -711,8 +866,9 @@ impl Url {
|
|||
/// # 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 {
|
||||
""
|
||||
}
|
||||
|
@ -745,7 +901,10 @@ impl Url {
|
|||
pub fn password(&self) -> Option<&str> {
|
||||
// This ':' is not the one marking a port number since a host can not be empty.
|
||||
// (Except for file: URLs, which do not have port numbers.)
|
||||
if self.has_authority() && self.byte_at(self.username_end) == b':' {
|
||||
if self.has_authority()
|
||||
&& self.username_end != self.serialization.len() as u32
|
||||
&& self.byte_at(self.username_end) == b':'
|
||||
{
|
||||
debug_assert!(self.byte_at(self.host_start - 1) == b'@');
|
||||
Some(self.slice(self.username_end + 1..self.host_start - 1))
|
||||
} else {
|
||||
|
@ -780,7 +939,8 @@ impl Url {
|
|||
|
||||
/// 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
|
||||
|
@ -819,7 +979,8 @@ impl Url {
|
|||
}
|
||||
|
||||
/// 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.
|
||||
|
@ -858,6 +1019,8 @@ impl Url {
|
|||
}
|
||||
|
||||
/// 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
|
||||
///
|
||||
|
@ -917,7 +1080,7 @@ impl Url {
|
|||
/// 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()`.
|
||||
|
@ -1048,7 +1211,7 @@ impl Url {
|
|||
/// use url::Url;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn run() -> Result<(), Box<Error>> {
|
||||
/// # fn run() -> Result<(), Box<dyn Error>> {
|
||||
/// 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"));
|
||||
|
@ -1071,7 +1234,8 @@ impl Url {
|
|||
/// # }
|
||||
/// # run().unwrap();
|
||||
/// ```
|
||||
pub fn path_segments(&self) -> Option<str::Split<char>> {
|
||||
#[allow(clippy::manual_strip)] // introduced in 1.45, MSRV is 1.36
|
||||
pub fn path_segments(&self) -> Option<str::Split<'_, char>> {
|
||||
let path = self.path();
|
||||
if path.starts_with('/') {
|
||||
Some(path[1..].split('/'))
|
||||
|
@ -1143,7 +1307,7 @@ impl Url {
|
|||
///
|
||||
|
||||
#[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())
|
||||
}
|
||||
|
||||
|
@ -1186,7 +1350,7 @@ impl Url {
|
|||
})
|
||||
}
|
||||
|
||||
fn mutate<F: FnOnce(&mut Parser) -> R, R>(&mut self, f: F) -> R {
|
||||
fn mutate<F: FnOnce(&mut Parser<'_>) -> 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;
|
||||
|
@ -1226,7 +1390,7 @@ impl Url {
|
|||
if let Some(input) = fragment {
|
||||
self.fragment_start = Some(to_u32(self.serialization.len()).unwrap());
|
||||
self.serialization.push('#');
|
||||
self.mutate(|parser| parser.parse_fragment(parser::Input::new(input)))
|
||||
self.mutate(|parser| parser.parse_fragment(parser::Input::no_trim(input)))
|
||||
} else {
|
||||
self.fragment_start = None
|
||||
}
|
||||
|
@ -1284,7 +1448,12 @@ impl Url {
|
|||
let scheme_type = SchemeType::from(self.scheme());
|
||||
let scheme_end = self.scheme_end;
|
||||
self.mutate(|parser| {
|
||||
parser.parse_query(scheme_type, scheme_end, parser::Input::new(input))
|
||||
let vfn = parser.violation_fn;
|
||||
parser.parse_query(
|
||||
scheme_type,
|
||||
scheme_end,
|
||||
parser::Input::trim_tab_and_newlines(input, vfn),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1323,7 +1492,7 @@ impl Url {
|
|||
/// 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<UrlQuery> {
|
||||
pub fn query_pairs_mut(&mut self) -> form_urlencoded::Serializer<'_, UrlQuery<'_>> {
|
||||
let fragment = self.take_fragment();
|
||||
|
||||
let query_start;
|
||||
|
@ -1400,7 +1569,8 @@ impl Url {
|
|||
/// 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<PathSegmentsMut, ()> {
|
||||
#[allow(clippy::result_unit_err)]
|
||||
pub fn path_segments_mut(&mut self) -> Result<PathSegmentsMut<'_>, ()> {
|
||||
if self.cannot_be_a_base() {
|
||||
Err(())
|
||||
} else {
|
||||
|
@ -1436,7 +1606,7 @@ impl Url {
|
|||
/// use url::Url;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn run() -> Result<(), Box<Error>> {
|
||||
/// # fn run() -> Result<(), Box<dyn Error>> {
|
||||
/// let mut url = Url::parse("ssh://example.net:2048/")?;
|
||||
///
|
||||
/// url.set_port(Some(4096)).map_err(|_| "cannot be base")?;
|
||||
|
@ -1455,7 +1625,7 @@ impl Url {
|
|||
/// use url::Url;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn run() -> Result<(), Box<Error>> {
|
||||
/// # fn run() -> Result<(), Box<dyn Error>> {
|
||||
/// let mut url = Url::parse("https://example.org/")?;
|
||||
///
|
||||
/// url.set_port(Some(443)).map_err(|_| "cannot be base")?;
|
||||
|
@ -1483,6 +1653,7 @@ impl Url {
|
|||
/// # }
|
||||
/// # run().unwrap();
|
||||
/// ```
|
||||
#[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" {
|
||||
|
@ -1622,17 +1793,35 @@ impl Url {
|
|||
}
|
||||
|
||||
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;
|
||||
// Otherwise, if c is U+003A (:) and the [] flag is unset, then
|
||||
if !host.starts_with('[') || !host.ends_with(']') {
|
||||
match host.find(':') {
|
||||
Some(0) => {
|
||||
// If buffer is the empty string, validation error, return failure.
|
||||
return Err(ParseError::InvalidDomainCharacter);
|
||||
}
|
||||
// Let host be the result of host parsing buffer
|
||||
Some(colon_index) => {
|
||||
host_substr = &host[..colon_index];
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
if SchemeType::from(self.scheme()).is_special() {
|
||||
self.set_host_internal(Host::parse(host)?, None)
|
||||
self.set_host_internal(Host::parse(host_substr)?, None);
|
||||
} else {
|
||||
self.set_host_internal(Host::parse_opaque(host)?, None)
|
||||
self.set_host_internal(Host::parse_opaque(host_substr)?, None);
|
||||
}
|
||||
} else if self.has_host() {
|
||||
if SchemeType::from(self.scheme()).is_special() {
|
||||
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('/');
|
||||
}
|
||||
debug_assert!(self.byte_at(self.scheme_end) == b':');
|
||||
debug_assert!(self.byte_at(self.path_start) == b'/');
|
||||
|
@ -1735,6 +1924,7 @@ impl Url {
|
|||
/// # 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(());
|
||||
|
@ -1774,6 +1964,7 @@ impl Url {
|
|||
/// # }
|
||||
/// # 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" {
|
||||
|
@ -1866,6 +2057,7 @@ impl Url {
|
|||
/// # }
|
||||
/// # 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" {
|
||||
|
@ -1919,11 +2111,19 @@ impl Url {
|
|||
|
||||
/// 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
|
||||
///
|
||||
|
@ -1935,14 +2135,28 @@ impl Url {
|
|||
///
|
||||
/// # fn run() -> Result<(), ParseError> {
|
||||
/// let mut url = Url::parse("https://example.net")?;
|
||||
/// let result = url.set_scheme("foo");
|
||||
/// assert_eq!(url.as_str(), "foo://example.net/");
|
||||
/// let result = url.set_scheme("http");
|
||||
/// assert_eq!(url.as_str(), "http://example.net/");
|
||||
/// assert!(result.is_ok());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # run().unwrap();
|
||||
/// ```
|
||||
/// Change the URL’s scheme from `foo` to `bar`:
|
||||
///
|
||||
/// ```
|
||||
/// use url::Url;
|
||||
/// # use url::ParseError;
|
||||
///
|
||||
/// # fn run() -> Result<(), ParseError> {
|
||||
/// let mut url = Url::parse("foo://example.net")?;
|
||||
/// let result = url.set_scheme("bar");
|
||||
/// assert_eq!(url.as_str(), "bar://example.net");
|
||||
/// assert!(result.is_ok());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # run().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// Cannot change URL’s scheme from `https` to `foõ`:
|
||||
///
|
||||
|
@ -1975,14 +2189,56 @@ impl Url {
|
|||
/// # }
|
||||
/// # run().unwrap();
|
||||
/// ```
|
||||
/// Cannot change the URL’s scheme from `foo` to `https`:
|
||||
///
|
||||
/// ```
|
||||
/// use url::Url;
|
||||
/// # use url::ParseError;
|
||||
///
|
||||
/// # fn run() -> Result<(), ParseError> {
|
||||
/// let mut url = Url::parse("foo://example.net")?;
|
||||
/// let result = url.set_scheme("https");
|
||||
/// assert_eq!(url.as_str(), "foo://example.net");
|
||||
/// assert!(result.is_err());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # run().unwrap();
|
||||
/// ```
|
||||
/// Cannot change the URL’s scheme from `http` to `foo`:
|
||||
///
|
||||
/// ```
|
||||
/// use url::Url;
|
||||
/// # use url::ParseError;
|
||||
///
|
||||
/// # fn run() -> Result<(), ParseError> {
|
||||
/// let mut url = Url::parse("http://example.net")?;
|
||||
/// let result = url.set_scheme("foo");
|
||||
/// assert_eq!(url.as_str(), "http://example.net/");
|
||||
/// assert!(result.is_err());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # 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))?;
|
||||
if !remaining.is_empty()
|
||||
|| (!self.has_host() && SchemeType::from(&parser.serialization).is_special())
|
||||
let new_scheme_type = SchemeType::from(&parser.serialization);
|
||||
let old_scheme_type = SchemeType::from(self.scheme());
|
||||
// If url’s scheme is a special scheme and buffer is not a special scheme, then return.
|
||||
if (new_scheme_type.is_special() && !old_scheme_type.is_special()) ||
|
||||
// If url’s scheme is not a special scheme and buffer is a special scheme, then return.
|
||||
(!new_scheme_type.is_special() && old_scheme_type.is_special()) ||
|
||||
// If url includes credentials or has a non-null port, and buffer is "file", then return.
|
||||
// If url’s scheme is "file" and its host is an empty host or null, then return.
|
||||
(new_scheme_type.is_file() && self.has_authority())
|
||||
{
|
||||
return Err(());
|
||||
}
|
||||
|
||||
if !remaining.is_empty() || (!self.has_host() && new_scheme_type.is_special()) {
|
||||
return Err(());
|
||||
}
|
||||
let old_scheme_end = self.scheme_end;
|
||||
let new_scheme_end = to_u32(parser.serialization.len()).unwrap();
|
||||
let adjust = |index: &mut u32| {
|
||||
|
@ -2004,6 +2260,14 @@ impl Url {
|
|||
|
||||
parser.serialization.push_str(self.slice(old_scheme_end..));
|
||||
self.serialization = parser.serialization;
|
||||
|
||||
// Update the port so it can be removed
|
||||
// If it is the scheme's default
|
||||
// we don't mind it silently failing
|
||||
// if there was no port in the first place
|
||||
let previous_port = self.port();
|
||||
let _ = self.set_port(previous_port);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -2035,6 +2299,7 @@ impl Url {
|
|||
/// # }
|
||||
/// ```
|
||||
#[cfg(any(unix, windows, target_os = "redox"))]
|
||||
#[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;
|
||||
|
@ -2071,6 +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::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('/') {
|
||||
|
@ -2187,6 +2453,7 @@ impl Url {
|
|||
/// 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<PathBuf, ()> {
|
||||
if let Some(segments) = self.path_segments() {
|
||||
let host = match self.host() {
|
||||
|
@ -2223,24 +2490,50 @@ impl str::FromStr for Url {
|
|||
type Err = ParseError;
|
||||
|
||||
#[inline]
|
||||
fn from_str(input: &str) -> Result<Url, ::ParseError> {
|
||||
fn from_str(input: &str) -> Result<Url, crate::ParseError> {
|
||||
Url::parse(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for Url {
|
||||
type Error = ParseError;
|
||||
|
||||
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
|
||||
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<Url> 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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2352,8 +2645,10 @@ impl<'de> serde::Deserialize<'de> for Url {
|
|||
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())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2408,6 +2703,7 @@ fn path_to_file_url_segments_windows(
|
|||
}
|
||||
let mut components = path.components();
|
||||
|
||||
let host_start = serialization.len() + 1;
|
||||
let host_end;
|
||||
let host_internal;
|
||||
match components.next() {
|
||||
|
@ -2434,22 +2730,31 @@ fn path_to_file_url_segments_windows(
|
|||
_ => return Err(()),
|
||||
}
|
||||
|
||||
let mut path_only_has_prefix = true;
|
||||
for component in components {
|
||||
if component == Component::RootDir {
|
||||
continue;
|
||||
}
|
||||
path_only_has_prefix = false;
|
||||
// FIXME: somehow work with non-unicode?
|
||||
let component = component.as_os_str().to_str().ok_or(())?;
|
||||
serialization.push('/');
|
||||
serialization.extend(percent_encode(component.as_bytes(), PATH_SEGMENT));
|
||||
}
|
||||
// A windows drive letter must end with a slash.
|
||||
if serialization.len() > host_start
|
||||
&& parser::is_windows_drive_letter(&serialization[host_start..])
|
||||
&& path_only_has_prefix
|
||||
{
|
||||
serialization.push('/');
|
||||
}
|
||||
Ok((host_end, host_internal))
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
fn file_url_segments_to_pathbuf(
|
||||
host: Option<&str>,
|
||||
segments: str::Split<char>,
|
||||
segments: str::Split<'_, char>,
|
||||
) -> Result<PathBuf, ()> {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::prelude::OsStrExt;
|
||||
|
@ -2467,6 +2772,13 @@ fn file_url_segments_to_pathbuf(
|
|||
bytes.push(b'/');
|
||||
bytes.extend(percent_decode(segment.as_bytes()));
|
||||
}
|
||||
// A windows drive letter must end with a slash.
|
||||
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);
|
||||
debug_assert!(
|
||||
|
@ -2488,7 +2800,7 @@ fn file_url_segments_to_pathbuf(
|
|||
#[cfg_attr(not(windows), allow(dead_code))]
|
||||
fn file_url_segments_to_pathbuf_windows(
|
||||
host: Option<&str>,
|
||||
mut segments: str::Split<char>,
|
||||
mut segments: str::Split<'_, char>,
|
||||
) -> Result<PathBuf, ()> {
|
||||
let mut string = if let Some(host) = host {
|
||||
r"\\".to_owned() + host
|
||||
|
@ -2544,6 +2856,30 @@ pub struct UrlQuery<'a> {
|
|||
fragment: Option<String>,
|
||||
}
|
||||
|
||||
// `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<UrlQuery>`
|
||||
// * `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() {
|
||||
|
|
|
@ -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 @@ pub fn url_origin(url: &Url) -> Origin {
|
|||
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 @@ pub fn url_origin(url: &Url) -> Origin {
|
|||
/// - 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.
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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;
|
|||
/// use url::Url;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn run() -> Result<(), Box<Error>> {
|
||||
/// # fn run() -> Result<(), Box<dyn Error>> {
|
||||
/// let mut url = Url::parse("mailto:me@example.com")?;
|
||||
/// assert!(url.path_segments_mut().is_err());
|
||||
///
|
||||
|
@ -42,10 +42,18 @@ pub struct PathSegmentsMut<'a> {
|
|||
}
|
||||
|
||||
// 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();
|
||||
debug_assert!(url.byte_at(url.path_start) == b'/');
|
||||
// Special urls always have a non empty path
|
||||
if SchemeType::from(url.scheme()).is_special() {
|
||||
debug_assert!(url.byte_at(url.path_start) == b'/');
|
||||
} else {
|
||||
debug_assert!(
|
||||
url.serialization.len() == url.path_start as usize
|
||||
|| url.byte_at(url.path_start) == b'/'
|
||||
);
|
||||
}
|
||||
PathSegmentsMut {
|
||||
after_first_slash: url.path_start as usize + "/".len(),
|
||||
url,
|
||||
|
@ -72,7 +80,7 @@ impl<'a> PathSegmentsMut<'a> {
|
|||
/// use url::Url;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn run() -> Result<(), Box<Error>> {
|
||||
/// # fn run() -> Result<(), Box<dyn Error>> {
|
||||
/// let mut url = Url::parse("https://github.com/servo/rust-url/")?;
|
||||
/// url.path_segments_mut().map_err(|_| "cannot be base")?
|
||||
/// .clear().push("logout");
|
||||
|
@ -100,7 +108,7 @@ impl<'a> PathSegmentsMut<'a> {
|
|||
/// use url::Url;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn run() -> Result<(), Box<Error>> {
|
||||
/// # fn run() -> Result<(), Box<dyn Error>> {
|
||||
/// let mut url = Url::parse("https://github.com/servo/rust-url/")?;
|
||||
/// url.path_segments_mut().map_err(|_| "cannot be base")?
|
||||
/// .push("pulls");
|
||||
|
@ -115,6 +123,9 @@ impl<'a> PathSegmentsMut<'a> {
|
|||
/// # 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();
|
||||
}
|
||||
|
@ -127,6 +138,9 @@ impl<'a> PathSegmentsMut<'a> {
|
|||
///
|
||||
/// 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);
|
||||
|
@ -169,7 +183,7 @@ impl<'a> PathSegmentsMut<'a> {
|
|||
/// use url::Url;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn run() -> Result<(), Box<Error>> {
|
||||
/// # fn run() -> Result<(), Box<dyn Error>> {
|
||||
/// let mut url = Url::parse("https://github.com/")?;
|
||||
/// let org = "servo";
|
||||
/// let repo = "rust-url";
|
||||
|
@ -189,7 +203,7 @@ impl<'a> PathSegmentsMut<'a> {
|
|||
/// use url::Url;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn run() -> Result<(), Box<Error>> {
|
||||
/// # fn run() -> Result<(), Box<dyn Error>> {
|
||||
/// let mut url = Url::parse("https://github.com/servo")?;
|
||||
/// url.path_segments_mut().map_err(|_| "cannot be base")?
|
||||
/// .extend(&["..", "rust-url", ".", "pulls"]);
|
||||
|
@ -212,7 +226,10 @@ impl<'a> PathSegmentsMut<'a> {
|
|||
if matches!(segment, "." | "..") {
|
||||
continue;
|
||||
}
|
||||
if parser.serialization.len() > path_start + 1 {
|
||||
if parser.serialization.len() > path_start + 1
|
||||
// Non special url's path might still be empty
|
||||
|| parser.serialization.len() == path_start
|
||||
{
|
||||
parser.serialization.push('/');
|
||||
}
|
||||
let mut has_host = true; // FIXME account for this?
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
// Copyright 2019 The rust-url developers.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<str> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 @@ pub fn protocol(url: &Url) -> &str {
|
|||
}
|
||||
|
||||
/// 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 @@ pub fn username(url: &Url) -> &str {
|
|||
}
|
||||
|
||||
/// 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 @@ pub fn password(url: &Url) -> &str {
|
|||
}
|
||||
|
||||
/// 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,27 +101,50 @@ pub fn host(url: &Url) -> &str {
|
|||
}
|
||||
|
||||
/// 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() {
|
||||
return Err(());
|
||||
}
|
||||
// Host parsing rules are strict,
|
||||
// We don't want to trim the input
|
||||
let input = Input::no_trim(new_host);
|
||||
let host;
|
||||
let opt_port;
|
||||
{
|
||||
let scheme = url.scheme();
|
||||
let result = Parser::parse_host(Input::new(new_host), SchemeType::from(scheme));
|
||||
match result {
|
||||
Ok((h, remaining)) => {
|
||||
host = h;
|
||||
opt_port = if let Some(remaining) = remaining.split_prefix(':') {
|
||||
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(':') {
|
||||
if remaining.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Parser::parse_port(remaining, || default_port(scheme), Context::Setter)
|
||||
.ok()
|
||||
.map(|(port, _remaining)| port)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
Err(_) => return Err(()),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
} else {
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
// Make sure we won't set an empty host to a url with a username or a port
|
||||
if host == Host::Domain("".to_string()) {
|
||||
if !username(&url).is_empty() {
|
||||
return Err(());
|
||||
} else if let Some(Some(_)) = opt_port {
|
||||
return Err(());
|
||||
} else if url.port().is_some() {
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
url.set_host_internal(host, opt_port);
|
||||
|
@ -132,12 +158,34 @@ pub fn hostname(url: &Url) -> &str {
|
|||
}
|
||||
|
||||
/// 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(());
|
||||
}
|
||||
let result = Parser::parse_host(Input::new(new_hostname), SchemeType::from(url.scheme()));
|
||||
if let Ok((host, _remaining)) = result {
|
||||
// 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() {
|
||||
// Empty host on special not file url
|
||||
if SchemeType::from(url.scheme()) == SchemeType::SpecialNotFile
|
||||
// Port with an empty host
|
||||
||!port(&url).is_empty()
|
||||
// Empty host that includes credentials
|
||||
|| !url.username().is_empty()
|
||||
|| !url.password().unwrap_or(&"").is_empty()
|
||||
{
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
}
|
||||
url.set_host_internal(host, None);
|
||||
Ok(())
|
||||
} else {
|
||||
|
@ -152,6 +200,7 @@ pub fn port(url: &Url) -> &str {
|
|||
}
|
||||
|
||||
/// 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;
|
||||
{
|
||||
|
@ -182,8 +231,19 @@ pub fn pathname(url: &Url) -> &str {
|
|||
|
||||
/// Setter for https://url.spec.whatwg.org/#dom-url-pathname
|
||||
pub fn set_pathname(url: &mut Url, new_pathname: &str) {
|
||||
if !url.cannot_be_a_base() {
|
||||
if url.cannot_be_a_base() {
|
||||
return;
|
||||
}
|
||||
if new_pathname.starts_with('/')
|
||||
|| (SchemeType::from(url.scheme()).is_special()
|
||||
// \ is a segment delimiter for 'special' URLs"
|
||||
&& new_pathname.starts_with('\\'))
|
||||
{
|
||||
url.set_path(new_pathname)
|
||||
} else {
|
||||
let mut path_to_set = String::from("/");
|
||||
path_to_set.push_str(new_pathname);
|
||||
url.set_path(&path_to_set)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,13 +268,14 @@ pub fn hash(url: &Url) -> &str {
|
|||
|
||||
/// Setter for https://url.spec.whatwg.org/#dom-url-hash
|
||||
pub fn set_hash(url: &mut Url, new_hash: &str) {
|
||||
if url.scheme() != "javascript" {
|
||||
url.set_fragment(match new_hash {
|
||||
"" => None,
|
||||
_ if new_hash.starts_with('#') => Some(&new_hash[1..]),
|
||||
_ => Some(new_hash),
|
||||
})
|
||||
}
|
||||
url.set_fragment(match new_hash {
|
||||
// If the given value is the empty string,
|
||||
// then set context object’s url’s fragment to null and return.
|
||||
"" => None,
|
||||
// Let input be the given value with a single leading U+0023 (#) removed, if any.
|
||||
_ if new_hash.starts_with('#') => Some(&new_hash[1..]),
|
||||
_ => Some(new_hash),
|
||||
})
|
||||
}
|
||||
|
||||
fn trim(s: &str) -> &str {
|
||||
|
|
|
@ -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<RangeFull> for Url {
|
||||
type Output = str;
|
||||
|
|
|
@ -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();
|
||||
#[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");
|
||||
|
||||
let mut passed = true;
|
||||
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 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);
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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!(passed);
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
|
||||
fn run_parsing(input: &str, base: &str, expected: Result<ExpectedAttributes, ()>) {
|
||||
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),
|
||||
};
|
||||
|
||||
check_invariants(&url);
|
||||
|
||||
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
|
||||
);
|
||||
}};
|
||||
passed &= test_eq_eprint(url.to_string(), &new_url.to_string(), name, comment);
|
||||
}
|
||||
|
||||
macro_rules! assert_attributes {
|
||||
($($attr: ident)+) => {
|
||||
{
|
||||
$(
|
||||
assert_eq!(expected.$attr, quirks::$attr(&url));
|
||||
)+;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_attributes!(href protocol username password host hostname port pathname search hash);
|
||||
|
||||
if let Some(expected_origin) = expected.origin {
|
||||
assert_eq!(expected_origin, quirks::origin(&url));
|
||||
}
|
||||
}
|
||||
|
||||
struct ExpectedAttributes {
|
||||
href: String,
|
||||
origin: Option<String>,
|
||||
protocol: String,
|
||||
username: String,
|
||||
password: String,
|
||||
host: String,
|
||||
hostname: String,
|
||||
port: String,
|
||||
pathname: String,
|
||||
search: String,
|
||||
hash: String,
|
||||
passed
|
||||
}
|
||||
|
||||
trait JsonExt {
|
||||
|
@ -111,100 +174,60 @@ impl JsonExt for Value {
|
|||
}
|
||||
}
|
||||
|
||||
fn collect_parsing<F: FnMut(String, test::TestFn)>(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<F>(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;
|
||||
}
|
||||
test::test_main(&std::env::args().collect::<Vec<_>>(), tests)
|
||||
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!();
|
||||
}
|
||||
}
|
||||
|
||||
const ATTRIBS: &[&str] = &[
|
||||
"href", "protocol", "username", "password", "host", "hostname", "port", "pathname", "search",
|
||||
"hash",
|
||||
];
|
||||
|
|
|
@ -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.",
|
||||
|
@ -27,7 +28,7 @@
|
|||
"href": "a://example.net",
|
||||
"new_value": "",
|
||||
"expected": {
|
||||
"href": "a://example.net/",
|
||||
"href": "a://example.net",
|
||||
"protocol": "a:"
|
||||
}
|
||||
},
|
||||
|
@ -35,16 +36,24 @@
|
|||
"href": "a://example.net",
|
||||
"new_value": "b",
|
||||
"expected": {
|
||||
"href": "b://example.net/",
|
||||
"href": "b://example.net",
|
||||
"protocol": "b:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "javascript:alert(1)",
|
||||
"new_value": "defuse",
|
||||
"expected": {
|
||||
"href": "defuse:alert(1)",
|
||||
"protocol": "defuse:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "Upper-case ASCII is lower-cased",
|
||||
"href": "a://example.net",
|
||||
"new_value": "B",
|
||||
"expected": {
|
||||
"href": "b://example.net/",
|
||||
"href": "b://example.net",
|
||||
"protocol": "b:"
|
||||
}
|
||||
},
|
||||
|
@ -53,7 +62,7 @@
|
|||
"href": "a://example.net",
|
||||
"new_value": "é",
|
||||
"expected": {
|
||||
"href": "a://example.net/",
|
||||
"href": "a://example.net",
|
||||
"protocol": "a:"
|
||||
}
|
||||
},
|
||||
|
@ -62,7 +71,7 @@
|
|||
"href": "a://example.net",
|
||||
"new_value": "0b",
|
||||
"expected": {
|
||||
"href": "a://example.net/",
|
||||
"href": "a://example.net",
|
||||
"protocol": "a:"
|
||||
}
|
||||
},
|
||||
|
@ -71,7 +80,7 @@
|
|||
"href": "a://example.net",
|
||||
"new_value": "+b",
|
||||
"expected": {
|
||||
"href": "a://example.net/",
|
||||
"href": "a://example.net",
|
||||
"protocol": "a:"
|
||||
}
|
||||
},
|
||||
|
@ -79,7 +88,7 @@
|
|||
"href": "a://example.net",
|
||||
"new_value": "bC0+-.",
|
||||
"expected": {
|
||||
"href": "bc0+-.://example.net/",
|
||||
"href": "bc0+-.://example.net",
|
||||
"protocol": "bc0+-.:"
|
||||
}
|
||||
},
|
||||
|
@ -88,7 +97,7 @@
|
|||
"href": "a://example.net",
|
||||
"new_value": "b,c",
|
||||
"expected": {
|
||||
"href": "a://example.net/",
|
||||
"href": "a://example.net",
|
||||
"protocol": "a:"
|
||||
}
|
||||
},
|
||||
|
@ -97,10 +106,35 @@
|
|||
"href": "a://example.net",
|
||||
"new_value": "bé",
|
||||
"expected": {
|
||||
"href": "a://example.net/",
|
||||
"href": "a://example.net",
|
||||
"protocol": "a:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "Can’t switch from URL containing username/password/port to file",
|
||||
"href": "http://test@example.net",
|
||||
"new_value": "file",
|
||||
"expected": {
|
||||
"href": "http://test@example.net/",
|
||||
"protocol": "http:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "gopher://example.net:1234",
|
||||
"new_value": "file",
|
||||
"expected": {
|
||||
"href": "gopher://example.net:1234",
|
||||
"protocol": "gopher:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "wss://x:x@example.net:1234",
|
||||
"new_value": "file",
|
||||
"expected": {
|
||||
"href": "wss://x:x@example.net:1234/",
|
||||
"protocol": "wss:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "Can’t switch from file URL with no host",
|
||||
"href": "file://localhost/",
|
||||
|
@ -127,12 +161,36 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"comment": "Spec deviation: from special scheme to not is not problematic. https://github.com/whatwg/url/issues/104",
|
||||
"comment": "Can’t switch from special scheme to non-special",
|
||||
"href": "http://example.net",
|
||||
"new_value": "b",
|
||||
"expected": {
|
||||
"href": "b://example.net/",
|
||||
"protocol": "b:"
|
||||
"href": "http://example.net/",
|
||||
"protocol": "http:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "file://hi/path",
|
||||
"new_value": "s",
|
||||
"expected": {
|
||||
"href": "file://hi/path",
|
||||
"protocol": "file:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "https://example.net",
|
||||
"new_value": "s",
|
||||
"expected": {
|
||||
"href": "https://example.net/",
|
||||
"protocol": "https:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "ftp://example.net",
|
||||
"new_value": "test",
|
||||
"expected": {
|
||||
"href": "ftp://example.net/",
|
||||
"protocol": "ftp:"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -145,12 +203,44 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"comment": "Spec deviation: from non-special scheme with a host to special is not problematic. https://github.com/whatwg/url/issues/104",
|
||||
"comment": "Can’t switch from non-special scheme to special",
|
||||
"href": "ssh://me@example.net",
|
||||
"new_value": "http",
|
||||
"expected": {
|
||||
"href": "http://me@example.net/",
|
||||
"protocol": "http:"
|
||||
"href": "ssh://me@example.net",
|
||||
"protocol": "ssh:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "ssh://me@example.net",
|
||||
"new_value": "https",
|
||||
"expected": {
|
||||
"href": "ssh://me@example.net",
|
||||
"protocol": "ssh:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "ssh://me@example.net",
|
||||
"new_value": "file",
|
||||
"expected": {
|
||||
"href": "ssh://me@example.net",
|
||||
"protocol": "ssh:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "ssh://example.net",
|
||||
"new_value": "file",
|
||||
"expected": {
|
||||
"href": "ssh://example.net",
|
||||
"protocol": "ssh:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "nonsense:///test",
|
||||
"new_value": "https",
|
||||
"expected": {
|
||||
"href": "nonsense:///test",
|
||||
"protocol": "nonsense:"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -170,6 +260,16 @@
|
|||
"href": "view-source+data:text/html,<p>Test",
|
||||
"protocol": "view-source+data:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "Port is set to null if it is the default for new scheme.",
|
||||
"href": "http://foo.com:443/",
|
||||
"new_value": "https",
|
||||
"expected": {
|
||||
"href": "https://foo.com/",
|
||||
"protocol": "https:",
|
||||
"port": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"username": [
|
||||
|
@ -266,14 +366,6 @@
|
|||
"username": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "file://test/",
|
||||
"new_value": "test",
|
||||
"expected": {
|
||||
"href": "file://test/",
|
||||
"username": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "javascript://x/",
|
||||
"new_value": "wario",
|
||||
|
@ -281,6 +373,14 @@
|
|||
"href": "javascript://wario@x/",
|
||||
"username": "wario"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "file://test/",
|
||||
"new_value": "test",
|
||||
"expected": {
|
||||
"href": "file://test/",
|
||||
"username": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"password": [
|
||||
|
@ -369,14 +469,6 @@
|
|||
"password": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "file://test/",
|
||||
"new_value": "test",
|
||||
"expected": {
|
||||
"href": "file://test/",
|
||||
"password": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "javascript://x/",
|
||||
"new_value": "bowser",
|
||||
|
@ -384,9 +476,27 @@
|
|||
"href": "javascript://:bowser@x/",
|
||||
"password": "bowser"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "file://test/",
|
||||
"new_value": "test",
|
||||
"expected": {
|
||||
"href": "file://test/",
|
||||
"password": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"host": [
|
||||
{
|
||||
"comment": "Non-special scheme",
|
||||
"href": "sc://x/",
|
||||
"new_value": "\u0000",
|
||||
"expected": {
|
||||
"href": "sc://x/",
|
||||
"host": "x",
|
||||
"hostname": "x"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "sc://x/",
|
||||
"new_value": "\u0009",
|
||||
|
@ -414,6 +524,15 @@
|
|||
"hostname": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "sc://x/",
|
||||
"new_value": " ",
|
||||
"expected": {
|
||||
"href": "sc://x/",
|
||||
"host": "x",
|
||||
"hostname": "x"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "sc://x/",
|
||||
"new_value": "#",
|
||||
|
@ -459,6 +578,16 @@
|
|||
"hostname": "%C3%9F"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "IDNA Nontransitional_Processing",
|
||||
"href": "https://x/",
|
||||
"new_value": "ß",
|
||||
"expected": {
|
||||
"href": "https://xn--zca/",
|
||||
"host": "xn--zca",
|
||||
"hostname": "xn--zca"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "Cannot-be-a-base means no host",
|
||||
"href": "mailto:me@example.net",
|
||||
|
@ -469,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": {
|
||||
|
@ -499,14 +628,14 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"comment": "Port number is removed if empty in the new value: https://github.com/whatwg/url/pull/113",
|
||||
"comment": "Port number is unchanged if not specified",
|
||||
"href": "http://example.net:8080",
|
||||
"new_value": "example.com:",
|
||||
"expected": {
|
||||
"href": "http://example.com/",
|
||||
"host": "example.com",
|
||||
"href": "http://example.com:8080/",
|
||||
"host": "example.com:8080",
|
||||
"hostname": "example.com",
|
||||
"port": ""
|
||||
"port": "8080"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -558,6 +687,17 @@
|
|||
"port": "2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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",
|
||||
|
@ -591,6 +731,17 @@
|
|||
"port": "80"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "Port number is removed if new port is scheme default and existing URL has a non-default port",
|
||||
"href": "http://example.net:8080",
|
||||
"new_value": "example.com:80",
|
||||
"expected": {
|
||||
"href": "http://example.com/",
|
||||
"host": "example.com",
|
||||
"hostname": "example.com",
|
||||
"port": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "Stuff after a / delimiter is ignored",
|
||||
"href": "http://example.net/path",
|
||||
|
@ -790,9 +941,59 @@
|
|||
"host": "example.net",
|
||||
"hostname": "example.net"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "file://y/",
|
||||
"new_value": "x:123",
|
||||
"expected": {
|
||||
"href": "file://y/",
|
||||
"host": "y",
|
||||
"hostname": "y",
|
||||
"port": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "file://y/",
|
||||
"new_value": "loc%41lhost",
|
||||
"expected": {
|
||||
"href": "file:///",
|
||||
"host": "",
|
||||
"hostname": "",
|
||||
"port": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "sc://test@test/",
|
||||
"new_value": "",
|
||||
"expected": {
|
||||
"href": "sc://test@test/",
|
||||
"host": "test",
|
||||
"hostname": "test",
|
||||
"username": "test"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "sc://test:12/",
|
||||
"new_value": "",
|
||||
"expected": {
|
||||
"href": "sc://test:12/",
|
||||
"host": "test:12",
|
||||
"hostname": "test",
|
||||
"port": "12"
|
||||
}
|
||||
}
|
||||
],
|
||||
"hostname": [
|
||||
{
|
||||
"comment": "Non-special scheme",
|
||||
"href": "sc://x/",
|
||||
"new_value": "\u0000",
|
||||
"expected": {
|
||||
"href": "sc://x/",
|
||||
"host": "x",
|
||||
"hostname": "x"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "sc://x/",
|
||||
"new_value": "\u0009",
|
||||
|
@ -820,6 +1021,15 @@
|
|||
"hostname": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "sc://x/",
|
||||
"new_value": " ",
|
||||
"expected": {
|
||||
"href": "sc://x/",
|
||||
"host": "x",
|
||||
"hostname": "x"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "sc://x/",
|
||||
"new_value": "#",
|
||||
|
@ -866,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": {
|
||||
|
@ -1055,6 +1265,46 @@
|
|||
"host": "example.net",
|
||||
"hostname": "example.net"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "file://y/",
|
||||
"new_value": "x:123",
|
||||
"expected": {
|
||||
"href": "file://y/",
|
||||
"host": "y",
|
||||
"hostname": "y",
|
||||
"port": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "file://y/",
|
||||
"new_value": "loc%41lhost",
|
||||
"expected": {
|
||||
"href": "file:///",
|
||||
"host": "",
|
||||
"hostname": "",
|
||||
"port": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "sc://test@test/",
|
||||
"new_value": "",
|
||||
"expected": {
|
||||
"href": "sc://test@test/",
|
||||
"host": "test",
|
||||
"hostname": "test",
|
||||
"username": "test"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "sc://test:12/",
|
||||
"new_value": "",
|
||||
"expected": {
|
||||
"href": "sc://test:12/",
|
||||
"host": "test:12",
|
||||
"hostname": "test",
|
||||
"port": "12"
|
||||
}
|
||||
}
|
||||
],
|
||||
"port": [
|
||||
|
@ -1324,12 +1574,12 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"comment": "UTF-8 percent encoding with the default encode set. Tabs and newlines are removed. Leading or training C0 controls and space are removed.",
|
||||
"comment": "UTF-8 percent encoding with the default encode set. Tabs and newlines are removed.",
|
||||
"href": "a:/",
|
||||
"new_value": "\u0000\u0001\t\n\r\u001f !\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé",
|
||||
"new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé",
|
||||
"expected": {
|
||||
"href": "a:/!%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9",
|
||||
"pathname": "/!%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9"
|
||||
"href": "a:/%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9",
|
||||
"pathname": "/%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1376,6 +1626,33 @@
|
|||
"href": "sc://example.net/%23",
|
||||
"pathname": "/%23"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "File URLs and (back)slashes",
|
||||
"href": "file://monkey/",
|
||||
"new_value": "\\\\",
|
||||
"expected": {
|
||||
"href": "file://monkey/",
|
||||
"pathname": "/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "File URLs and (back)slashes",
|
||||
"href": "file:///unicorn",
|
||||
"new_value": "//\\/",
|
||||
"expected": {
|
||||
"href": "file:///",
|
||||
"pathname": "/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "File URLs and (back)slashes",
|
||||
"href": "file:///unicorn",
|
||||
"new_value": "//monkey/..//",
|
||||
"expected": {
|
||||
"href": "file:///",
|
||||
"pathname": "/"
|
||||
}
|
||||
}
|
||||
],
|
||||
"search": [
|
||||
|
@ -1444,12 +1721,12 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"comment": "UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. Leading or training C0 controls and space are removed.",
|
||||
"comment": "UTF-8 percent encoding with the query encode set. Tabs and newlines are removed.",
|
||||
"href": "a:/",
|
||||
"new_value": "\u0000\u0001\t\n\r\u001f !\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé",
|
||||
"new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé",
|
||||
"expected": {
|
||||
"href": "a:/?!%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9",
|
||||
"search": "?!%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
|
||||
"href": "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9",
|
||||
"search": "?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1512,12 +1789,70 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"comment": "Simple percent-encoding; nuls, tabs, and newlines are removed",
|
||||
"href": "a:/",
|
||||
"new_value": "\u0000\u0001\t\n\r\u001f !\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé",
|
||||
"href": "http://example.net",
|
||||
"new_value": "#foo bar",
|
||||
"expected": {
|
||||
"href": "a:/#!%01%1F !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9",
|
||||
"hash": "#!%01%1F !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
|
||||
"href": "http://example.net/#foo%20bar",
|
||||
"hash": "#foo%20bar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "http://example.net",
|
||||
"new_value": "#foo\"bar",
|
||||
"expected": {
|
||||
"href": "http://example.net/#foo%22bar",
|
||||
"hash": "#foo%22bar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "http://example.net",
|
||||
"new_value": "#foo<bar",
|
||||
"expected": {
|
||||
"href": "http://example.net/#foo%3Cbar",
|
||||
"hash": "#foo%3Cbar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "http://example.net",
|
||||
"new_value": "#foo>bar",
|
||||
"expected": {
|
||||
"href": "http://example.net/#foo%3Ebar",
|
||||
"hash": "#foo%3Ebar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "http://example.net",
|
||||
"new_value": "#foo`bar",
|
||||
"expected": {
|
||||
"href": "http://example.net/#foo%60bar",
|
||||
"hash": "#foo%60bar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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:/#%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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1528,6 +1863,14 @@
|
|||
"href": "http://example.net/#%c3%89t%C3%A9",
|
||||
"hash": "#%c3%89t%C3%A9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "javascript:alert(1)",
|
||||
"new_value": "castle",
|
||||
"expected": {
|
||||
"href": "javascript:alert(1)#castle",
|
||||
"hash": "#castle"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
@ -23,6 +20,49 @@ fn size() {
|
|||
assert_eq!(size_of::<Url>(), size_of::<Option<Url>>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative() {
|
||||
let base: Url = "sc://%C3%B1".parse().unwrap();
|
||||
let url = base.join("/resources/testharness.js").unwrap();
|
||||
assert_eq!(url.as_str(), "sc://%C3%B1/resources/testharness.js");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_empty() {
|
||||
let base: Url = "sc://%C3%B1".parse().unwrap();
|
||||
let url = base.join("").unwrap();
|
||||
assert_eq!(url.as_str(), "sc://%C3%B1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_empty_host() {
|
||||
let mut base: Url = "moz://foo:bar@servo/baz".parse().unwrap();
|
||||
base.set_username("").unwrap();
|
||||
assert_eq!(base.as_str(), "moz://:bar@servo/baz");
|
||||
base.set_host(None).unwrap();
|
||||
assert_eq!(base.as_str(), "moz:/baz");
|
||||
base.set_host(Some("servo")).unwrap();
|
||||
assert_eq!(base.as_str(), "moz://servo/baz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_empty_hostname() {
|
||||
use url::quirks;
|
||||
let mut base: Url = "moz://foo@servo/baz".parse().unwrap();
|
||||
assert!(
|
||||
quirks::set_hostname(&mut base, "").is_err(),
|
||||
"setting an empty hostname to a url with a username should fail"
|
||||
);
|
||||
base = "moz://:pass@servo/baz".parse().unwrap();
|
||||
assert!(
|
||||
quirks::set_hostname(&mut base, "").is_err(),
|
||||
"setting an empty hostname to a url with a password should fail"
|
||||
);
|
||||
base = "moz://servo/baz".parse().unwrap();
|
||||
quirks::set_hostname(&mut base, "").unwrap();
|
||||
assert_eq!(base.as_str(), "moz:///baz");
|
||||
}
|
||||
|
||||
macro_rules! assert_from_file_path {
|
||||
($path: expr) => {
|
||||
assert_from_file_path!($path, $path)
|
||||
|
@ -234,6 +274,9 @@ fn host() {
|
|||
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]
|
||||
|
@ -294,7 +337,7 @@ fn test_serialization() {
|
|||
|
||||
#[test]
|
||||
fn test_form_urlencoded() {
|
||||
let pairs: &[(Cow<str>, Cow<str>)] = &[
|
||||
let pairs: &[(Cow<'_, str>, Cow<'_, str>)] = &[
|
||||
("foo".into(), "é&".into()),
|
||||
("bar".into(), "".into()),
|
||||
("foo".into(), "#".into()),
|
||||
|
@ -315,8 +358,9 @@ fn test_form_serialize() {
|
|||
.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]
|
||||
|
@ -324,8 +368,9 @@ fn form_urlencoded_encoding_override() {
|
|||
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]
|
||||
|
@ -413,9 +458,9 @@ fn test_set_host() {
|
|||
assert_eq!(url.as_str(), "foobar:/hello");
|
||||
|
||||
let mut url = Url::parse("foo://ș").unwrap();
|
||||
assert_eq!(url.as_str(), "foo://%C8%99/");
|
||||
assert_eq!(url.as_str(), "foo://%C8%99");
|
||||
url.set_host(Some("goșu.ro")).unwrap();
|
||||
assert_eq!(url.as_str(), "foo://go%C8%99u.ro/");
|
||||
assert_eq!(url.as_str(), "foo://go%C8%99u.ro");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -472,6 +517,209 @@ fn test_origin_hash() {
|
|||
assert_ne!(hash(&opaque_origin), hash(&other_opaque_origin));
|
||||
}
|
||||
|
||||
#[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) {
|
||||
|
@ -536,6 +784,38 @@ fn test_syntax_violation_callback_lifetimes() {
|
|||
assert_eq!(violation.take(), Some(Backslash));
|
||||
}
|
||||
|
||||
#[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::*;
|
||||
|
@ -550,3 +830,289 @@ fn test_options_reuse() {
|
|||
assert_eq!(url.as_str(), "http://mozilla.org/sub/path");
|
||||
assert_eq!(*violations.borrow(), vec!(ExpectedDoubleSlash, Backslash));
|
||||
}
|
||||
|
||||
/// https://github.com/servo/rust-url/issues/505
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn test_url_from_file_path() {
|
||||
use std::path::PathBuf;
|
||||
use url::Url;
|
||||
|
||||
let p = PathBuf::from("c:///");
|
||||
let u = Url::from_file_path(p).unwrap();
|
||||
let path = u.to_file_path().unwrap();
|
||||
assert_eq!("C:\\", path.to_str().unwrap());
|
||||
}
|
||||
|
||||
/// https://github.com/servo/rust-url/issues/505
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn test_url_from_file_path() {
|
||||
use std::path::PathBuf;
|
||||
use url::Url;
|
||||
|
||||
let p = PathBuf::from("/c:/");
|
||||
let u = Url::from_file_path(p).unwrap();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -215,7 +215,7 @@ impl<'s> Store<'s> {
|
|||
let title = String::from_utf16(&*raw_title)?;
|
||||
url.map(|url| Content::Bookmark {
|
||||
title,
|
||||
url_href: url.into_string(),
|
||||
url_href: url.into(),
|
||||
})
|
||||
}
|
||||
Kind::Folder | Kind::Livemark => {
|
||||
|
|
Загрузка…
Ссылка в новой задаче