зеркало из https://github.com/mozilla/gecko-dev.git
No bug - Revendor rust dependencies
This commit is contained in:
Родитель
77124f8a0d
Коммит
277e04065d
|
@ -0,0 +1 @@
|
|||
{"files":{".travis.yml":"890af214187ffcba4732acb2d1af30d7adb9aade0679e9fdb06baae363240b8e","Cargo.toml":"ec586106c4d0625919a3591fe3ae915043e82c8bfdd1c9e747171ba5e21047e1","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e","Makefile":"bffd75d34654b2955d4f005f1a5e85c821c90becf1a8a52cbe10121972f43148","README.md":"eb3f4694003f408cbe3c7f3e9fbbc71241defb940cc55a816981f0f0f144c8eb","UPGRADING.md":"fbcc2d39bdf17db0745793db6626fcd5c909dddd4ce13b27566cfabece22c368","appveyor.yml":"c78486dbfbe6ebbf3d808afb9a19f7ec18c4704ce451c6305f0716999b70a1a6","docs/.nojekyll":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","docs/404.html":"f61e6271c1ea1aa113b64b356e994595fa548f0433f89948d747503ad22195cd","docs/index.html":"f61e6271c1ea1aa113b64b356e994595fa548f0433f89948d747503ad22195cd","github.png":"b432fd855efe7c430fe6a57ccf83935c1996f03a7cdc8d6e1b34154b8c43f6ec","rust-url-todo":"1192cee7b6cedf2133d97dc6074b593a1d19b0ee13fff6f28d6329855044e575","src/encoding.rs":"f3e109ca8ec5a9130da50cdfb3003530aedb6dd5a440f0790d76b71f6981119c","src/form_urlencoded.rs":"7ccaef7148e4bc2577154c50f8705db3a055b641269e24c22770f06222321e1e","src/host.rs":"281165d732ea87b6f01a98f7c68ffcb284c41f84b3ab6ed674fb8e57022d1019","src/lib.rs":"bd156e8bcfbd44f0cd52c8b394e03ec63fea012c0bf5ca554521352714838605","src/origin.rs":"7071dcc1070ccfae84cdcd43586b84a9706e35a9a099ff4dde128da0909bd0bc","src/parser.rs":"9d30868f0900586fec6f122a0322598a08116ab0b4c4d8caf5c35a720381a73a","src/path_segments.rs":"7bd3142eaa568863ef44e2255c181239141f9eeee337f889b9ffaaeab4ca669d","src/quirks.rs":"1231f965e22bb3632c22993e2a8d4c7470bcb4a8de25d049f31784303f0def03","src/slicing.rs":"4e539886b23945a92094625f3e531a4bff40daa44240b5d19ee8577478c4f7fe","tests/data.rs":"c333766897f6492fb6583ab5c8a511973b7a55f58ca550799432343da64d5ca7","tests/setters_tests.json":"ebcbdb52e9a4b5a565f8806d52ebc610d46a34df883e10b0be080d026468ff73","tests/unit.rs":"c2f206f433be619414d761d358a2a4a5a46cfe8a4fea5339adec5e9937d78de2","tests/urltestdata.json":"430c74aa3a31afaa57a92805544e00825f4dffe2def98c1e3c212c3db80268af"},"package":"eeb819346883532a271eb626deb43c4a1bb4c4dd47c519bd78137c3e72a4fe27"}
|
|
@ -0,0 +1,9 @@
|
|||
language: rust
|
||||
rust:
|
||||
- nightly
|
||||
- beta
|
||||
- stable
|
||||
- 1.17.0
|
||||
script: make test
|
||||
notifications:
|
||||
webhooks: http://build.servo.org:54856/travis
|
|
@ -0,0 +1,49 @@
|
|||
[package]
|
||||
|
||||
name = "url"
|
||||
# When updating version, also modify html_root_url in the lib.rs
|
||||
version = "1.5.1"
|
||||
authors = ["The rust-url developers"]
|
||||
|
||||
description = "URL library for Rust, based on the WHATWG URL Standard"
|
||||
documentation = "https://docs.rs/url"
|
||||
repository = "https://github.com/servo/rust-url"
|
||||
readme = "README.md"
|
||||
keywords = ["url", "parser"]
|
||||
categories = ["parser-implementations", "web-programming", "encoding"]
|
||||
license = "MIT/Apache-2.0"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "servo/rust-url" }
|
||||
appveyor = { repository = "servo/rust-url" }
|
||||
|
||||
[workspace]
|
||||
members = [".", "idna", "percent_encoding", "url_serde"]
|
||||
|
||||
[[test]]
|
||||
name = "unit"
|
||||
|
||||
[[test]]
|
||||
name = "data"
|
||||
harness = false
|
||||
|
||||
[lib]
|
||||
test = false
|
||||
|
||||
[dev-dependencies]
|
||||
rustc-test = "0.1"
|
||||
rustc-serialize = "0.3"
|
||||
serde_json = ">=0.6.1, <0.9"
|
||||
|
||||
[features]
|
||||
query_encoding = ["encoding"]
|
||||
heap_size = ["heapsize"]
|
||||
|
||||
[dependencies]
|
||||
encoding = {version = "0.2", optional = true}
|
||||
heapsize = {version = ">=0.1.1, <0.5", optional = true}
|
||||
idna = { version = "0.1.0", path = "./idna" }
|
||||
matches = "0.1"
|
||||
percent-encoding = { version = "1.0.0", path = "./percent_encoding" }
|
||||
rustc-serialize = {version = "0.3", optional = true}
|
||||
serde = {version = ">=0.6.1, <0.9", optional = true}
|
|
@ -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.
|
|
@ -0,0 +1,6 @@
|
|||
test:
|
||||
cargo test --features "query_encoding serde rustc-serialize heapsize"
|
||||
(cd idna && cargo test)
|
||||
(cd url_serde && cargo test)
|
||||
|
||||
.PHONY: test
|
|
@ -0,0 +1,10 @@
|
|||
rust-url
|
||||
========
|
||||
|
||||
[![Travis build Status](https://travis-ci.org/servo/rust-url.svg?branch=master)](https://travis-ci.org/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.
|
|
@ -0,0 +1,263 @@
|
|||
# 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.
|
|
@ -0,0 +1,13 @@
|
|||
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
|
|
@ -0,0 +1,3 @@
|
|||
<meta http-equiv="refresh" content="0; url=https://docs.rs/url/">
|
||||
<link rel="canonical" href="https://docs.rs/url/">
|
||||
<a href="https://docs.rs/url/">Moved to docs.rs</a>
|
|
@ -0,0 +1,3 @@
|
|||
<meta http-equiv="refresh" content="0; url=https://docs.rs/url/">
|
||||
<link rel="canonical" href="https://docs.rs/url/">
|
||||
<a href="https://docs.rs/url/">Moved to docs.rs</a>
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 7.6 KiB |
|
@ -0,0 +1,14 @@
|
|||
* standalone path parsing?
|
||||
* Test setters
|
||||
* Test trim C0/space
|
||||
* Test remove tab & newline
|
||||
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_path_segments() {
|
||||
let mut url = Url::parse("http://example.net").unwrap();
|
||||
url.push_path_segment("foo").unwrap();
|
||||
url.extend_path_segments(&["bar", "b/az"]).unwrap();
|
||||
assert_eq!(url.as_str(), "http://example.net/foo");
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
// Copyright 2013-2014 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.
|
||||
|
||||
|
||||
//! Abstraction that conditionally compiles either to rust-encoding,
|
||||
//! or to only support UTF-8.
|
||||
|
||||
#[cfg(feature = "query_encoding")] extern crate encoding;
|
||||
|
||||
use std::borrow::Cow;
|
||||
#[cfg(feature = "query_encoding")] use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
#[cfg(feature = "query_encoding")] use self::encoding::types::{DecoderTrap, EncoderTrap};
|
||||
#[cfg(feature = "query_encoding")] use self::encoding::label::encoding_from_whatwg_label;
|
||||
#[cfg(feature = "query_encoding")] pub use self::encoding::types::EncodingRef;
|
||||
|
||||
#[cfg(feature = "query_encoding")]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct EncodingOverride {
|
||||
/// `None` means UTF-8.
|
||||
encoding: Option<EncodingRef>
|
||||
}
|
||||
|
||||
#[cfg(feature = "query_encoding")]
|
||||
impl EncodingOverride {
|
||||
pub fn from_opt_encoding(encoding: Option<EncodingRef>) -> Self {
|
||||
encoding.map(Self::from_encoding).unwrap_or_else(Self::utf8)
|
||||
}
|
||||
|
||||
pub fn from_encoding(encoding: EncodingRef) -> Self {
|
||||
EncodingOverride {
|
||||
encoding: if encoding.name() == "utf-8" { None } else { Some(encoding) }
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn utf8() -> Self {
|
||||
EncodingOverride { encoding: None }
|
||||
}
|
||||
|
||||
pub fn lookup(label: &[u8]) -> Option<Self> {
|
||||
// Don't use String::from_utf8_lossy since no encoding label contains U+FFFD
|
||||
// https://encoding.spec.whatwg.org/#names-and-labels
|
||||
::std::str::from_utf8(label)
|
||||
.ok()
|
||||
.and_then(encoding_from_whatwg_label)
|
||||
.map(Self::from_encoding)
|
||||
}
|
||||
|
||||
/// https://encoding.spec.whatwg.org/#get-an-output-encoding
|
||||
pub fn to_output_encoding(self) -> Self {
|
||||
if let Some(encoding) = self.encoding {
|
||||
if matches!(encoding.name(), "utf-16le" | "utf-16be") {
|
||||
return Self::utf8()
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_utf8(&self) -> bool {
|
||||
self.encoding.is_none()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self.encoding {
|
||||
Some(encoding) => encoding.name(),
|
||||
None => "utf-8",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode<'a>(&self, input: Cow<'a, [u8]>) -> Cow<'a, str> {
|
||||
match self.encoding {
|
||||
// `encoding.decode` never returns `Err` when called with `DecoderTrap::Replace`
|
||||
Some(encoding) => encoding.decode(&input, DecoderTrap::Replace).unwrap().into(),
|
||||
None => decode_utf8_lossy(input),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode<'a>(&self, input: Cow<'a, str>) -> Cow<'a, [u8]> {
|
||||
match self.encoding {
|
||||
// `encoding.encode` never returns `Err` when called with `EncoderTrap::NcrEscape`
|
||||
Some(encoding) => Cow::Owned(encoding.encode(&input, EncoderTrap::NcrEscape).unwrap()),
|
||||
None => encode_utf8(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "query_encoding")]
|
||||
impl Debug for EncodingOverride {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "EncodingOverride {{ encoding: ")?;
|
||||
match self.encoding {
|
||||
Some(e) => write!(f, "{} }}", e.name()),
|
||||
None => write!(f, "None }}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "query_encoding"))]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct EncodingOverride;
|
||||
|
||||
#[cfg(not(feature = "query_encoding"))]
|
||||
impl EncodingOverride {
|
||||
#[inline]
|
||||
pub fn utf8() -> Self {
|
||||
EncodingOverride
|
||||
}
|
||||
|
||||
pub fn decode<'a>(&self, input: Cow<'a, [u8]>) -> Cow<'a, str> {
|
||||
decode_utf8_lossy(input)
|
||||
}
|
||||
|
||||
pub fn encode<'a>(&self, input: Cow<'a, str>) -> Cow<'a, [u8]> {
|
||||
encode_utf8(input)
|
||||
}
|
||||
}
|
||||
|
||||
pub 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode_utf8(input: Cow<str>) -> Cow<[u8]> {
|
||||
match input {
|
||||
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
|
||||
Cow::Owned(s) => Cow::Owned(s.into_bytes())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,369 @@
|
|||
// Copyright 2013-2016 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.
|
||||
|
||||
//! Parser and serializer for the [`application/x-www-form-urlencoded` syntax](
|
||||
//! http://url.spec.whatwg.org/#application/x-www-form-urlencoded),
|
||||
//! as used by HTML forms.
|
||||
//!
|
||||
//! Converts between a string (such as an URL’s query string)
|
||||
//! and a sequence of (name, value) pairs.
|
||||
|
||||
use encoding::EncodingOverride;
|
||||
use percent_encoding::{percent_encode_byte, percent_decode};
|
||||
use std::borrow::{Borrow, Cow};
|
||||
use std::str;
|
||||
|
||||
|
||||
/// Convert a byte string in the `application/x-www-form-urlencoded` syntax
|
||||
/// into a iterator of (name, value) pairs.
|
||||
///
|
||||
/// Use `parse(input.as_bytes())` to parse a `&str` string.
|
||||
///
|
||||
/// The names and values are percent-decoded. For instance, `%23first=%25try%25` will be
|
||||
/// converted to `[("#first", "%try%")]`.
|
||||
#[inline]
|
||||
pub fn parse(input: &[u8]) -> Parse {
|
||||
Parse {
|
||||
input: input,
|
||||
encoding: EncodingOverride::utf8(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Convert a byte string in the `application/x-www-form-urlencoded` syntax
|
||||
/// into a iterator of (name, value) pairs.
|
||||
///
|
||||
/// Use `parse(input.as_bytes())` to parse a `&str` string.
|
||||
///
|
||||
/// This function is only available if the `query_encoding`
|
||||
/// [feature](http://doc.crates.io/manifest.html#the-features-section]) is enabled.
|
||||
///
|
||||
/// Arguments:
|
||||
///
|
||||
/// * `encoding_override`: The character encoding each name and values is decoded as
|
||||
/// after percent-decoding. Defaults to UTF-8.
|
||||
/// `EncodingRef` is defined in [rust-encoding](https://github.com/lifthrasiir/rust-encoding).
|
||||
/// * `use_charset`: The *use _charset_ flag*. If in doubt, set to `false`.
|
||||
#[cfg(feature = "query_encoding")]
|
||||
pub fn parse_with_encoding<'a>(input: &'a [u8],
|
||||
encoding_override: Option<::encoding::EncodingRef>,
|
||||
use_charset: bool)
|
||||
-> Result<Parse<'a>, ()> {
|
||||
use std::ascii::AsciiExt;
|
||||
|
||||
let mut encoding = EncodingOverride::from_opt_encoding(encoding_override);
|
||||
if !(encoding.is_utf8() || input.is_ascii()) {
|
||||
return Err(())
|
||||
}
|
||||
if use_charset {
|
||||
for sequence in input.split(|&b| b == b'&') {
|
||||
// No '+' in "_charset_" to replace with ' '.
|
||||
if sequence.starts_with(b"_charset_=") {
|
||||
let value = &sequence[b"_charset_=".len()..];
|
||||
// Skip replacing '+' with ' ' in value since no encoding label contains either:
|
||||
// https://encoding.spec.whatwg.org/#names-and-labels
|
||||
if let Some(e) = EncodingOverride::lookup(value) {
|
||||
encoding = e;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Parse {
|
||||
input: input,
|
||||
encoding: encoding,
|
||||
})
|
||||
}
|
||||
|
||||
/// The return type of `parse()`.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Parse<'a> {
|
||||
input: &'a [u8],
|
||||
encoding: EncodingOverride,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Parse<'a> {
|
||||
type Item = (Cow<'a, str>, Cow<'a, str>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
if self.input.is_empty() {
|
||||
return None
|
||||
}
|
||||
let mut split2 = self.input.splitn(2, |&b| b == b'&');
|
||||
let sequence = split2.next().unwrap();
|
||||
self.input = split2.next().unwrap_or(&[][..]);
|
||||
if sequence.is_empty() {
|
||||
continue
|
||||
}
|
||||
let mut split2 = sequence.splitn(2, |&b| b == b'=');
|
||||
let name = split2.next().unwrap();
|
||||
let value = split2.next().unwrap_or(&[][..]);
|
||||
return Some((
|
||||
decode(name, self.encoding),
|
||||
decode(value, self.encoding),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode(input: &[u8], encoding: EncodingOverride) -> Cow<str> {
|
||||
let replaced = replace_plus(input);
|
||||
encoding.decode(match percent_decode(&replaced).if_any() {
|
||||
Some(vec) => Cow::Owned(vec),
|
||||
None => replaced,
|
||||
})
|
||||
}
|
||||
|
||||
/// Replace b'+' with b' '
|
||||
fn replace_plus(input: &[u8]) -> Cow<[u8]> {
|
||||
match input.iter().position(|&b| b == b'+') {
|
||||
None => Cow::Borrowed(input),
|
||||
Some(first_position) => {
|
||||
let mut replaced = input.to_owned();
|
||||
replaced[first_position] = b' ';
|
||||
for byte in &mut replaced[first_position + 1..] {
|
||||
if *byte == b'+' {
|
||||
*byte = b' ';
|
||||
}
|
||||
}
|
||||
Cow::Owned(replaced)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> {
|
||||
/// Return a new iterator that yields pairs of `String` instead of pairs of `Cow<str>`.
|
||||
pub fn into_owned(self) -> ParseIntoOwned<'a> {
|
||||
ParseIntoOwned { inner: self }
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `Parse`, but yields pairs of `String` instead of pairs of `Cow<str>`.
|
||||
#[derive(Debug)]
|
||||
pub struct ParseIntoOwned<'a> {
|
||||
inner: Parse<'a>
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ParseIntoOwned<'a> {
|
||||
type Item = (String, String);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next().map(|(k, v)| (k.into_owned(), v.into_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
/// The [`application/x-www-form-urlencoded` byte serializer](
|
||||
/// https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer).
|
||||
///
|
||||
/// Return an iterator of `&str` slices.
|
||||
pub fn byte_serialize(input: &[u8]) -> ByteSerialize {
|
||||
ByteSerialize {
|
||||
bytes: input,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return value of `byte_serialize()`.
|
||||
#[derive(Debug)]
|
||||
pub struct ByteSerialize<'a> {
|
||||
bytes: &'a [u8],
|
||||
}
|
||||
|
||||
fn byte_serialized_unchanged(byte: u8) -> bool {
|
||||
matches!(byte, b'*' | b'-' | b'.' | b'0' ... b'9' | b'A' ... b'Z' | b'_' | b'a' ... b'z')
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ByteSerialize<'a> {
|
||||
type Item = &'a str;
|
||||
|
||||
fn next(&mut self) -> Option<&'a str> {
|
||||
if let Some((&first, tail)) = self.bytes.split_first() {
|
||||
if !byte_serialized_unchanged(first) {
|
||||
self.bytes = tail;
|
||||
return Some(if first == b' ' { "+" } else { percent_encode_byte(first) })
|
||||
}
|
||||
let position = tail.iter().position(|&b| !byte_serialized_unchanged(b));
|
||||
let (unchanged_slice, remaining) = match position {
|
||||
// 1 for first_byte + i unchanged in tail
|
||||
Some(i) => self.bytes.split_at(1 + i),
|
||||
None => (self.bytes, &[][..]),
|
||||
};
|
||||
self.bytes = remaining;
|
||||
Some(unsafe { str::from_utf8_unchecked(unchanged_slice) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
if self.bytes.is_empty() {
|
||||
(0, Some(0))
|
||||
} else {
|
||||
(1, Some(self.bytes.len()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The [`application/x-www-form-urlencoded` serializer](
|
||||
/// https://url.spec.whatwg.org/#concept-urlencoded-serializer).
|
||||
#[derive(Debug)]
|
||||
pub struct Serializer<T: Target> {
|
||||
target: Option<T>,
|
||||
start_position: usize,
|
||||
encoding: EncodingOverride,
|
||||
}
|
||||
|
||||
pub trait Target {
|
||||
fn as_mut_string(&mut self) -> &mut String;
|
||||
fn finish(self) -> Self::Finished;
|
||||
type Finished;
|
||||
}
|
||||
|
||||
impl Target for String {
|
||||
fn as_mut_string(&mut self) -> &mut String { self }
|
||||
fn finish(self) -> Self { self }
|
||||
type Finished = Self;
|
||||
}
|
||||
|
||||
impl<'a> Target for &'a mut String {
|
||||
fn as_mut_string(&mut self) -> &mut String { &mut **self }
|
||||
fn finish(self) -> Self { self }
|
||||
type Finished = Self;
|
||||
}
|
||||
|
||||
// `as_mut_string` string here exposes the internal serialization of an `Url`,
|
||||
// which should not be exposed to users.
|
||||
// We achieve that by not giving users direct access to `UrlQuery`:
|
||||
// * Its fields are private
|
||||
// (and so can not be constructed with struct literal syntax outside of this crate),
|
||||
// * It has no constructor
|
||||
// * It is only visible (on the type level) to users in the return type of
|
||||
// `Url::query_pairs_mut` which is `Serializer<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.serialization }
|
||||
fn finish(self) -> &'a mut ::Url { self.url }
|
||||
type Finished = &'a mut ::Url;
|
||||
}
|
||||
|
||||
impl<T: Target> Serializer<T> {
|
||||
/// Create a new `application/x-www-form-urlencoded` serializer for the given target.
|
||||
///
|
||||
/// If the target is non-empty,
|
||||
/// its content is assumed to already be in `application/x-www-form-urlencoded` syntax.
|
||||
pub fn new(target: T) -> Self {
|
||||
Self::for_suffix(target, 0)
|
||||
}
|
||||
|
||||
/// Create a new `application/x-www-form-urlencoded` serializer
|
||||
/// for a suffix of the given target.
|
||||
///
|
||||
/// If that suffix is non-empty,
|
||||
/// its content is assumed to already be in `application/x-www-form-urlencoded` syntax.
|
||||
pub fn for_suffix(mut target: T, start_position: usize) -> Self {
|
||||
&target.as_mut_string()[start_position..]; // Panic if out of bounds
|
||||
Serializer {
|
||||
target: Some(target),
|
||||
start_position: start_position,
|
||||
encoding: EncodingOverride::utf8(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove any existing name/value pair.
|
||||
///
|
||||
/// Panics if called after `.finish()`.
|
||||
pub fn clear(&mut self) -> &mut Self {
|
||||
string(&mut self.target).truncate(self.start_position);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the character encoding to be used for names and values before percent-encoding.
|
||||
#[cfg(feature = "query_encoding")]
|
||||
pub fn encoding_override(&mut self, new: Option<::encoding::EncodingRef>) -> &mut Self {
|
||||
self.encoding = EncodingOverride::from_opt_encoding(new).to_output_encoding();
|
||||
self
|
||||
}
|
||||
|
||||
/// Serialize and append a name/value pair.
|
||||
///
|
||||
/// Panics if called after `.finish()`.
|
||||
pub fn append_pair(&mut self, name: &str, value: &str) -> &mut Self {
|
||||
append_pair(string(&mut self.target), self.start_position, self.encoding, name, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Serialize and append a number of name/value pairs.
|
||||
///
|
||||
/// This simply calls `append_pair` repeatedly.
|
||||
/// This can be more convenient, so the user doesn’t need to introduce a block
|
||||
/// to limit the scope of `Serializer`’s borrow of its string.
|
||||
///
|
||||
/// Panics if called after `.finish()`.
|
||||
pub fn extend_pairs<I, K, V>(&mut self, iter: I) -> &mut Self
|
||||
where I: IntoIterator, I::Item: Borrow<(K, V)>, K: AsRef<str>, V: AsRef<str> {
|
||||
{
|
||||
let string = string(&mut self.target);
|
||||
for pair in iter {
|
||||
let &(ref k, ref v) = pair.borrow();
|
||||
append_pair(string, self.start_position, self.encoding, k.as_ref(), v.as_ref());
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a name/value pair whose name is `_charset_`
|
||||
/// and whose value is the character encoding’s name.
|
||||
/// (See the `encoding_override()` method.)
|
||||
///
|
||||
/// Panics if called after `.finish()`.
|
||||
#[cfg(feature = "query_encoding")]
|
||||
pub fn append_charset(&mut self) -> &mut Self {
|
||||
{
|
||||
let string = string(&mut self.target);
|
||||
append_separator_if_needed(string, self.start_position);
|
||||
string.push_str("_charset_=");
|
||||
string.push_str(self.encoding.name());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// If this serializer was constructed with a string, take and return that string.
|
||||
///
|
||||
/// ```rust
|
||||
/// use url::form_urlencoded;
|
||||
/// let encoded: String = form_urlencoded::Serializer::new(String::new())
|
||||
/// .append_pair("foo", "bar & baz")
|
||||
/// .append_pair("saison", "Été+hiver")
|
||||
/// .finish();
|
||||
/// assert_eq!(encoded, "foo=bar+%26+baz&saison=%C3%89t%C3%A9%2Bhiver");
|
||||
/// ```
|
||||
///
|
||||
/// Panics if called more than once.
|
||||
pub fn finish(&mut self) -> T::Finished {
|
||||
self.target.take().expect("url::form_urlencoded::Serializer double finish").finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn append_separator_if_needed(string: &mut String, start_position: usize) {
|
||||
if string.len() > start_position {
|
||||
string.push('&')
|
||||
}
|
||||
}
|
||||
|
||||
fn string<T: Target>(target: &mut Option<T>) -> &mut String {
|
||||
target.as_mut().expect("url::form_urlencoded::Serializer finished").as_mut_string()
|
||||
}
|
||||
|
||||
fn append_pair(string: &mut String, start_position: usize, encoding: EncodingOverride,
|
||||
name: &str, value: &str) {
|
||||
append_separator_if_needed(string, start_position);
|
||||
string.extend(byte_serialize(&encoding.encode(name.into())));
|
||||
string.push('=');
|
||||
string.extend(byte_serialize(&encoding.encode(value.into())));
|
||||
}
|
|
@ -0,0 +1,502 @@
|
|||
// Copyright 2013-2016 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.
|
||||
|
||||
#[cfg(feature = "heapsize")] use heapsize::HeapSizeOf;
|
||||
use std::cmp;
|
||||
use std::fmt::{self, Formatter};
|
||||
use std::io;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
|
||||
use std::vec;
|
||||
use parser::{ParseResult, ParseError};
|
||||
use percent_encoding::percent_decode;
|
||||
use idna;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum HostInternal {
|
||||
None,
|
||||
Domain,
|
||||
Ipv4(Ipv4Addr),
|
||||
Ipv6(Ipv6Addr),
|
||||
}
|
||||
|
||||
#[cfg(feature = "heapsize")]
|
||||
known_heap_size!(0, HostInternal);
|
||||
|
||||
#[cfg(feature="serde")]
|
||||
impl ::serde::Serialize for HostInternal {
|
||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: ::serde::Serializer {
|
||||
// This doesn’t use `derive` because that involves
|
||||
// large dependencies (that take a long time to build), and
|
||||
// either Macros 1.1 which are not stable yet or a cumbersome build script.
|
||||
//
|
||||
// Implementing `Serializer` correctly for an enum is tricky,
|
||||
// so let’s use existing enums that already do.
|
||||
use std::net::IpAddr;
|
||||
match *self {
|
||||
HostInternal::None => None,
|
||||
HostInternal::Domain => Some(None),
|
||||
HostInternal::Ipv4(addr) => Some(Some(IpAddr::V4(addr))),
|
||||
HostInternal::Ipv6(addr) => Some(Some(IpAddr::V6(addr))),
|
||||
}.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature="serde")]
|
||||
impl ::serde::Deserialize for HostInternal {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error> where D: ::serde::Deserializer {
|
||||
use std::net::IpAddr;
|
||||
Ok(match ::serde::Deserialize::deserialize(deserializer)? {
|
||||
None => HostInternal::None,
|
||||
Some(None) => HostInternal::Domain,
|
||||
Some(Some(IpAddr::V4(addr))) => HostInternal::Ipv4(addr),
|
||||
Some(Some(IpAddr::V6(addr))) => HostInternal::Ipv6(addr),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> From<Host<S>> for HostInternal {
|
||||
fn from(host: Host<S>) -> HostInternal {
|
||||
match host {
|
||||
Host::Domain(_) => HostInternal::Domain,
|
||||
Host::Ipv4(address) => HostInternal::Ipv4(address),
|
||||
Host::Ipv6(address) => HostInternal::Ipv6(address),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The host name of an URL.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, 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.
|
||||
Domain(S),
|
||||
|
||||
/// An IPv4 address.
|
||||
/// `Url::host_str` returns the serialization of this address,
|
||||
/// as four decimal integers separated by `.` dots.
|
||||
Ipv4(Ipv4Addr),
|
||||
|
||||
/// An IPv6 address.
|
||||
/// `Url::host_str` returns the serialization of that address between `[` and `]` brackets,
|
||||
/// in the format per [RFC 5952 *A Recommendation
|
||||
/// for IPv6 Address Text Representation*](https://tools.ietf.org/html/rfc5952):
|
||||
/// lowercase hexadecimal with maximal `::` compression.
|
||||
Ipv6(Ipv6Addr),
|
||||
}
|
||||
|
||||
#[cfg(feature="serde")]
|
||||
impl<S: ::serde::Serialize> ::serde::Serialize for Host<S> {
|
||||
fn serialize<R>(&self, serializer: &mut R) -> Result<(), R::Error> where R: ::serde::Serializer {
|
||||
use std::net::IpAddr;
|
||||
match *self {
|
||||
Host::Domain(ref s) => Ok(s),
|
||||
Host::Ipv4(addr) => Err(IpAddr::V4(addr)),
|
||||
Host::Ipv6(addr) => Err(IpAddr::V6(addr)),
|
||||
}.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature="serde")]
|
||||
impl<S: ::serde::Deserialize> ::serde::Deserialize for Host<S> {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error> where D: ::serde::Deserializer {
|
||||
use std::net::IpAddr;
|
||||
Ok(match ::serde::Deserialize::deserialize(deserializer)? {
|
||||
Ok(s) => Host::Domain(s),
|
||||
Err(IpAddr::V4(addr)) => Host::Ipv4(addr),
|
||||
Err(IpAddr::V6(addr)) => Host::Ipv6(addr),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "heapsize")]
|
||||
impl<S: HeapSizeOf> HeapSizeOf for Host<S> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
match *self {
|
||||
Host::Domain(ref s) => s.heap_size_of_children(),
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Host<&'a str> {
|
||||
/// Return a copy of `self` that owns an allocated `String` but does not borrow an `&Url`.
|
||||
pub fn to_owned(&self) -> Host<String> {
|
||||
match *self {
|
||||
Host::Domain(domain) => Host::Domain(domain.to_owned()),
|
||||
Host::Ipv4(address) => Host::Ipv4(address),
|
||||
Host::Ipv6(address) => Host::Ipv6(address),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Host<String> {
|
||||
/// Parse a host: either an IPv6 address in [] square brackets, or a domain.
|
||||
///
|
||||
/// https://url.spec.whatwg.org/#host-parsing
|
||||
pub fn parse(input: &str) -> Result<Self, ParseError> {
|
||||
if input.starts_with('[') {
|
||||
if !input.ends_with(']') {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
return parse_ipv6addr(&input[1..input.len() - 1]).map(Host::Ipv6)
|
||||
}
|
||||
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 let Some(address) = parse_ipv4addr(&domain)? {
|
||||
Ok(Host::Ipv4(address))
|
||||
} else {
|
||||
Ok(Host::Domain(domain.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<str>> fmt::Display for Host<S> {
|
||||
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),
|
||||
Host::Ipv6(ref addr) => {
|
||||
f.write_str("[")?;
|
||||
write_ipv6(addr, f)?;
|
||||
f.write_str("]")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This mostly exists because coherence rules don’t allow us to implement
|
||||
/// `ToSocketAddrs for (Host<S>, u16)`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HostAndPort<S=String> {
|
||||
pub host: Host<S>,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
impl<'a> HostAndPort<&'a str> {
|
||||
/// Return a copy of `self` that owns an allocated `String` but does not borrow an `&Url`.
|
||||
pub fn to_owned(&self) -> HostAndPort<String> {
|
||||
HostAndPort {
|
||||
host: self.host.to_owned(),
|
||||
port: self.port
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<str>> fmt::Display for HostAndPort<S> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
self.host.fmt(f)?;
|
||||
f.write_str(":")?;
|
||||
self.port.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<S: AsRef<str>> ToSocketAddrs for HostAndPort<S> {
|
||||
type Iter = SocketAddrs;
|
||||
|
||||
fn to_socket_addrs(&self) -> io::Result<Self::Iter> {
|
||||
let port = self.port;
|
||||
match self.host {
|
||||
Host::Domain(ref domain) => Ok(SocketAddrs {
|
||||
// FIXME: use std::net::lookup_host when it’s stable.
|
||||
state: SocketAddrsState::Domain((domain.as_ref(), port).to_socket_addrs()?)
|
||||
}),
|
||||
Host::Ipv4(address) => Ok(SocketAddrs {
|
||||
state: SocketAddrsState::One(SocketAddr::V4(SocketAddrV4::new(address, port)))
|
||||
}),
|
||||
Host::Ipv6(address) => Ok(SocketAddrs {
|
||||
state: SocketAddrsState::One(SocketAddr::V6(SocketAddrV6::new(address, port, 0, 0)))
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Socket addresses for an URL.
|
||||
#[derive(Debug)]
|
||||
pub struct SocketAddrs {
|
||||
state: SocketAddrsState
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SocketAddrsState {
|
||||
Domain(vec::IntoIter<SocketAddr>),
|
||||
One(SocketAddr),
|
||||
Done,
|
||||
}
|
||||
|
||||
impl Iterator for SocketAddrs {
|
||||
type Item = SocketAddr;
|
||||
fn next(&mut self) -> Option<SocketAddr> {
|
||||
match self.state {
|
||||
SocketAddrsState::Domain(ref mut iter) => iter.next(),
|
||||
SocketAddrsState::One(s) => {
|
||||
self.state = SocketAddrsState::Done;
|
||||
Some(s)
|
||||
}
|
||||
SocketAddrsState::Done => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
while i < 8 {
|
||||
if i == compress_start {
|
||||
f.write_str(":")?;
|
||||
if i == 0 {
|
||||
f.write_str(":")?;
|
||||
}
|
||||
if compress_end < 8 {
|
||||
i = compress_end;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
write!(f, "{:x}", segments[i as usize])?;
|
||||
if i < 7 {
|
||||
f.write_str(":")?;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#concept-ipv6-serializer step 2 and 3
|
||||
fn longest_zero_sequence(pieces: &[u16; 8]) -> (isize, isize) {
|
||||
let mut longest = -1;
|
||||
let mut longest_length = -1;
|
||||
let mut start = -1;
|
||||
macro_rules! finish_sequence(
|
||||
($end: expr) => {
|
||||
if start >= 0 {
|
||||
let length = $end - start;
|
||||
if length > longest_length {
|
||||
longest = start;
|
||||
longest_length = length;
|
||||
}
|
||||
}
|
||||
};
|
||||
);
|
||||
for i in 0..8 {
|
||||
if pieces[i as usize] == 0 {
|
||||
if start < 0 {
|
||||
start = i;
|
||||
}
|
||||
} else {
|
||||
finish_sequence!(i);
|
||||
start = -1;
|
||||
}
|
||||
}
|
||||
finish_sequence!(8);
|
||||
// https://url.spec.whatwg.org/#concept-ipv6-serializer
|
||||
// step 3: ignore lone zeroes
|
||||
if longest_length < 2 {
|
||||
(-1, -2)
|
||||
} else {
|
||||
(longest, longest + longest_length)
|
||||
}
|
||||
}
|
||||
|
||||
/// https://url.spec.whatwg.org/#ipv4-number-parser
|
||||
fn parse_ipv4number(mut input: &str) -> Result<u32, ()> {
|
||||
let mut r = 10;
|
||||
if input.starts_with("0x") || input.starts_with("0X") {
|
||||
input = &input[2..];
|
||||
r = 16;
|
||||
} else if input.len() >= 2 && input.starts_with('0') {
|
||||
input = &input[1..];
|
||||
r = 8;
|
||||
}
|
||||
if input.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
if input.starts_with('+') {
|
||||
return Err(())
|
||||
}
|
||||
match u32::from_str_radix(input, r) {
|
||||
Ok(number) => Ok(number),
|
||||
Err(_) => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// https://url.spec.whatwg.org/#concept-ipv4-parser
|
||||
fn parse_ipv4addr(input: &str) -> ParseResult<Option<Ipv4Addr>> {
|
||||
if input.is_empty() {
|
||||
return Ok(None)
|
||||
}
|
||||
let mut parts: Vec<&str> = input.split('.').collect();
|
||||
if parts.last() == Some(&"") {
|
||||
parts.pop();
|
||||
}
|
||||
if parts.len() > 4 {
|
||||
return Ok(None);
|
||||
}
|
||||
let mut numbers: Vec<u32> = Vec::new();
|
||||
for part in parts {
|
||||
if part == "" {
|
||||
return Ok(None);
|
||||
}
|
||||
if let Ok(n) = parse_ipv4number(part) {
|
||||
numbers.push(n);
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
let mut ipv4 = numbers.pop().expect("a non-empty list of numbers");
|
||||
// Equivalent to: ipv4 >= 256 ** (4 − numbers.len())
|
||||
if ipv4 > u32::max_value() >> (8 * numbers.len() as u32) {
|
||||
return Err(ParseError::InvalidIpv4Address);
|
||||
}
|
||||
if numbers.iter().any(|x| *x > 255) {
|
||||
return Err(ParseError::InvalidIpv4Address);
|
||||
}
|
||||
for (counter, n) in numbers.iter().enumerate() {
|
||||
ipv4 += n << (8 * (3 - counter as u32))
|
||||
}
|
||||
Ok(Some(Ipv4Addr::from(ipv4)))
|
||||
}
|
||||
|
||||
/// https://url.spec.whatwg.org/#concept-ipv6-parser
|
||||
fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
|
||||
let input = input.as_bytes();
|
||||
let len = input.len();
|
||||
let mut is_ip_v4 = false;
|
||||
let mut pieces = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||
let mut piece_pointer = 0;
|
||||
let mut compress_pointer = None;
|
||||
let mut i = 0;
|
||||
|
||||
if len < 2 {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
|
||||
if input[0] == b':' {
|
||||
if input[1] != b':' {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
i = 2;
|
||||
piece_pointer = 1;
|
||||
compress_pointer = Some(1);
|
||||
}
|
||||
|
||||
while i < len {
|
||||
if piece_pointer == 8 {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
if input[i] == b':' {
|
||||
if compress_pointer.is_some() {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
i += 1;
|
||||
piece_pointer += 1;
|
||||
compress_pointer = Some(piece_pointer);
|
||||
continue
|
||||
}
|
||||
let start = i;
|
||||
let end = cmp::min(len, start + 4);
|
||||
let mut value = 0u16;
|
||||
while i < end {
|
||||
match (input[i] as char).to_digit(16) {
|
||||
Some(digit) => {
|
||||
value = value * 0x10 + digit as u16;
|
||||
i += 1;
|
||||
},
|
||||
None => break
|
||||
}
|
||||
}
|
||||
if i < len {
|
||||
match input[i] {
|
||||
b'.' => {
|
||||
if i == start {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
i = start;
|
||||
is_ip_v4 = true;
|
||||
},
|
||||
b':' => {
|
||||
i += 1;
|
||||
if i == len {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
},
|
||||
_ => return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
}
|
||||
if is_ip_v4 {
|
||||
break
|
||||
}
|
||||
pieces[piece_pointer] = value;
|
||||
piece_pointer += 1;
|
||||
}
|
||||
|
||||
if is_ip_v4 {
|
||||
if piece_pointer > 6 {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
let mut dots_seen = 0;
|
||||
while i < len {
|
||||
let mut value = None;
|
||||
while i < len {
|
||||
let digit = match input[i] {
|
||||
c @ b'0' ... b'9' => c - b'0',
|
||||
_ => break
|
||||
};
|
||||
match value {
|
||||
None => value = Some(digit as u16),
|
||||
Some(0) => return Err(ParseError::InvalidIpv6Address), // No leading zero
|
||||
Some(ref mut v) => {
|
||||
*v = *v * 10 + digit as u16;
|
||||
if *v > 255 {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
if dots_seen < 3 && !(i < len && input[i] == b'.') {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
pieces[piece_pointer] = if let Some(v) = value {
|
||||
pieces[piece_pointer] * 0x100 + v
|
||||
} else {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
};
|
||||
if dots_seen == 1 || dots_seen == 3 {
|
||||
piece_pointer += 1;
|
||||
}
|
||||
i += 1;
|
||||
if dots_seen == 3 && i < len {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
dots_seen += 1;
|
||||
}
|
||||
}
|
||||
match compress_pointer {
|
||||
Some(compress_pointer) => {
|
||||
let mut swaps = piece_pointer - compress_pointer;
|
||||
piece_pointer = 7;
|
||||
while swaps > 0 {
|
||||
pieces.swap(piece_pointer, compress_pointer + swaps - 1);
|
||||
swaps -= 1;
|
||||
piece_pointer -= 1;
|
||||
}
|
||||
}
|
||||
_ => if piece_pointer != 8 {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
}
|
||||
Ok(Ipv6Addr::new(pieces[0], pieces[1], pieces[2], pieces[3],
|
||||
pieces[4], pieces[5], pieces[6], pieces[7]))
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,130 @@
|
|||
// Copyright 2016 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.
|
||||
|
||||
#[cfg(feature = "heapsize")] use heapsize::HeapSizeOf;
|
||||
use host::Host;
|
||||
use idna::domain_to_unicode;
|
||||
use parser::default_port;
|
||||
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
|
||||
use Url;
|
||||
|
||||
pub fn url_origin(url: &Url) -> Origin {
|
||||
let scheme = url.scheme();
|
||||
match scheme {
|
||||
"blob" => {
|
||||
let result = Url::parse(url.path());
|
||||
match result {
|
||||
Ok(ref url) => url_origin(url),
|
||||
Err(_) => Origin::new_opaque()
|
||||
}
|
||||
},
|
||||
"ftp" | "gopher" | "http" | "https" | "ws" | "wss" => {
|
||||
Origin::Tuple(scheme.to_owned(), url.host().unwrap().to_owned(),
|
||||
url.port_or_known_default().unwrap())
|
||||
},
|
||||
// TODO: Figure out what to do if the scheme is a file
|
||||
"file" => Origin::new_opaque(),
|
||||
_ => Origin::new_opaque()
|
||||
}
|
||||
}
|
||||
|
||||
/// The origin of an URL
|
||||
///
|
||||
/// Two URLs with the same origin are considered
|
||||
/// to originate from the same entity and can therefore trust
|
||||
/// each other.
|
||||
///
|
||||
/// The origin is determined based on the scheme as follows:
|
||||
///
|
||||
/// - 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",
|
||||
/// 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.
|
||||
///
|
||||
/// For more information see https://url.spec.whatwg.org/#origin
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
|
||||
pub enum Origin {
|
||||
/// A globally unique identifier
|
||||
Opaque(OpaqueOrigin),
|
||||
|
||||
/// Consists of the URL's scheme, host and port
|
||||
Tuple(String, Host<String>, u16)
|
||||
}
|
||||
|
||||
#[cfg(feature = "heapsize")]
|
||||
impl HeapSizeOf for Origin {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
match *self {
|
||||
Origin::Tuple(ref scheme, ref host, _) => {
|
||||
scheme.heap_size_of_children() +
|
||||
host.heap_size_of_children()
|
||||
},
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Origin {
|
||||
/// Creates a new opaque origin that is only equal to itself.
|
||||
pub fn new_opaque() -> Origin {
|
||||
static COUNTER: AtomicUsize = ATOMIC_USIZE_INIT;
|
||||
Origin::Opaque(OpaqueOrigin(COUNTER.fetch_add(1, Ordering::SeqCst)))
|
||||
}
|
||||
|
||||
/// Return whether this origin is a (scheme, host, port) tuple
|
||||
/// (as opposed to an opaque origin).
|
||||
pub fn is_tuple(&self) -> bool {
|
||||
matches!(*self, Origin::Tuple(..))
|
||||
}
|
||||
|
||||
/// https://html.spec.whatwg.org/multipage/#ascii-serialisation-of-an-origin
|
||||
pub fn ascii_serialization(&self) -> String {
|
||||
match *self {
|
||||
Origin::Opaque(_) => "null".to_owned(),
|
||||
Origin::Tuple(ref scheme, ref host, port) => {
|
||||
if default_port(scheme) == Some(port) {
|
||||
format!("{}://{}", scheme, host)
|
||||
} else {
|
||||
format!("{}://{}:{}", scheme, host, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// https://html.spec.whatwg.org/multipage/#unicode-serialisation-of-an-origin
|
||||
pub fn unicode_serialization(&self) -> String {
|
||||
match *self {
|
||||
Origin::Opaque(_) => "null".to_owned(),
|
||||
Origin::Tuple(ref scheme, ref host, port) => {
|
||||
let host = match *host {
|
||||
Host::Domain(ref domain) => {
|
||||
let (domain, _errors) = domain_to_unicode(domain);
|
||||
Host::Domain(domain)
|
||||
}
|
||||
_ => host.clone()
|
||||
};
|
||||
if default_port(scheme) == Some(port) {
|
||||
format!("{}://{}", scheme, host)
|
||||
} else {
|
||||
format!("{}://{}:{}", scheme, host, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque identifier for URLs that have file or other schemes
|
||||
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
|
||||
pub struct OpaqueOrigin(usize);
|
||||
|
||||
#[cfg(feature = "heapsize")]
|
||||
known_heap_size!(0, OpaqueOrigin);
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,217 @@
|
|||
// Copyright 2016 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 parser::{self, SchemeType, to_u32};
|
||||
use std::str;
|
||||
use Url;
|
||||
|
||||
/// Exposes methods to manipulate the path of an URL that is not cannot-be-base.
|
||||
///
|
||||
/// The path always starts with a `/` slash, and is made of slash-separated segments.
|
||||
/// There is always at least one segment (which may be the empty string).
|
||||
///
|
||||
/// Examples:
|
||||
///
|
||||
/// ```rust
|
||||
/// use url::Url;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn run() -> Result<(), Box<Error>> {
|
||||
/// let mut url = Url::parse("mailto:me@example.com")?;
|
||||
/// assert!(url.path_segments_mut().is_err());
|
||||
///
|
||||
/// let mut url = Url::parse("http://example.net/foo/index.html")?;
|
||||
/// url.path_segments_mut().map_err(|_| "cannot be base")?
|
||||
/// .pop().push("img").push("2/100%.png");
|
||||
/// assert_eq!(url.as_str(), "http://example.net/foo/img/2%2F100%25.png");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # run().unwrap();
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct PathSegmentsMut<'a> {
|
||||
url: &'a mut Url,
|
||||
after_first_slash: usize,
|
||||
after_path: String,
|
||||
old_after_path_position: u32,
|
||||
}
|
||||
|
||||
// Not re-exported outside the crate
|
||||
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'/');
|
||||
PathSegmentsMut {
|
||||
after_first_slash: url.path_start as usize + "/".len(),
|
||||
url: url,
|
||||
old_after_path_position: old_after_path_position,
|
||||
after_path: after_path,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for PathSegmentsMut<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.url.restore_after_path(self.old_after_path_position, &self.after_path)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PathSegmentsMut<'a> {
|
||||
/// Remove all segments in the path, leaving the minimal `url.path() == "/"`.
|
||||
///
|
||||
/// Returns `&mut Self` so that method calls can be chained.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```rust
|
||||
/// use url::Url;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn run() -> Result<(), Box<Error>> {
|
||||
/// let mut url = Url::parse("https://github.com/servo/rust-url/")?;
|
||||
/// url.path_segments_mut().map_err(|_| "cannot be base")?
|
||||
/// .clear().push("logout");
|
||||
/// assert_eq!(url.as_str(), "https://github.com/logout");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # run().unwrap();
|
||||
/// ```
|
||||
pub fn clear(&mut self) -> &mut Self {
|
||||
self.url.serialization.truncate(self.after_first_slash);
|
||||
self
|
||||
}
|
||||
|
||||
/// Remove the last segment of this URL’s path if it is empty,
|
||||
/// except if these was only one segment to begin with.
|
||||
///
|
||||
/// In other words, remove one path trailing slash, if any,
|
||||
/// unless it is also the initial slash (so this does nothing if `url.path() == "/")`.
|
||||
///
|
||||
/// Returns `&mut Self` so that method calls can be chained.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```rust
|
||||
/// use url::Url;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn run() -> Result<(), Box<Error>> {
|
||||
/// let mut url = Url::parse("https://github.com/servo/rust-url/")?;
|
||||
/// url.path_segments_mut().map_err(|_| "cannot be base")?
|
||||
/// .push("pulls");
|
||||
/// assert_eq!(url.as_str(), "https://github.com/servo/rust-url//pulls");
|
||||
///
|
||||
/// let mut url = Url::parse("https://github.com/servo/rust-url/")?;
|
||||
/// url.path_segments_mut().map_err(|_| "cannot be base")?
|
||||
/// .pop_if_empty().push("pulls");
|
||||
/// assert_eq!(url.as_str(), "https://github.com/servo/rust-url/pulls");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # run().unwrap();
|
||||
/// ```
|
||||
pub fn pop_if_empty(&mut self) -> &mut Self {
|
||||
if self.url.serialization[self.after_first_slash..].ends_with('/') {
|
||||
self.url.serialization.pop();
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Remove the last segment of this URL’s path.
|
||||
///
|
||||
/// If the path only has one segment, make it empty such that `url.path() == "/"`.
|
||||
///
|
||||
/// Returns `&mut Self` so that method calls can be chained.
|
||||
pub fn pop(&mut self) -> &mut Self {
|
||||
let last_slash = self.url.serialization[self.after_first_slash..].rfind('/').unwrap_or(0);
|
||||
self.url.serialization.truncate(self.after_first_slash + last_slash);
|
||||
self
|
||||
}
|
||||
|
||||
/// Append the given segment at the end of this URL’s path.
|
||||
///
|
||||
/// See the documentation for `.extend()`.
|
||||
///
|
||||
/// Returns `&mut Self` so that method calls can be chained.
|
||||
pub fn push(&mut self, segment: &str) -> &mut Self {
|
||||
self.extend(Some(segment))
|
||||
}
|
||||
|
||||
/// Append each segment from the given iterator at the end of this URL’s path.
|
||||
///
|
||||
/// Each segment is percent-encoded like in `Url::parse` or `Url::join`,
|
||||
/// except that `%` and `/` characters are also encoded (to `%25` and `%2F`).
|
||||
/// This is unlike `Url::parse` where `%` is left as-is in case some of the input
|
||||
/// is already percent-encoded, and `/` denotes a path segment separator.)
|
||||
///
|
||||
/// Note that, in addition to slashes between new segments,
|
||||
/// this always adds a slash between the existing path and the new segments
|
||||
/// *except* if the existing path is `"/"`.
|
||||
/// If the previous last segment was empty (if the path had a trailing slash)
|
||||
/// the path after `.extend()` will contain two consecutive slashes.
|
||||
/// If that is undesired, call `.pop_if_empty()` first.
|
||||
///
|
||||
/// To obtain a behavior similar to `Url::join`, call `.pop()` unconditionally first.
|
||||
///
|
||||
/// Returns `&mut Self` so that method calls can be chained.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```rust
|
||||
/// use url::Url;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn run() -> Result<(), Box<Error>> {
|
||||
/// let mut url = Url::parse("https://github.com/")?;
|
||||
/// let org = "servo";
|
||||
/// let repo = "rust-url";
|
||||
/// let issue_number = "188";
|
||||
/// url.path_segments_mut().map_err(|_| "cannot be base")?
|
||||
/// .extend(&[org, repo, "issues", issue_number]);
|
||||
/// assert_eq!(url.as_str(), "https://github.com/servo/rust-url/issues/188");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # run().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// In order to make sure that parsing the serialization of an URL gives the same URL,
|
||||
/// a segment is ignored if it is `"."` or `".."`:
|
||||
///
|
||||
/// ```rust
|
||||
/// use url::Url;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn run() -> Result<(), Box<Error>> {
|
||||
/// let mut url = Url::parse("https://github.com/servo")?;
|
||||
/// url.path_segments_mut().map_err(|_| "cannot be base")?
|
||||
/// .extend(&["..", "rust-url", ".", "pulls"]);
|
||||
/// assert_eq!(url.as_str(), "https://github.com/servo/rust-url/pulls");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # run().unwrap();
|
||||
/// ```
|
||||
pub fn extend<I>(&mut self, segments: I) -> &mut Self
|
||||
where I: IntoIterator, I::Item: AsRef<str> {
|
||||
let scheme_type = SchemeType::from(self.url.scheme());
|
||||
let path_start = self.url.path_start as usize;
|
||||
self.url.mutate(|parser| {
|
||||
parser.context = parser::Context::PathSegmentSetter;
|
||||
for segment in segments {
|
||||
let segment = segment.as_ref();
|
||||
if matches!(segment, "." | "..") {
|
||||
continue
|
||||
}
|
||||
if parser.serialization.len() > path_start + 1 {
|
||||
parser.serialization.push('/');
|
||||
}
|
||||
let mut has_host = true; // FIXME account for this?
|
||||
parser.parse_path(scheme_type, &mut has_host, path_start,
|
||||
parser::Input::new(segment));
|
||||
}
|
||||
});
|
||||
self
|
||||
}
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
// Copyright 2016 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.
|
||||
|
||||
//! Getters and setters for URL components implemented per https://url.spec.whatwg.org/#api
|
||||
//!
|
||||
//! Unless you need to be interoperable with web browsers,
|
||||
//! you probably want to use `Url` method instead.
|
||||
|
||||
use {Url, Position, Host, ParseError, idna};
|
||||
use parser::{Parser, SchemeType, default_port, Context, Input};
|
||||
|
||||
/// https://url.spec.whatwg.org/#dom-url-domaintoascii
|
||||
pub fn domain_to_ascii(domain: &str) -> String {
|
||||
match Host::parse(domain) {
|
||||
Ok(Host::Domain(domain)) => domain,
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// https://url.spec.whatwg.org/#dom-url-domaintounicode
|
||||
pub fn domain_to_unicode(domain: &str) -> String {
|
||||
match Host::parse(domain) {
|
||||
Ok(Host::Domain(ref domain)) => {
|
||||
let (unicode, _errors) = idna::domain_to_unicode(domain);
|
||||
unicode
|
||||
}
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Getter for https://url.spec.whatwg.org/#dom-url-href
|
||||
pub fn href(url: &Url) -> &str {
|
||||
url.as_str()
|
||||
}
|
||||
|
||||
/// Setter for https://url.spec.whatwg.org/#dom-url-href
|
||||
pub fn set_href(url: &mut Url, value: &str) -> Result<(), ParseError> {
|
||||
*url = Url::parse(value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Getter for https://url.spec.whatwg.org/#dom-url-origin
|
||||
pub fn origin(url: &Url) -> String {
|
||||
url.origin().unicode_serialization()
|
||||
}
|
||||
|
||||
/// Getter for https://url.spec.whatwg.org/#dom-url-protocol
|
||||
#[inline]
|
||||
pub fn protocol(url: &Url) -> &str {
|
||||
&url.as_str()[..url.scheme().len() + ":".len()]
|
||||
}
|
||||
|
||||
/// Setter for https://url.spec.whatwg.org/#dom-url-protocol
|
||||
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.
|
||||
if let Some(position) = new_protocol.find(':') {
|
||||
new_protocol = &new_protocol[..position];
|
||||
}
|
||||
url.set_scheme(new_protocol)
|
||||
}
|
||||
|
||||
/// Getter for https://url.spec.whatwg.org/#dom-url-username
|
||||
#[inline]
|
||||
pub fn username(url: &Url) -> &str {
|
||||
url.username()
|
||||
}
|
||||
|
||||
/// Setter for https://url.spec.whatwg.org/#dom-url-username
|
||||
pub fn set_username(url: &mut Url, new_username: &str) -> Result<(), ()> {
|
||||
url.set_username(new_username)
|
||||
}
|
||||
|
||||
/// Getter for https://url.spec.whatwg.org/#dom-url-password
|
||||
#[inline]
|
||||
pub fn password(url: &Url) -> &str {
|
||||
url.password().unwrap_or("")
|
||||
}
|
||||
|
||||
/// Setter for https://url.spec.whatwg.org/#dom-url-password
|
||||
pub fn set_password(url: &mut Url, new_password: &str) -> Result<(), ()> {
|
||||
url.set_password(if new_password.is_empty() { None } else { Some(new_password) })
|
||||
}
|
||||
|
||||
/// Getter for https://url.spec.whatwg.org/#dom-url-host
|
||||
#[inline]
|
||||
pub fn host(url: &Url) -> &str {
|
||||
&url[Position::BeforeHost..Position::AfterPort]
|
||||
}
|
||||
|
||||
/// Setter for https://url.spec.whatwg.org/#dom-url-host
|
||||
pub fn set_host(url: &mut Url, new_host: &str) -> Result<(), ()> {
|
||||
if url.cannot_be_a_base() {
|
||||
return Err(())
|
||||
}
|
||||
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(':') {
|
||||
Parser::parse_port(remaining, || default_port(scheme), Context::Setter)
|
||||
.ok().map(|(port, _remaining)| port)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
Err(_) => return Err(())
|
||||
}
|
||||
}
|
||||
url.set_host_internal(host, opt_port);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Getter for https://url.spec.whatwg.org/#dom-url-hostname
|
||||
#[inline]
|
||||
pub fn hostname(url: &Url) -> &str {
|
||||
url.host_str().unwrap_or("")
|
||||
}
|
||||
|
||||
/// Setter for https://url.spec.whatwg.org/#dom-url-hostname
|
||||
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 {
|
||||
url.set_host_internal(host, None);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Getter for https://url.spec.whatwg.org/#dom-url-port
|
||||
#[inline]
|
||||
pub fn port(url: &Url) -> &str {
|
||||
&url[Position::BeforePort..Position::AfterPort]
|
||||
}
|
||||
|
||||
/// Setter for https://url.spec.whatwg.org/#dom-url-port
|
||||
pub fn set_port(url: &mut Url, new_port: &str) -> Result<(), ()> {
|
||||
let result;
|
||||
{
|
||||
// has_host implies !cannot_be_a_base
|
||||
let scheme = url.scheme();
|
||||
if !url.has_host() || scheme == "file" {
|
||||
return Err(())
|
||||
}
|
||||
result = Parser::parse_port(Input::new(new_port), || default_port(scheme), Context::Setter)
|
||||
}
|
||||
if let Ok((new_port, _remaining)) = result {
|
||||
url.set_port_internal(new_port);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Getter for https://url.spec.whatwg.org/#dom-url-pathname
|
||||
#[inline]
|
||||
pub fn pathname(url: &Url) -> &str {
|
||||
url.path()
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
url.set_path(new_pathname)
|
||||
}
|
||||
}
|
||||
|
||||
/// Getter for https://url.spec.whatwg.org/#dom-url-search
|
||||
pub fn search(url: &Url) -> &str {
|
||||
trim(&url[Position::AfterPath..Position::AfterQuery])
|
||||
}
|
||||
|
||||
/// Setter for https://url.spec.whatwg.org/#dom-url-search
|
||||
pub fn set_search(url: &mut Url, new_search: &str) {
|
||||
url.set_query(match new_search {
|
||||
"" => None,
|
||||
_ if new_search.starts_with('?') => Some(&new_search[1..]),
|
||||
_ => Some(new_search),
|
||||
})
|
||||
}
|
||||
|
||||
/// Getter for https://url.spec.whatwg.org/#dom-url-hash
|
||||
pub fn hash(url: &Url) -> &str {
|
||||
trim(&url[Position::AfterQuery..])
|
||||
}
|
||||
|
||||
/// 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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn trim(s: &str) -> &str {
|
||||
if s.len() == 1 {
|
||||
""
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
// Copyright 2016 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::ops::{Range, RangeFrom, RangeTo, RangeFull, Index};
|
||||
use Url;
|
||||
|
||||
impl Index<RangeFull> for Url {
|
||||
type Output = str;
|
||||
fn index(&self, _: RangeFull) -> &str {
|
||||
&self.serialization
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<RangeFrom<Position>> for Url {
|
||||
type Output = str;
|
||||
fn index(&self, range: RangeFrom<Position>) -> &str {
|
||||
&self.serialization[self.index(range.start)..]
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<RangeTo<Position>> for Url {
|
||||
type Output = str;
|
||||
fn index(&self, range: RangeTo<Position>) -> &str {
|
||||
&self.serialization[..self.index(range.end)]
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<Range<Position>> for Url {
|
||||
type Output = str;
|
||||
fn index(&self, range: Range<Position>) -> &str {
|
||||
&self.serialization[self.index(range.start)..self.index(range.end)]
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates a position within a URL based on its components.
|
||||
///
|
||||
/// A range of positions can be used for slicing `Url`:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use url::{Url, Position};
|
||||
/// # fn something(some_url: Url) {
|
||||
/// let serialization: &str = &some_url[..];
|
||||
/// let serialization_without_fragment: &str = &some_url[..Position::AfterQuery];
|
||||
/// let authority: &str = &some_url[Position::BeforeUsername..Position::AfterPort];
|
||||
/// let data_url_payload: &str = &some_url[Position::BeforePath..Position::AfterQuery];
|
||||
/// let scheme_relative: &str = &some_url[Position::BeforeUsername..];
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// In a pseudo-grammar (where `[`…`]?` makes a sub-sequence optional),
|
||||
/// URL components and delimiters that separate them are:
|
||||
///
|
||||
/// ```notrust
|
||||
/// url =
|
||||
/// scheme ":"
|
||||
/// [ "//" [ username [ ":" password ]? "@" ]? host [ ":" port ]? ]?
|
||||
/// path [ "?" query ]? [ "#" fragment ]?
|
||||
/// ```
|
||||
///
|
||||
/// When a given component is not present,
|
||||
/// its "before" and "after" position are the same
|
||||
/// (so that `&some_url[BeforeFoo..AfterFoo]` is the empty string)
|
||||
/// and component ordering is preserved
|
||||
/// (so that a missing query "is between" a path and a fragment).
|
||||
///
|
||||
/// The end of a component and the start of the next are either the same or separate
|
||||
/// by a delimiter.
|
||||
/// (Not that the initial `/` of a path is considered part of the path here, not a delimiter.)
|
||||
/// For example, `&url[..BeforeFragment]` would include a `#` delimiter (if present in `url`),
|
||||
/// so `&url[..AfterQuery]` might be desired instead.
|
||||
///
|
||||
/// `BeforeScheme` and `AfterFragment` are always the start and end of the entire URL,
|
||||
/// so `&url[BeforeScheme..X]` is the same as `&url[..X]`
|
||||
/// and `&url[X..AfterFragment]` is the same as `&url[X..]`.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Position {
|
||||
BeforeScheme,
|
||||
AfterScheme,
|
||||
BeforeUsername,
|
||||
AfterUsername,
|
||||
BeforePassword,
|
||||
AfterPassword,
|
||||
BeforeHost,
|
||||
AfterHost,
|
||||
BeforePort,
|
||||
AfterPort,
|
||||
BeforePath,
|
||||
AfterPath,
|
||||
BeforeQuery,
|
||||
AfterQuery,
|
||||
BeforeFragment,
|
||||
AfterFragment
|
||||
}
|
||||
|
||||
impl Url {
|
||||
#[inline]
|
||||
fn index(&self, position: Position) -> usize {
|
||||
match position {
|
||||
Position::BeforeScheme => 0,
|
||||
|
||||
Position::AfterScheme => self.scheme_end as usize,
|
||||
|
||||
Position::BeforeUsername => if self.has_authority() {
|
||||
self.scheme_end as usize + "://".len()
|
||||
} else {
|
||||
debug_assert!(self.byte_at(self.scheme_end) == b':');
|
||||
debug_assert!(self.scheme_end + ":".len() as u32 == self.username_end);
|
||||
self.scheme_end as usize + ":".len()
|
||||
},
|
||||
|
||||
Position::AfterUsername => self.username_end as usize,
|
||||
|
||||
Position::BeforePassword => if self.has_authority() &&
|
||||
self.byte_at(self.username_end) == b':' {
|
||||
self.username_end as usize + ":".len()
|
||||
} else {
|
||||
debug_assert!(self.username_end == self.host_start);
|
||||
self.username_end as usize
|
||||
},
|
||||
|
||||
Position::AfterPassword => if self.has_authority() &&
|
||||
self.byte_at(self.username_end) == b':' {
|
||||
debug_assert!(self.byte_at(self.host_start - "@".len() as u32) == b'@');
|
||||
self.host_start as usize - "@".len()
|
||||
} else {
|
||||
debug_assert!(self.username_end == self.host_start);
|
||||
self.host_start as usize
|
||||
},
|
||||
|
||||
Position::BeforeHost => self.host_start as usize,
|
||||
|
||||
Position::AfterHost => self.host_end as usize,
|
||||
|
||||
Position::BeforePort => if self.port.is_some() {
|
||||
debug_assert!(self.byte_at(self.host_end) == b':');
|
||||
self.host_end as usize + ":".len()
|
||||
} else {
|
||||
self.host_end as usize
|
||||
},
|
||||
|
||||
Position::AfterPort => self.path_start as usize,
|
||||
|
||||
Position::BeforePath => self.path_start as usize,
|
||||
|
||||
Position::AfterPath => match (self.query_start, self.fragment_start) {
|
||||
(Some(q), _) => q as usize,
|
||||
(None, Some(f)) => f as usize,
|
||||
(None, None) => self.serialization.len(),
|
||||
},
|
||||
|
||||
Position::BeforeQuery => match (self.query_start, self.fragment_start) {
|
||||
(Some(q), _) => {
|
||||
debug_assert!(self.byte_at(q) == b'?');
|
||||
q as usize + "?".len()
|
||||
}
|
||||
(None, Some(f)) => f as usize,
|
||||
(None, None) => self.serialization.len(),
|
||||
},
|
||||
|
||||
Position::AfterQuery => match self.fragment_start {
|
||||
None => self.serialization.len(),
|
||||
Some(f) => f as usize,
|
||||
},
|
||||
|
||||
Position::BeforeFragment => match self.fragment_start {
|
||||
Some(f) => {
|
||||
debug_assert!(self.byte_at(f) == b'#');
|
||||
f as usize + "#".len()
|
||||
}
|
||||
None => self.serialization.len(),
|
||||
},
|
||||
|
||||
Position::AfterFragment => self.serialization.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
// Copyright 2013-2014 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.
|
||||
|
||||
//! Data-driven tests
|
||||
|
||||
extern crate rustc_serialize;
|
||||
extern crate test;
|
||||
extern crate url;
|
||||
|
||||
use rustc_serialize::json::{self, Json};
|
||||
use url::{Url, quirks};
|
||||
|
||||
fn check_invariants(url: &Url) {
|
||||
url.check_invariants().unwrap();
|
||||
#[cfg(feature="serde")] {
|
||||
extern crate serde_json;
|
||||
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(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, "{:?} != {} {:?} for URL {:?}",
|
||||
got, stringify!($expected), expected, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
trait JsonExt {
|
||||
fn take(&mut self, key: &str) -> Option<Json>;
|
||||
fn object(self) -> json::Object;
|
||||
fn string(self) -> String;
|
||||
fn take_string(&mut self, key: &str) -> String;
|
||||
}
|
||||
|
||||
impl JsonExt for Json {
|
||||
fn take(&mut self, key: &str) -> Option<Json> {
|
||||
self.as_object_mut().unwrap().remove(key)
|
||||
}
|
||||
|
||||
fn object(self) -> json::Object {
|
||||
if let Json::Object(o) = self { o } else { panic!("Not a Json::Object") }
|
||||
}
|
||||
|
||||
fn string(self) -> String {
|
||||
if let Json::String(s) = self { s } else { panic!("Not a Json::String") }
|
||||
}
|
||||
|
||||
fn take_string(&mut self, key: &str) -> String {
|
||||
self.take(key).unwrap().string()
|
||||
}
|
||||
}
|
||||
|
||||
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 = Json::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.find("failure").is_some() {
|
||||
Err(())
|
||||
} else {
|
||||
Ok(ExpectedAttributes {
|
||||
href: entry.take_string("href"),
|
||||
origin: entry.take("origin").map(Json::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 collect_setters<F>(add_test: &mut F) where F: FnMut(String, test::TestFn) {
|
||||
let mut json = Json::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($attr).unwrap();
|
||||
for mut test in tests.as_array_mut().unwrap().drain(..) {
|
||||
let comment = test.take("comment").map(Json::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("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(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);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut tests = Vec::new();
|
||||
{
|
||||
let mut add_one = |name: String, run: test::TestFn| {
|
||||
tests.push(test::TestDescAndFn {
|
||||
desc: test::TestDesc {
|
||||
name: test::DynTestName(name),
|
||||
ignore: false,
|
||||
should_panic: test::ShouldPanic::No,
|
||||
},
|
||||
testfn: run,
|
||||
})
|
||||
};
|
||||
collect_parsing(&mut add_one);
|
||||
collect_setters(&mut add_one);
|
||||
}
|
||||
test::test_main(&std::env::args().collect::<Vec<_>>(), tests)
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,480 @@
|
|||
// Copyright 2013-2014 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.
|
||||
|
||||
//! Unit tests
|
||||
|
||||
#[macro_use]
|
||||
extern crate url;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::path::{Path, PathBuf};
|
||||
use url::{Host, HostAndPort, Url, form_urlencoded};
|
||||
|
||||
#[test]
|
||||
fn size() {
|
||||
use std::mem::size_of;
|
||||
assert_eq!(size_of::<Url>(), size_of::<Option<Url>>());
|
||||
}
|
||||
|
||||
macro_rules! assert_from_file_path {
|
||||
($path: expr) => { assert_from_file_path!($path, $path) };
|
||||
($path: expr, $url_path: expr) => {{
|
||||
let url = Url::from_file_path(Path::new($path)).unwrap();
|
||||
assert_eq!(url.host(), None);
|
||||
assert_eq!(url.path(), $url_path);
|
||||
assert_eq!(url.to_file_path(), Ok(PathBuf::from($path)));
|
||||
}};
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[test]
|
||||
fn new_file_paths() {
|
||||
if cfg!(unix) {
|
||||
assert_eq!(Url::from_file_path(Path::new("relative")), Err(()));
|
||||
assert_eq!(Url::from_file_path(Path::new("../relative")), Err(()));
|
||||
}
|
||||
if cfg!(windows) {
|
||||
assert_eq!(Url::from_file_path(Path::new("relative")), Err(()));
|
||||
assert_eq!(Url::from_file_path(Path::new(r"..\relative")), Err(()));
|
||||
assert_eq!(Url::from_file_path(Path::new(r"\drive-relative")), Err(()));
|
||||
assert_eq!(Url::from_file_path(Path::new(r"\\ucn\")), Err(()));
|
||||
}
|
||||
|
||||
if cfg!(unix) {
|
||||
assert_from_file_path!("/foo/bar");
|
||||
assert_from_file_path!("/foo/ba\0r", "/foo/ba%00r");
|
||||
assert_from_file_path!("/foo/ba%00r", "/foo/ba%2500r");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn new_path_bad_utf8() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::prelude::*;
|
||||
|
||||
let url = Url::from_file_path(Path::new(OsStr::from_bytes(b"/foo/ba\x80r"))).unwrap();
|
||||
let os_str = OsStr::from_bytes(b"/foo/ba\x80r");
|
||||
assert_eq!(url.to_file_path(), Ok(PathBuf::from(os_str)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_path_windows_fun() {
|
||||
if cfg!(windows) {
|
||||
assert_from_file_path!(r"C:\foo\bar", "/C:/foo/bar");
|
||||
assert_from_file_path!("C:\\foo\\ba\0r", "/C:/foo/ba%00r");
|
||||
|
||||
// Invalid UTF-8
|
||||
assert!(Url::parse("file:///C:/foo/ba%80r").unwrap().to_file_path().is_err());
|
||||
|
||||
// test windows canonicalized path
|
||||
let path = PathBuf::from(r"\\?\C:\foo\bar");
|
||||
assert!(Url::from_file_path(path).is_ok());
|
||||
|
||||
// Percent-encoded drive letter
|
||||
let url = Url::parse("file:///C%3A/foo/bar").unwrap();
|
||||
assert_eq!(url.to_file_path(), Ok(PathBuf::from(r"C:\foo\bar")));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn new_directory_paths() {
|
||||
if cfg!(unix) {
|
||||
assert_eq!(Url::from_directory_path(Path::new("relative")), Err(()));
|
||||
assert_eq!(Url::from_directory_path(Path::new("../relative")), Err(()));
|
||||
|
||||
let url = Url::from_directory_path(Path::new("/foo/bar")).unwrap();
|
||||
assert_eq!(url.host(), None);
|
||||
assert_eq!(url.path(), "/foo/bar/");
|
||||
}
|
||||
if cfg!(windows) {
|
||||
assert_eq!(Url::from_directory_path(Path::new("relative")), Err(()));
|
||||
assert_eq!(Url::from_directory_path(Path::new(r"..\relative")), Err(()));
|
||||
assert_eq!(Url::from_directory_path(Path::new(r"\drive-relative")), Err(()));
|
||||
assert_eq!(Url::from_directory_path(Path::new(r"\\ucn\")), Err(()));
|
||||
|
||||
let url = Url::from_directory_path(Path::new(r"C:\foo\bar")).unwrap();
|
||||
assert_eq!(url.host(), None);
|
||||
assert_eq!(url.path(), "/C:/foo/bar/");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str() {
|
||||
assert!("http://testing.com/this".parse::<Url>().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_with_params() {
|
||||
let url = Url::parse_with_params("http://testing.com/this?dont=clobberme",
|
||||
&[("lang", "rust")]).unwrap();
|
||||
|
||||
assert_eq!(url.as_str(), "http://testing.com/this?dont=clobberme&lang=rust");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_124() {
|
||||
let url: Url = "file:a".parse().unwrap();
|
||||
assert_eq!(url.path(), "/a");
|
||||
let url: Url = "file:...".parse().unwrap();
|
||||
assert_eq!(url.path(), "/...");
|
||||
let url: Url = "file:..".parse().unwrap();
|
||||
assert_eq!(url.path(), "/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_equality() {
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
|
||||
fn check_eq(a: &Url, b: &Url) {
|
||||
assert_eq!(a, b);
|
||||
|
||||
let mut h1 = DefaultHasher::new();
|
||||
a.hash(&mut h1);
|
||||
let mut h2 = DefaultHasher::new();
|
||||
b.hash(&mut h2);
|
||||
assert_eq!(h1.finish(), h2.finish());
|
||||
}
|
||||
|
||||
fn url(s: &str) -> Url {
|
||||
let rv = s.parse().unwrap();
|
||||
check_eq(&rv, &rv);
|
||||
rv
|
||||
}
|
||||
|
||||
// Doesn't care if default port is given.
|
||||
let a: Url = url("https://example.com/");
|
||||
let b: Url = url("https://example.com:443/");
|
||||
check_eq(&a, &b);
|
||||
|
||||
// Different ports
|
||||
let a: Url = url("http://example.com/");
|
||||
let b: Url = url("http://example.com:8080/");
|
||||
assert!(a != b, "{:?} != {:?}", a, b);
|
||||
|
||||
// Different scheme
|
||||
let a: Url = url("http://example.com/");
|
||||
let b: Url = url("https://example.com/");
|
||||
assert_ne!(a, b);
|
||||
|
||||
// Different host
|
||||
let a: Url = url("http://foo.com/");
|
||||
let b: Url = url("http://bar.com/");
|
||||
assert_ne!(a, b);
|
||||
|
||||
// Missing path, automatically substituted. Semantically the same.
|
||||
let a: Url = url("http://foo.com");
|
||||
let b: Url = url("http://foo.com/");
|
||||
check_eq(&a, &b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host() {
|
||||
fn assert_host(input: &str, host: Host<&str>) {
|
||||
assert_eq!(Url::parse(input).unwrap().host(), Some(host));
|
||||
}
|
||||
assert_host("http://www.mozilla.org", Host::Domain("www.mozilla.org"));
|
||||
assert_host("http://1.35.33.49", Host::Ipv4(Ipv4Addr::new(1, 35, 33, 49)));
|
||||
assert_host("http://[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]", Host::Ipv6(Ipv6Addr::new(
|
||||
0x2001, 0x0db8, 0x85a3, 0x08d3, 0x1319, 0x8a2e, 0x0370, 0x7344)));
|
||||
assert_host("http://1.35.+33.49", Host::Domain("1.35.+33.49"));
|
||||
assert_host("http://[::]", Host::Ipv6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)));
|
||||
assert_host("http://[::1]", Host::Ipv6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)));
|
||||
assert_host("http://0x1.0X23.0x21.061", Host::Ipv4(Ipv4Addr::new(1, 35, 33, 49)));
|
||||
assert_host("http://0x1232131", Host::Ipv4(Ipv4Addr::new(1, 35, 33, 49)));
|
||||
assert_host("http://111", Host::Ipv4(Ipv4Addr::new(0, 0, 0, 111)));
|
||||
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());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_serialization() {
|
||||
// libstd’s `Display for Ipv6Addr` serializes 0:0:0:0:0:0:_:_ and 0:0:0:0:0:ffff:_:_
|
||||
// using IPv4-like syntax, as suggested in https://tools.ietf.org/html/rfc5952#section-4
|
||||
// but https://url.spec.whatwg.org/#concept-ipv6-serializer specifies not to.
|
||||
|
||||
// Not [::0.0.0.2] / [::ffff:0.0.0.2]
|
||||
assert_eq!(Url::parse("http://[0::2]").unwrap().host_str(), Some("[::2]"));
|
||||
assert_eq!(Url::parse("http://[0::ffff:0:2]").unwrap().host_str(), Some("[::ffff:0:2]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_idna() {
|
||||
assert!("http://goșu.ro".parse::<Url>().is_ok());
|
||||
assert_eq!(Url::parse("http://☃.net/").unwrap().host(), Some(Host::Domain("xn--n3h.net")));
|
||||
assert!("https://r2---sn-huoa-cvhl.googlevideo.com/crossdomain.xml".parse::<Url>().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialization() {
|
||||
let data = [
|
||||
("http://example.com/", "http://example.com/"),
|
||||
("http://addslash.com", "http://addslash.com/"),
|
||||
("http://@emptyuser.com/", "http://emptyuser.com/"),
|
||||
("http://:@emptypass.com/", "http://:@emptypass.com/"),
|
||||
("http://user@user.com/", "http://user@user.com/"),
|
||||
("http://user:pass@userpass.com/", "http://user:pass@userpass.com/"),
|
||||
("http://slashquery.com/path/?q=something", "http://slashquery.com/path/?q=something"),
|
||||
("http://noslashquery.com/path?q=something", "http://noslashquery.com/path?q=something")
|
||||
];
|
||||
for &(input, result) in &data {
|
||||
let url = Url::parse(input).unwrap();
|
||||
assert_eq!(url.as_str(), result);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_form_urlencoded() {
|
||||
let pairs: &[(Cow<str>, Cow<str>)] = &[
|
||||
("foo".into(), "é&".into()),
|
||||
("bar".into(), "".into()),
|
||||
("foo".into(), "#".into())
|
||||
];
|
||||
let encoded = form_urlencoded::Serializer::new(String::new()).extend_pairs(pairs).finish();
|
||||
assert_eq!(encoded, "foo=%C3%A9%26&bar=&foo=%23");
|
||||
assert_eq!(form_urlencoded::parse(encoded.as_bytes()).collect::<Vec<_>>(), pairs.to_vec());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_form_serialize() {
|
||||
let encoded = form_urlencoded::Serializer::new(String::new())
|
||||
.append_pair("foo", "é&")
|
||||
.append_pair("bar", "")
|
||||
.append_pair("foo", "#")
|
||||
.finish();
|
||||
assert_eq!(encoded, "foo=%C3%A9%26&bar=&foo=%23");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_and_port_display() {
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{}",
|
||||
HostAndPort{ host: Host::Domain("www.mozilla.org"), port: 80}
|
||||
),
|
||||
"www.mozilla.org:80"
|
||||
);
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{}",
|
||||
HostAndPort::<String>{ host: Host::Ipv4(Ipv4Addr::new(1, 35, 33, 49)), port: 65535 }
|
||||
),
|
||||
"1.35.33.49:65535"
|
||||
);
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{}",
|
||||
HostAndPort::<String>{
|
||||
host: Host::Ipv6(Ipv6Addr::new(
|
||||
0x2001, 0x0db8, 0x85a3, 0x08d3, 0x1319, 0x8a2e, 0x0370, 0x7344
|
||||
)),
|
||||
port: 1337
|
||||
})
|
||||
,
|
||||
"[2001:db8:85a3:8d3:1319:8a2e:370:7344]:1337"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// https://github.com/servo/rust-url/issues/25
|
||||
fn issue_25() {
|
||||
let filename = if cfg!(windows) { r"C:\run\pg.sock" } else { "/run/pg.sock" };
|
||||
let mut url = Url::from_file_path(filename).unwrap();
|
||||
url.check_invariants().unwrap();
|
||||
url.set_scheme("postgres").unwrap();
|
||||
url.check_invariants().unwrap();
|
||||
url.set_host(Some("")).unwrap();
|
||||
url.check_invariants().unwrap();
|
||||
url.set_username("me").unwrap();
|
||||
url.check_invariants().unwrap();
|
||||
let expected = format!("postgres://me@/{}run/pg.sock", if cfg!(windows) { "C:/" } else { "" });
|
||||
assert_eq!(url.as_str(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// https://github.com/servo/rust-url/issues/61
|
||||
fn issue_61() {
|
||||
let mut url = Url::parse("http://mozilla.org").unwrap();
|
||||
url.set_scheme("https").unwrap();
|
||||
assert_eq!(url.port(), None);
|
||||
assert_eq!(url.port_or_known_default(), Some(443));
|
||||
url.check_invariants().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
/// https://github.com/servo/rust-url/issues/197
|
||||
fn issue_197() {
|
||||
let mut url = Url::from_file_path("/").expect("Failed to parse path");
|
||||
url.check_invariants().unwrap();
|
||||
assert_eq!(url, Url::parse("file:///").expect("Failed to parse path + protocol"));
|
||||
url.path_segments_mut().expect("path_segments_mut").pop_if_empty();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_241() {
|
||||
Url::parse("mailto:").unwrap().cannot_be_a_base();
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// https://github.com/servo/rust-url/issues/222
|
||||
fn append_trailing_slash() {
|
||||
let mut url: Url = "http://localhost:6767/foo/bar?a=b".parse().unwrap();
|
||||
url.check_invariants().unwrap();
|
||||
url.path_segments_mut().unwrap().push("");
|
||||
url.check_invariants().unwrap();
|
||||
assert_eq!(url.to_string(), "http://localhost:6767/foo/bar/?a=b");
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// https://github.com/servo/rust-url/issues/227
|
||||
fn extend_query_pairs_then_mutate() {
|
||||
let mut url: Url = "http://localhost:6767/foo/bar".parse().unwrap();
|
||||
url.query_pairs_mut().extend_pairs(vec![ ("auth", "my-token") ].into_iter());
|
||||
url.check_invariants().unwrap();
|
||||
assert_eq!(url.to_string(), "http://localhost:6767/foo/bar?auth=my-token");
|
||||
url.path_segments_mut().unwrap().push("some_other_path");
|
||||
url.check_invariants().unwrap();
|
||||
assert_eq!(url.to_string(), "http://localhost:6767/foo/bar/some_other_path?auth=my-token");
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// https://github.com/servo/rust-url/issues/222
|
||||
fn append_empty_segment_then_mutate() {
|
||||
let mut url: Url = "http://localhost:6767/foo/bar?a=b".parse().unwrap();
|
||||
url.check_invariants().unwrap();
|
||||
url.path_segments_mut().unwrap().push("").pop();
|
||||
url.check_invariants().unwrap();
|
||||
assert_eq!(url.to_string(), "http://localhost:6767/foo/bar?a=b");
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// https://github.com/servo/rust-url/issues/243
|
||||
fn test_set_host() {
|
||||
let mut url = Url::parse("https://example.net/hello").unwrap();
|
||||
url.set_host(Some("foo.com")).unwrap();
|
||||
assert_eq!(url.as_str(), "https://foo.com/hello");
|
||||
assert!(url.set_host(None).is_err());
|
||||
assert_eq!(url.as_str(), "https://foo.com/hello");
|
||||
assert!(url.set_host(Some("")).is_err());
|
||||
assert_eq!(url.as_str(), "https://foo.com/hello");
|
||||
|
||||
let mut url = Url::parse("foobar://example.net/hello").unwrap();
|
||||
url.set_host(None).unwrap();
|
||||
assert_eq!(url.as_str(), "foobar:/hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
// https://github.com/servo/rust-url/issues/166
|
||||
fn test_leading_dots() {
|
||||
assert_eq!(Host::parse(".org").unwrap(), Host::Domain(".org".to_owned()));
|
||||
assert_eq!(Url::parse("file://./foo").unwrap().domain(), Some("."));
|
||||
}
|
||||
|
||||
// This is testing that the macro produces buildable code when invoked
|
||||
// inside both a module and a function
|
||||
#[test]
|
||||
fn define_encode_set_scopes() {
|
||||
use url::percent_encoding::{utf8_percent_encode, SIMPLE_ENCODE_SET};
|
||||
|
||||
define_encode_set! {
|
||||
/// This encode set is used in the URL parser for query strings.
|
||||
pub QUERY_ENCODE_SET = [SIMPLE_ENCODE_SET] | {' ', '"', '#', '<', '>'}
|
||||
}
|
||||
|
||||
assert_eq!(utf8_percent_encode("foo bar", QUERY_ENCODE_SET).collect::<String>(), "foo%20bar");
|
||||
|
||||
mod m {
|
||||
use url::percent_encoding::{utf8_percent_encode, SIMPLE_ENCODE_SET};
|
||||
|
||||
define_encode_set! {
|
||||
/// This encode set is used in the URL parser for query strings.
|
||||
pub QUERY_ENCODE_SET = [SIMPLE_ENCODE_SET] | {' ', '"', '#', '<', '>'}
|
||||
}
|
||||
|
||||
pub fn test() {
|
||||
assert_eq!(utf8_percent_encode("foo bar", QUERY_ENCODE_SET).collect::<String>(), "foo%20bar");
|
||||
}
|
||||
}
|
||||
|
||||
m::test();
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// https://github.com/servo/rust-url/issues/302
|
||||
fn test_origin_hash() {
|
||||
use std::hash::{Hash,Hasher};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
|
||||
fn hash<T: Hash>(value: &T) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
value.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
let origin = &Url::parse("http://example.net/").unwrap().origin();
|
||||
|
||||
let origins_to_compare = [
|
||||
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(),
|
||||
];
|
||||
|
||||
for origin_to_compare in &origins_to_compare {
|
||||
if origin == origin_to_compare {
|
||||
assert_eq!(hash(origin), hash(origin_to_compare));
|
||||
} else {
|
||||
assert_ne!(hash(origin), hash(origin_to_compare));
|
||||
}
|
||||
}
|
||||
|
||||
let opaque_origin = Url::parse("file://example.net").unwrap().origin();
|
||||
let same_opaque_origin = Url::parse("file://example.net").unwrap().origin();
|
||||
let other_opaque_origin = Url::parse("file://other").unwrap().origin();
|
||||
|
||||
assert_ne!(hash(&opaque_origin), hash(&same_opaque_origin));
|
||||
assert_ne!(hash(&opaque_origin), hash(&other_opaque_origin));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_windows_unc_path() {
|
||||
if !cfg!(windows) {
|
||||
return
|
||||
}
|
||||
|
||||
let url = Url::from_file_path(Path::new(r"\\host\share\path\file.txt")).unwrap();
|
||||
assert_eq!(url.as_str(), "file://host/share/path/file.txt");
|
||||
|
||||
let url = Url::from_file_path(Path::new(r"\\höst\share\path\file.txt")).unwrap();
|
||||
assert_eq!(url.as_str(), "file://xn--hst-sna/share/path/file.txt");
|
||||
|
||||
let url = Url::from_file_path(Path::new(r"\\192.168.0.1\share\path\file.txt")).unwrap();
|
||||
assert_eq!(url.host(), Some(Host::Ipv4(Ipv4Addr::new(192, 168, 0, 1))));
|
||||
|
||||
let path = url.to_file_path().unwrap();
|
||||
assert_eq!(path.to_str(), Some(r"\\192.168.0.1\share\path\file.txt"));
|
||||
|
||||
// Another way to write these:
|
||||
let url = Url::from_file_path(Path::new(r"\\?\UNC\host\share\path\file.txt")).unwrap();
|
||||
assert_eq!(url.as_str(), "file://host/share/path/file.txt");
|
||||
|
||||
// Paths starting with "\\.\" (Local Device Paths) are intentionally not supported.
|
||||
let url = Url::from_file_path(Path::new(r"\\.\some\path\file.txt"));
|
||||
assert!(url.is_err());
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1 +1 @@
|
|||
{"files":{".travis.yml":"890af214187ffcba4732acb2d1af30d7adb9aade0679e9fdb06baae363240b8e","Cargo.toml":"ec586106c4d0625919a3591fe3ae915043e82c8bfdd1c9e747171ba5e21047e1","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e","Makefile":"bffd75d34654b2955d4f005f1a5e85c821c90becf1a8a52cbe10121972f43148","README.md":"eb3f4694003f408cbe3c7f3e9fbbc71241defb940cc55a816981f0f0f144c8eb","UPGRADING.md":"fbcc2d39bdf17db0745793db6626fcd5c909dddd4ce13b27566cfabece22c368","appveyor.yml":"c78486dbfbe6ebbf3d808afb9a19f7ec18c4704ce451c6305f0716999b70a1a6","docs/.nojekyll":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","docs/404.html":"f61e6271c1ea1aa113b64b356e994595fa548f0433f89948d747503ad22195cd","docs/index.html":"f61e6271c1ea1aa113b64b356e994595fa548f0433f89948d747503ad22195cd","github.png":"b432fd855efe7c430fe6a57ccf83935c1996f03a7cdc8d6e1b34154b8c43f6ec","rust-url-todo":"1192cee7b6cedf2133d97dc6074b593a1d19b0ee13fff6f28d6329855044e575","src/encoding.rs":"f3e109ca8ec5a9130da50cdfb3003530aedb6dd5a440f0790d76b71f6981119c","src/form_urlencoded.rs":"7ccaef7148e4bc2577154c50f8705db3a055b641269e24c22770f06222321e1e","src/host.rs":"281165d732ea87b6f01a98f7c68ffcb284c41f84b3ab6ed674fb8e57022d1019","src/lib.rs":"bd156e8bcfbd44f0cd52c8b394e03ec63fea012c0bf5ca554521352714838605","src/origin.rs":"7071dcc1070ccfae84cdcd43586b84a9706e35a9a099ff4dde128da0909bd0bc","src/parser.rs":"9d30868f0900586fec6f122a0322598a08116ab0b4c4d8caf5c35a720381a73a","src/path_segments.rs":"7bd3142eaa568863ef44e2255c181239141f9eeee337f889b9ffaaeab4ca669d","src/quirks.rs":"1231f965e22bb3632c22993e2a8d4c7470bcb4a8de25d049f31784303f0def03","src/slicing.rs":"4e539886b23945a92094625f3e531a4bff40daa44240b5d19ee8577478c4f7fe","tests/data.rs":"c333766897f6492fb6583ab5c8a511973b7a55f58ca550799432343da64d5ca7","tests/setters_tests.json":"ebcbdb52e9a4b5a565f8806d52ebc610d46a34df883e10b0be080d026468ff73","tests/unit.rs":"c2f206f433be619414d761d358a2a4a5a46cfe8a4fea5339adec5e9937d78de2","tests/urltestdata.json":"430c74aa3a31afaa57a92805544e00825f4dffe2def98c1e3c212c3db80268af"},"package":"eeb819346883532a271eb626deb43c4a1bb4c4dd47c519bd78137c3e72a4fe27"}
|
||||
{"files":{".travis.yml":"890af214187ffcba4732acb2d1af30d7adb9aade0679e9fdb06baae363240b8e","Cargo.toml":"52e8e0a7014d3b0c654491184e9fc82f8c61ba7f51332e2b6a787330be42a301","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e","Makefile":"bffd75d34654b2955d4f005f1a5e85c821c90becf1a8a52cbe10121972f43148","README.md":"eb3f4694003f408cbe3c7f3e9fbbc71241defb940cc55a816981f0f0f144c8eb","UPGRADING.md":"fbcc2d39bdf17db0745793db6626fcd5c909dddd4ce13b27566cfabece22c368","appveyor.yml":"c78486dbfbe6ebbf3d808afb9a19f7ec18c4704ce451c6305f0716999b70a1a6","docs/.nojekyll":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","docs/404.html":"f61e6271c1ea1aa113b64b356e994595fa548f0433f89948d747503ad22195cd","docs/index.html":"f61e6271c1ea1aa113b64b356e994595fa548f0433f89948d747503ad22195cd","github.png":"b432fd855efe7c430fe6a57ccf83935c1996f03a7cdc8d6e1b34154b8c43f6ec","rust-url-todo":"1192cee7b6cedf2133d97dc6074b593a1d19b0ee13fff6f28d6329855044e575","src/encoding.rs":"f3e109ca8ec5a9130da50cdfb3003530aedb6dd5a440f0790d76b71f6981119c","src/form_urlencoded.rs":"320418526c4564a4469581d426e7467bcefe504eecd098e1eb90a2663a75fd80","src/host.rs":"4c74946777d76e9b307604f8e24a3485fd0856b664450194e8b429e262cf410d","src/lib.rs":"894cc76c31357fb588292e990a87f4e951043e32ea3d9f38fddc145302d0b318","src/origin.rs":"6e4821eb9600a32ef54d05c8e1a7937f6d9b4dd1e3bda7f36c7988f6a2bef78b","src/parser.rs":"e379c9eb51cff977b9cef368ab64bcff7626e885b859772ab1bc76c9193e9fde","src/path_segments.rs":"7bd3142eaa568863ef44e2255c181239141f9eeee337f889b9ffaaeab4ca669d","src/quirks.rs":"fe6095104cf583053ab35d8a2a093a8581da083641e32d1c5acfe322a26c4bde","src/slicing.rs":"4e539886b23945a92094625f3e531a4bff40daa44240b5d19ee8577478c4f7fe","tests/data.rs":"50a110e475b1717fdaff6524be7d8916cb41a45461e0715f632ff54d0ce28886","tests/setters_tests.json":"ebcbdb52e9a4b5a565f8806d52ebc610d46a34df883e10b0be080d026468ff73","tests/unit.rs":"c0305ca991b2c03815aac2e69ef1c1792b087df92272a6196eb698b27625321f","tests/urltestdata.json":"df87028e1eaea4ef70cf1c1faaed2584e81a46b8b6cd90f50d47b77726ece41c"},"package":"fa35e768d4daf1d85733418a49fb42e10d7f633e394fccab4ab7aba897053fe2"}
|
|
@ -1,24 +1,29 @@
|
|||
# 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]
|
||||
|
||||
name = "url"
|
||||
# When updating version, also modify html_root_url in the lib.rs
|
||||
version = "1.5.1"
|
||||
version = "1.6.0"
|
||||
authors = ["The rust-url developers"]
|
||||
|
||||
description = "URL library for Rust, based on the WHATWG URL Standard"
|
||||
documentation = "https://docs.rs/url"
|
||||
repository = "https://github.com/servo/rust-url"
|
||||
readme = "README.md"
|
||||
keywords = ["url", "parser"]
|
||||
categories = ["parser-implementations", "web-programming", "encoding"]
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/servo/rust-url"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "servo/rust-url" }
|
||||
appveyor = { repository = "servo/rust-url" }
|
||||
|
||||
[workspace]
|
||||
members = [".", "idna", "percent_encoding", "url_serde"]
|
||||
[lib]
|
||||
test = false
|
||||
|
||||
[[test]]
|
||||
name = "unit"
|
||||
|
@ -26,24 +31,44 @@ name = "unit"
|
|||
[[test]]
|
||||
name = "data"
|
||||
harness = false
|
||||
[dependencies.rustc-serialize]
|
||||
version = "0.3"
|
||||
optional = true
|
||||
|
||||
[lib]
|
||||
test = false
|
||||
[dependencies.matches]
|
||||
version = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
rustc-test = "0.1"
|
||||
rustc-serialize = "0.3"
|
||||
serde_json = ">=0.6.1, <0.9"
|
||||
[dependencies.serde]
|
||||
version = ">=0.6.1, <0.9"
|
||||
optional = true
|
||||
|
||||
[dependencies.percent-encoding]
|
||||
version = "1.0.0"
|
||||
|
||||
[dependencies.encoding]
|
||||
version = "0.2"
|
||||
optional = true
|
||||
|
||||
[dependencies.idna]
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies.heapsize]
|
||||
version = ">=0.4.1, <0.5"
|
||||
optional = true
|
||||
[dev-dependencies.rustc-test]
|
||||
version = "0.2"
|
||||
|
||||
[dev-dependencies.rustc-serialize]
|
||||
version = "0.3"
|
||||
|
||||
[dev-dependencies.serde_json]
|
||||
version = ">=0.6.1, <0.9"
|
||||
|
||||
[features]
|
||||
query_encoding = ["encoding"]
|
||||
heap_size = ["heapsize"]
|
||||
query_encoding = ["encoding"]
|
||||
[badges.travis-ci]
|
||||
repository = "servo/rust-url"
|
||||
|
||||
[dependencies]
|
||||
encoding = {version = "0.2", optional = true}
|
||||
heapsize = {version = ">=0.1.1, <0.5", optional = true}
|
||||
idna = { version = "0.1.0", path = "./idna" }
|
||||
matches = "0.1"
|
||||
percent-encoding = { version = "1.0.0", path = "./percent_encoding" }
|
||||
rustc-serialize = {version = "0.3", optional = true}
|
||||
serde = {version = ">=0.6.1, <0.9", optional = true}
|
||||
[badges.appveyor]
|
||||
repository = "Manishearth/rust-url"
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
use encoding::EncodingOverride;
|
||||
use percent_encoding::{percent_encode_byte, percent_decode};
|
||||
use std::borrow::{Borrow, Cow};
|
||||
use std::fmt;
|
||||
use std::str;
|
||||
|
||||
|
||||
|
@ -216,6 +217,15 @@ pub struct Serializer<T: Target> {
|
|||
target: Option<T>,
|
||||
start_position: usize,
|
||||
encoding: EncodingOverride,
|
||||
custom_encoding: Option<SilentDebug<Box<FnMut(&str) -> Cow<[u8]>>>>,
|
||||
}
|
||||
|
||||
struct SilentDebug<T>(T);
|
||||
|
||||
impl<T> fmt::Debug for SilentDebug<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("…")
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Target {
|
||||
|
@ -272,6 +282,7 @@ impl<T: Target> Serializer<T> {
|
|||
target: Some(target),
|
||||
start_position: start_position,
|
||||
encoding: EncodingOverride::utf8(),
|
||||
custom_encoding: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -290,11 +301,20 @@ impl<T: Target> Serializer<T> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Set the character encoding to be used for names and values before percent-encoding.
|
||||
pub fn custom_encoding_override<F>(&mut self, encode: F) -> &mut Self
|
||||
where F: FnMut(&str) -> Cow<[u8]> + 'static
|
||||
{
|
||||
self.custom_encoding = Some(SilentDebug(Box::new(encode)));
|
||||
self
|
||||
}
|
||||
|
||||
/// Serialize and append a name/value pair.
|
||||
///
|
||||
/// Panics if called after `.finish()`.
|
||||
pub fn append_pair(&mut self, name: &str, value: &str) -> &mut Self {
|
||||
append_pair(string(&mut self.target), self.start_position, self.encoding, name, value);
|
||||
append_pair(string(&mut self.target), self.start_position, self.encoding,
|
||||
&mut self.custom_encoding, name, value);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -311,7 +331,8 @@ impl<T: Target> Serializer<T> {
|
|||
let string = string(&mut self.target);
|
||||
for pair in iter {
|
||||
let &(ref k, ref v) = pair.borrow();
|
||||
append_pair(string, self.start_position, self.encoding, k.as_ref(), v.as_ref());
|
||||
append_pair(string, self.start_position, self.encoding,
|
||||
&mut self.custom_encoding, k.as_ref(), v.as_ref());
|
||||
}
|
||||
}
|
||||
self
|
||||
|
@ -324,6 +345,8 @@ impl<T: Target> Serializer<T> {
|
|||
/// Panics if called after `.finish()`.
|
||||
#[cfg(feature = "query_encoding")]
|
||||
pub fn append_charset(&mut self) -> &mut Self {
|
||||
assert!(self.custom_encoding.is_none(),
|
||||
"Cannot use both custom_encoding_override() and append_charset()");
|
||||
{
|
||||
let string = string(&mut self.target);
|
||||
append_separator_if_needed(string, self.start_position);
|
||||
|
@ -361,9 +384,20 @@ fn string<T: Target>(target: &mut Option<T>) -> &mut String {
|
|||
}
|
||||
|
||||
fn append_pair(string: &mut String, start_position: usize, encoding: EncodingOverride,
|
||||
custom_encoding: &mut Option<SilentDebug<Box<FnMut(&str) -> Cow<[u8]>>>>,
|
||||
name: &str, value: &str) {
|
||||
append_separator_if_needed(string, start_position);
|
||||
string.extend(byte_serialize(&encoding.encode(name.into())));
|
||||
append_encoded(name, string, encoding, custom_encoding);
|
||||
string.push('=');
|
||||
string.extend(byte_serialize(&encoding.encode(value.into())));
|
||||
append_encoded(value, string, encoding, custom_encoding);
|
||||
}
|
||||
|
||||
fn append_encoded(s: &str, string: &mut String, encoding: EncodingOverride,
|
||||
custom_encoding: &mut Option<SilentDebug<Box<FnMut(&str) -> Cow<[u8]>>>>) {
|
||||
let bytes = if let Some(SilentDebug(ref mut custom)) = *custom_encoding {
|
||||
custom(s)
|
||||
} else {
|
||||
encoding.encode(s.into())
|
||||
};
|
||||
string.extend(byte_serialize(&bytes));
|
||||
}
|
||||
|
|
|
@ -137,7 +137,7 @@ impl<'a> Host<&'a str> {
|
|||
impl Host<String> {
|
||||
/// Parse a host: either an IPv6 address in [] square brackets, or a domain.
|
||||
///
|
||||
/// https://url.spec.whatwg.org/#host-parsing
|
||||
/// <https://url.spec.whatwg.org/#host-parsing>
|
||||
pub fn parse(input: &str) -> Result<Self, ParseError> {
|
||||
if input.starts_with('[') {
|
||||
if !input.ends_with(']') {
|
||||
|
@ -309,7 +309,7 @@ fn longest_zero_sequence(pieces: &[u16; 8]) -> (isize, isize) {
|
|||
}
|
||||
}
|
||||
|
||||
/// https://url.spec.whatwg.org/#ipv4-number-parser
|
||||
/// <https://url.spec.whatwg.org/#ipv4-number-parser>
|
||||
fn parse_ipv4number(mut input: &str) -> Result<u32, ()> {
|
||||
let mut r = 10;
|
||||
if input.starts_with("0x") || input.starts_with("0X") {
|
||||
|
@ -331,7 +331,7 @@ fn parse_ipv4number(mut input: &str) -> Result<u32, ()> {
|
|||
}
|
||||
}
|
||||
|
||||
/// https://url.spec.whatwg.org/#concept-ipv4-parser
|
||||
/// <https://url.spec.whatwg.org/#concept-ipv4-parser>
|
||||
fn parse_ipv4addr(input: &str) -> ParseResult<Option<Ipv4Addr>> {
|
||||
if input.is_empty() {
|
||||
return Ok(None)
|
||||
|
@ -368,7 +368,7 @@ fn parse_ipv4addr(input: &str) -> ParseResult<Option<Ipv4Addr>> {
|
|||
Ok(Some(Ipv4Addr::from(ipv4)))
|
||||
}
|
||||
|
||||
/// https://url.spec.whatwg.org/#concept-ipv6-parser
|
||||
/// <https://url.spec.whatwg.org/#concept-ipv6-parser>
|
||||
fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
|
||||
let input = input.as_bytes();
|
||||
let len = input.len();
|
||||
|
@ -423,6 +423,9 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
|
|||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
i = start;
|
||||
if piece_pointer > 6 {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
is_ip_v4 = true;
|
||||
},
|
||||
b':' => {
|
||||
|
@ -445,16 +448,24 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
|
|||
if piece_pointer > 6 {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
let mut dots_seen = 0;
|
||||
let mut numbers_seen = 0;
|
||||
while i < len {
|
||||
let mut value = None;
|
||||
if numbers_seen > 0 {
|
||||
if numbers_seen < 4 && (i < len && input[i] == b'.') {
|
||||
i += 1
|
||||
} else {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
}
|
||||
|
||||
let mut ipv4_piece = None;
|
||||
while i < len {
|
||||
let digit = match input[i] {
|
||||
c @ b'0' ... b'9' => c - b'0',
|
||||
_ => break
|
||||
};
|
||||
match value {
|
||||
None => value = Some(digit as u16),
|
||||
match ipv4_piece {
|
||||
None => ipv4_piece = Some(digit as u16),
|
||||
Some(0) => return Err(ParseError::InvalidIpv6Address), // No leading zero
|
||||
Some(ref mut v) => {
|
||||
*v = *v * 10 + digit as u16;
|
||||
|
@ -465,24 +476,28 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
|
|||
}
|
||||
i += 1;
|
||||
}
|
||||
if dots_seen < 3 && !(i < len && input[i] == b'.') {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
pieces[piece_pointer] = if let Some(v) = value {
|
||||
|
||||
pieces[piece_pointer] = if let Some(v) = ipv4_piece {
|
||||
pieces[piece_pointer] * 0x100 + v
|
||||
} else {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
};
|
||||
if dots_seen == 1 || dots_seen == 3 {
|
||||
numbers_seen += 1;
|
||||
|
||||
if numbers_seen == 2 || numbers_seen == 4 {
|
||||
piece_pointer += 1;
|
||||
}
|
||||
i += 1;
|
||||
if dots_seen == 3 && i < len {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
dots_seen += 1;
|
||||
}
|
||||
|
||||
if numbers_seen != 4 {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
}
|
||||
|
||||
if i < len {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
|
||||
match compress_pointer {
|
||||
Some(compress_pointer) => {
|
||||
let mut swaps = piece_pointer - compress_pointer;
|
||||
|
|
|
@ -104,7 +104,7 @@ assert_eq!(css_url.as_str(), "http://servo.github.io/rust-url/main.css");
|
|||
# run().unwrap();
|
||||
*/
|
||||
|
||||
#![doc(html_root_url = "https://docs.rs/url/1.5.1")]
|
||||
#![doc(html_root_url = "https://docs.rs/url/1.6.0")]
|
||||
|
||||
#[cfg(feature="rustc-serialize")] extern crate rustc_serialize;
|
||||
#[macro_use] extern crate matches;
|
||||
|
@ -252,6 +252,13 @@ impl Url {
|
|||
/// # }
|
||||
/// # run().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If the function can not parse an absolute URL from the given string,
|
||||
/// a [`ParseError`] variant will be returned.
|
||||
///
|
||||
/// [`ParseError`]: enum.ParseError.html
|
||||
#[inline]
|
||||
pub fn parse(input: &str) -> Result<Url, ::ParseError> {
|
||||
Url::options().parse(input)
|
||||
|
@ -274,6 +281,13 @@ impl Url {
|
|||
/// # }
|
||||
/// # run().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If the function can not parse an absolute URL from the given string,
|
||||
/// a [`ParseError`] variant will be returned.
|
||||
///
|
||||
/// [`ParseError`]: enum.ParseError.html
|
||||
#[inline]
|
||||
pub fn parse_with_params<I, K, V>(input: &str, iter: I) -> Result<Url, ::ParseError>
|
||||
where I: IntoIterator,
|
||||
|
@ -314,6 +328,13 @@ impl Url {
|
|||
/// # }
|
||||
/// # run().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If the function can not parse an URL from the given string
|
||||
/// with this URL as the base URL, a [`ParseError`] variant will be returned.
|
||||
///
|
||||
/// [`ParseError`]: enum.ParseError.html
|
||||
#[inline]
|
||||
pub fn join(&self, input: &str) -> Result<Url, ::ParseError> {
|
||||
Url::options().base_url(Some(self)).parse(input)
|
||||
|
@ -500,7 +521,7 @@ impl Url {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the origin of this URL (https://url.spec.whatwg.org/#origin)
|
||||
/// Return the origin of this URL (<https://url.spec.whatwg.org/#origin>)
|
||||
///
|
||||
/// Note: this returns an opaque origin for `file:` URLs, which causes
|
||||
/// `url.origin() != url.origin()`.
|
||||
|
@ -1448,9 +1469,6 @@ impl Url {
|
|||
|
||||
/// Change this URL’s host.
|
||||
///
|
||||
/// If this URL is cannot-be-a-base or there is an error parsing the given `host`,
|
||||
/// do nothing and return `Err`.
|
||||
///
|
||||
/// Removing the host (calling this with `None`)
|
||||
/// will also remove any username, password, and port number.
|
||||
///
|
||||
|
@ -1524,6 +1542,13 @@ impl Url {
|
|||
/// # }
|
||||
/// # run().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If this URL is cannot-be-a-base or there is an error parsing the given `host`,
|
||||
/// a [`ParseError`] variant will be returned.
|
||||
///
|
||||
/// [`ParseError`]: enum.ParseError.html
|
||||
pub fn set_host(&mut self, host: Option<&str>) -> Result<(), ParseError> {
|
||||
if self.cannot_be_a_base() {
|
||||
return Err(ParseError::SetHostOnCannotBeABaseUrl)
|
||||
|
@ -1732,15 +1757,16 @@ impl Url {
|
|||
/// ```
|
||||
///
|
||||
/// Setup username to user1
|
||||
///
|
||||
/// ```rust
|
||||
/// use url::{Url, ParseError};
|
||||
///
|
||||
/// # fn run() -> Result<(), ParseError> {
|
||||
/// let mut url = Url::parse("ftp://:secre1@example.com")?;
|
||||
/// let mut url = Url::parse("ftp://:secre1@example.com/")?;
|
||||
/// let result = url.set_username("user1");
|
||||
/// assert!(result.is_ok());
|
||||
/// assert_eq!(url.username(), "user1");
|
||||
/// assert_eq!(url.as_str(), "ftp://user1:secre1@example.com");
|
||||
/// assert_eq!(url.as_str(), "ftp://user1:secre1@example.com/");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # run().unwrap();
|
||||
|
|
|
@ -49,7 +49,7 @@ pub fn url_origin(url: &Url) -> Origin {
|
|||
/// - If the scheme is anything else, the origin is opaque, meaning
|
||||
/// the URL does not have the same origin as any other URL.
|
||||
///
|
||||
/// For more information see https://url.spec.whatwg.org/#origin
|
||||
/// For more information see <https://url.spec.whatwg.org/#origin>
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
|
||||
pub enum Origin {
|
||||
/// A globally unique identifier
|
||||
|
@ -86,7 +86,7 @@ impl Origin {
|
|||
matches!(*self, Origin::Tuple(..))
|
||||
}
|
||||
|
||||
/// https://html.spec.whatwg.org/multipage/#ascii-serialisation-of-an-origin
|
||||
/// <https://html.spec.whatwg.org/multipage/#ascii-serialisation-of-an-origin>
|
||||
pub fn ascii_serialization(&self) -> String {
|
||||
match *self {
|
||||
Origin::Opaque(_) => "null".to_owned(),
|
||||
|
@ -100,7 +100,7 @@ impl Origin {
|
|||
}
|
||||
}
|
||||
|
||||
/// https://html.spec.whatwg.org/multipage/#unicode-serialisation-of-an-origin
|
||||
/// <https://html.spec.whatwg.org/multipage/#unicode-serialisation-of-an-origin>
|
||||
pub fn unicode_serialization(&self) -> String {
|
||||
match *self {
|
||||
Origin::Opaque(_) => "null".to_owned(),
|
||||
|
|
|
@ -683,7 +683,7 @@ impl<'a> Parser<'a> {
|
|||
self.syntax_violation("unencoded @ sign in username or password")
|
||||
} else {
|
||||
self.syntax_violation(
|
||||
"embedding authentification information (username or password) \
|
||||
"embedding authentication information (username or password) \
|
||||
in an URL is not recommended")
|
||||
}
|
||||
last_at = Some((char_count, remaining.clone()))
|
||||
|
|
|
@ -46,7 +46,7 @@ pub fn set_href(url: &mut Url, value: &str) -> Result<(), ParseError> {
|
|||
|
||||
/// Getter for https://url.spec.whatwg.org/#dom-url-origin
|
||||
pub fn origin(url: &Url) -> String {
|
||||
url.origin().unicode_serialization()
|
||||
url.origin().ascii_serialization()
|
||||
}
|
||||
|
||||
/// Getter for https://url.spec.whatwg.org/#dom-url-protocol
|
||||
|
|
|
@ -188,11 +188,7 @@ fn main() {
|
|||
{
|
||||
let mut add_one = |name: String, run: test::TestFn| {
|
||||
tests.push(test::TestDescAndFn {
|
||||
desc: test::TestDesc {
|
||||
name: test::DynTestName(name),
|
||||
ignore: false,
|
||||
should_panic: test::ShouldPanic::No,
|
||||
},
|
||||
desc: test::TestDesc::new(test::DynTestName(name)),
|
||||
testfn: run,
|
||||
})
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#[macro_use]
|
||||
extern crate url;
|
||||
|
||||
use std::ascii::AsciiExt;
|
||||
use std::borrow::Cow;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -255,6 +256,15 @@ fn test_form_serialize() {
|
|||
assert_eq!(encoded, "foo=%C3%A9%26&bar=&foo=%23");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn form_urlencoded_custom_encoding_override() {
|
||||
let encoded = form_urlencoded::Serializer::new(String::new())
|
||||
.custom_encoding_override(|s| s.as_bytes().to_ascii_uppercase().into())
|
||||
.append_pair("foo", "bar")
|
||||
.finish();
|
||||
assert_eq!(encoded, "FOO=BAR");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_and_port_display() {
|
||||
assert_eq!(
|
||||
|
|
|
@ -3536,7 +3536,7 @@
|
|||
"input": "http://你好你好",
|
||||
"base": "http://other.com/",
|
||||
"href": "http://xn--6qqa088eba/",
|
||||
"origin": "http://你好你好",
|
||||
"origin": "http://xn--6qqa088eba",
|
||||
"protocol": "http:",
|
||||
"username": "",
|
||||
"password": "",
|
||||
|
@ -3634,6 +3634,26 @@
|
|||
"base": "http://other.com/",
|
||||
"failure": true
|
||||
},
|
||||
{
|
||||
"input": "http://[::1.2.3.4x]",
|
||||
"base": "http://other.com/",
|
||||
"failure": true
|
||||
},
|
||||
{
|
||||
"input": "http://[::1.2.3.]",
|
||||
"base": "http://other.com/",
|
||||
"failure": true
|
||||
},
|
||||
{
|
||||
"input": "http://[::1.2.]",
|
||||
"base": "http://other.com/",
|
||||
"failure": true
|
||||
},
|
||||
{
|
||||
"input": "http://[::1.]",
|
||||
"base": "http://other.com/",
|
||||
"failure": true
|
||||
},
|
||||
"Misc Unicode",
|
||||
{
|
||||
"input": "http://foo:💩@example.com/bar",
|
||||
|
|
Загрузка…
Ссылка в новой задаче