mirror of
https://github.com/openharmony/third_party_rust_rust-url.git
synced 2026-07-21 01:45:27 -04:00
Auto merge of #517 - servo:2.0, r=nox
2.0 <!-- Reviewable:start --> This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/rust-url/517) <!-- Reviewable:end -->
This commit is contained in:
+2
-18
@@ -1,28 +1,12 @@
|
||||
language: rust
|
||||
script: cargo test --all-features --all
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- rust: 1.17.0
|
||||
install:
|
||||
# --precise requires Cargo.lock to already exist
|
||||
- cargo update
|
||||
# getopts is only used in tests. Its versions 0.2.16+ don’t build on 1.17.0
|
||||
- cargo update -p getopts --precise 0.2.15
|
||||
|
||||
- cargo update -p unicode-normalization --precise 0.1.5
|
||||
|
||||
# data-url uses pub(crate) which is unstable in 1.17
|
||||
script: cargo test --all-features -p url -p idna -p percent-encoding -p url_serde
|
||||
|
||||
- rust: 1.30.0
|
||||
- rust: stable
|
||||
script: cargo test --all-features --all
|
||||
|
||||
- rust: beta
|
||||
script: cargo test --all-features --all
|
||||
|
||||
- rust: nightly
|
||||
script: cargo test --all-features --all
|
||||
|
||||
- rust: nightly
|
||||
env: TARGET=WASM32 # For job list UI
|
||||
install: rustup target add wasm32-unknown-unknown
|
||||
|
||||
+5
-16
@@ -2,7 +2,7 @@
|
||||
|
||||
name = "url"
|
||||
# When updating version, also modify html_root_url in the lib.rs
|
||||
version = "1.7.2"
|
||||
version = "2.0.0"
|
||||
authors = ["The rust-url developers"]
|
||||
|
||||
description = "URL library for Rust, based on the WHATWG URL Standard"
|
||||
@@ -18,7 +18,7 @@ travis-ci = { repository = "servo/rust-url" }
|
||||
appveyor = { repository = "Manishearth/rust-url" }
|
||||
|
||||
[workspace]
|
||||
members = [".", "idna", "percent_encoding", "url_serde", "data-url"]
|
||||
members = [".", "idna", "percent_encoding", "data-url"]
|
||||
|
||||
[[test]]
|
||||
name = "unit"
|
||||
@@ -32,27 +32,16 @@ test = false
|
||||
|
||||
[dev-dependencies]
|
||||
rustc-test = "0.3"
|
||||
rustc-serialize = "0.3"
|
||||
serde_json = ">=0.6.1, <0.9"
|
||||
serde_json = "1.0"
|
||||
|
||||
bencher = "0.1"
|
||||
|
||||
[features]
|
||||
query_encoding = ["encoding"]
|
||||
heap_size = ["heapsize"]
|
||||
|
||||
[dependencies]
|
||||
encoding = {version = "0.2", optional = true}
|
||||
heapsize = {version = ">=0.4.1, <0.5", optional = true}
|
||||
idna = { version = "0.1.0", path = "./idna" }
|
||||
idna = { version = "0.2.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}
|
||||
serde = {version = "1.0", optional = true}
|
||||
|
||||
[[bench]]
|
||||
name = "parse_url"
|
||||
harness = false
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["query_encoding"]
|
||||
|
||||
@@ -29,7 +29,7 @@ impl From<DecodeError<Impossible>> for InvalidBase64 {
|
||||
fn from(e: DecodeError<Impossible>) -> Self {
|
||||
match e {
|
||||
DecodeError::InvalidBase64(e) => e,
|
||||
DecodeError::WriteError(e) => match e {}
|
||||
DecodeError::WriteError(e) => match e {},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,14 +46,20 @@ pub fn decode_to_vec(input: &[u8]) -> Result<Vec<u8>, InvalidBase64> {
|
||||
}
|
||||
|
||||
/// <https://infra.spec.whatwg.org/#forgiving-base64-decode>
|
||||
pub struct Decoder<F, E> where F: FnMut(&[u8]) -> Result<(), E> {
|
||||
pub struct Decoder<F, E>
|
||||
where
|
||||
F: FnMut(&[u8]) -> Result<(), E>,
|
||||
{
|
||||
write_bytes: F,
|
||||
bit_buffer: u32,
|
||||
buffer_bit_length: u8,
|
||||
padding_symbols: u8,
|
||||
}
|
||||
|
||||
impl<F, E> Decoder<F, E> where F: FnMut(&[u8]) -> Result<(), E> {
|
||||
impl<F, E> Decoder<F, E>
|
||||
where
|
||||
F: FnMut(&[u8]) -> Result<(), E>,
|
||||
{
|
||||
pub fn new(write_bytes: F) -> Self {
|
||||
Self {
|
||||
write_bytes,
|
||||
@@ -72,12 +78,12 @@ impl<F, E> Decoder<F, E> where F: FnMut(&[u8]) -> Result<(), E> {
|
||||
|
||||
// Remove ASCII whitespace
|
||||
if matches!(byte, b' ' | b'\t' | b'\n' | b'\r' | b'\x0C') {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
if byte == b'=' {
|
||||
self.padding_symbols = self.padding_symbols.saturating_add(1);
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
Err(InvalidBase64Details::UnexpectedSymbol(byte))?
|
||||
@@ -115,32 +121,22 @@ impl<F, E> Decoder<F, E> where F: FnMut(&[u8]) -> Result<(), E> {
|
||||
(12, 2) | (12, 0) => {
|
||||
// A multiple of four of alphabet symbols, followed by two more symbols,
|
||||
// optionally followed by two padding characters (which make a total multiple of four).
|
||||
let byte_buffer = [
|
||||
(self.bit_buffer >> 4) as u8,
|
||||
];
|
||||
let byte_buffer = [(self.bit_buffer >> 4) as u8];
|
||||
(self.write_bytes)(&byte_buffer).map_err(DecodeError::WriteError)?;
|
||||
}
|
||||
(18, 1) | (18, 0) => {
|
||||
// A multiple of four of alphabet symbols, followed by three more symbols,
|
||||
// optionally followed by one padding character (which make a total multiple of four).
|
||||
let byte_buffer = [
|
||||
(self.bit_buffer >> 10) as u8,
|
||||
(self.bit_buffer >> 2) as u8,
|
||||
];
|
||||
let byte_buffer = [(self.bit_buffer >> 10) as u8, (self.bit_buffer >> 2) as u8];
|
||||
(self.write_bytes)(&byte_buffer).map_err(DecodeError::WriteError)?;
|
||||
}
|
||||
(6, _) => {
|
||||
Err(InvalidBase64Details::LoneAlphabetSymbol)?
|
||||
}
|
||||
_ => {
|
||||
Err(InvalidBase64Details::Padding)?
|
||||
}
|
||||
(6, _) => Err(InvalidBase64Details::LoneAlphabetSymbol)?,
|
||||
_ => Err(InvalidBase64Details::Padding)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Generated by `make_base64_decode_table.py` based on "Table 1: The Base 64 Alphabet"
|
||||
/// at <https://tools.ietf.org/html/rfc4648#section-4>
|
||||
///
|
||||
@@ -148,6 +144,7 @@ impl<F, E> Decoder<F, E> where F: FnMut(&[u8]) -> Result<(), E> {
|
||||
/// Array values are their positions in the base64 alphabet,
|
||||
/// or -1 for symbols not in the alphabet.
|
||||
/// The position contributes 6 bits to the decoded bytes.
|
||||
#[rustfmt::skip]
|
||||
const BASE64_DECODE_TABLE: [i8; 256] = [
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
|
||||
+46
-30
@@ -15,14 +15,15 @@
|
||||
//! assert!(fragment.is_none());
|
||||
//! ```
|
||||
|
||||
#[macro_use] extern crate matches;
|
||||
#[macro_use]
|
||||
extern crate matches;
|
||||
|
||||
macro_rules! require {
|
||||
($condition: expr) => {
|
||||
if !$condition {
|
||||
return None
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub mod forgiving_base64;
|
||||
@@ -53,7 +54,11 @@ impl<'a> DataUrl<'a> {
|
||||
|
||||
let (mime_type, base64) = parse_header(from_colon_to_comma);
|
||||
|
||||
Ok(DataUrl { mime_type, base64, encoded_body_plus_fragment })
|
||||
Ok(DataUrl {
|
||||
mime_type,
|
||||
base64,
|
||||
encoded_body_plus_fragment,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn mime_type(&self) -> &mime::Mime {
|
||||
@@ -62,9 +67,12 @@ impl<'a> DataUrl<'a> {
|
||||
|
||||
/// Streaming-decode the data URL’s body to `write_body_bytes`,
|
||||
/// and return the URL’s fragment identifier if it has one.
|
||||
pub fn decode<F, E>(&self, write_body_bytes: F)
|
||||
-> Result<Option<FragmentIdentifier<'a>>, forgiving_base64::DecodeError<E>>
|
||||
where F: FnMut(&[u8]) -> Result<(), E>
|
||||
pub fn decode<F, E>(
|
||||
&self,
|
||||
write_body_bytes: F,
|
||||
) -> Result<Option<FragmentIdentifier<'a>>, forgiving_base64::DecodeError<E>>
|
||||
where
|
||||
F: FnMut(&[u8]) -> Result<(), E>,
|
||||
{
|
||||
if self.base64 {
|
||||
decode_with_base64(self.encoded_body_plus_fragment, write_body_bytes)
|
||||
@@ -75,9 +83,9 @@ impl<'a> DataUrl<'a> {
|
||||
}
|
||||
|
||||
/// Return the decoded body, and the URL’s fragment identifier if it has one.
|
||||
pub fn decode_to_vec(&self)
|
||||
-> Result<(Vec<u8>, Option<FragmentIdentifier<'a>>), forgiving_base64::InvalidBase64>
|
||||
{
|
||||
pub fn decode_to_vec(
|
||||
&self,
|
||||
) -> Result<(Vec<u8>, Option<FragmentIdentifier<'a>>), forgiving_base64::InvalidBase64> {
|
||||
let mut body = Vec::new();
|
||||
let fragment = self.decode(|bytes| Ok(body.extend_from_slice(bytes)))?;
|
||||
Ok((body, fragment))
|
||||
@@ -100,7 +108,7 @@ impl<'a> FragmentIdentifier<'a> {
|
||||
percent_encode(byte, &mut string)
|
||||
}
|
||||
// Printable ASCII
|
||||
_ => string.push(byte as char)
|
||||
_ => string.push(byte as char),
|
||||
}
|
||||
}
|
||||
string
|
||||
@@ -125,7 +133,9 @@ fn pretend_parse_data_url(input: &str) -> Option<&str> {
|
||||
let mut bytes = left_trimmed.bytes();
|
||||
{
|
||||
// Ignore ASCII tabs or newlines like the URL parser would
|
||||
let mut iter = bytes.by_ref().filter(|&byte| !matches!(byte, b'\t' | b'\n' | b'\r'));
|
||||
let mut iter = bytes
|
||||
.by_ref()
|
||||
.filter(|&byte| !matches!(byte, b'\t' | b'\n' | b'\r'));
|
||||
require!(iter.next()?.to_ascii_lowercase() == b'd');
|
||||
require!(iter.next()?.to_ascii_lowercase() == b'a');
|
||||
require!(iter.next()?.to_ascii_lowercase() == b't');
|
||||
@@ -142,10 +152,10 @@ fn pretend_parse_data_url(input: &str) -> Option<&str> {
|
||||
fn find_comma_before_fragment(after_colon: &str) -> Option<(&str, &str)> {
|
||||
for (i, byte) in after_colon.bytes().enumerate() {
|
||||
if byte == b',' {
|
||||
return Some((&after_colon[..i], &after_colon[i + 1..]))
|
||||
return Some((&after_colon[..i], &after_colon[i + 1..]));
|
||||
}
|
||||
if byte == b'#' {
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -187,18 +197,16 @@ fn parse_header(from_colon_to_comma: &str) -> (mime::Mime, bool) {
|
||||
}
|
||||
|
||||
// Printable ASCII
|
||||
_ => string.push(byte as char)
|
||||
_ => string.push(byte as char),
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: does Mime::from_str match the MIME Sniffing Standard’s parsing algorithm?
|
||||
// <https://mimesniff.spec.whatwg.org/#parse-a-mime-type>
|
||||
let mime_type = string.parse().unwrap_or_else(|_| {
|
||||
mime::Mime {
|
||||
type_: String::from("text"),
|
||||
subtype: String::from("plain"),
|
||||
parameters: vec![(String::from("charset"), String::from("US-ASCII"))],
|
||||
}
|
||||
let mime_type = string.parse().unwrap_or_else(|_| mime::Mime {
|
||||
type_: String::from("text"),
|
||||
subtype: String::from("plain"),
|
||||
parameters: vec![(String::from("charset"), String::from("US-ASCII"))],
|
||||
});
|
||||
|
||||
(mime_type, base64)
|
||||
@@ -209,7 +217,9 @@ fn remove_base64_suffix(s: &str) -> Option<&str> {
|
||||
let mut bytes = s.bytes();
|
||||
{
|
||||
// Ignore ASCII tabs or newlines like the URL parser would
|
||||
let iter = bytes.by_ref().filter(|&byte| !matches!(byte, b'\t' | b'\n' | b'\r'));
|
||||
let iter = bytes
|
||||
.by_ref()
|
||||
.filter(|&byte| !matches!(byte, b'\t' | b'\n' | b'\r'));
|
||||
|
||||
// Search from the end
|
||||
let mut iter = iter.rev();
|
||||
@@ -240,9 +250,12 @@ fn percent_encode(byte: u8, string: &mut String) {
|
||||
/// Anything that would have been UTF-8 percent-encoded by the URL parser
|
||||
/// would be percent-decoded here.
|
||||
/// We skip that round-trip and pass it through unchanged.
|
||||
fn decode_without_base64<F, E>(encoded_body_plus_fragment: &str, mut write_bytes: F)
|
||||
-> Result<Option<FragmentIdentifier>, E>
|
||||
where F: FnMut(&[u8]) -> Result<(), E>
|
||||
fn decode_without_base64<F, E>(
|
||||
encoded_body_plus_fragment: &str,
|
||||
mut write_bytes: F,
|
||||
) -> Result<Option<FragmentIdentifier>, E>
|
||||
where
|
||||
F: FnMut(&[u8]) -> Result<(), E>,
|
||||
{
|
||||
let bytes = encoded_body_plus_fragment.as_bytes();
|
||||
let mut slice_start = 0;
|
||||
@@ -275,11 +288,11 @@ fn decode_without_base64<F, E>(encoded_body_plus_fragment: &str, mut write_bytes
|
||||
b'#' => {
|
||||
let fragment_start = i + 1;
|
||||
let fragment = &encoded_body_plus_fragment[fragment_start..];
|
||||
return Ok(Some(FragmentIdentifier(fragment)))
|
||||
return Ok(Some(FragmentIdentifier(fragment)));
|
||||
}
|
||||
|
||||
// Ignore over '\t' | '\n' | '\r'
|
||||
_ => slice_start = i + 1
|
||||
_ => slice_start = i + 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,9 +303,12 @@ fn decode_without_base64<F, E>(encoded_body_plus_fragment: &str, mut write_bytes
|
||||
/// `decode_without_base64()` composed with
|
||||
/// <https://infra.spec.whatwg.org/#isomorphic-decode> composed with
|
||||
/// <https://infra.spec.whatwg.org/#forgiving-base64-decode>.
|
||||
fn decode_with_base64<F, E>(encoded_body_plus_fragment: &str, write_bytes: F)
|
||||
-> Result<Option<FragmentIdentifier>, forgiving_base64::DecodeError<E>>
|
||||
where F: FnMut(&[u8]) -> Result<(), E>
|
||||
fn decode_with_base64<F, E>(
|
||||
encoded_body_plus_fragment: &str,
|
||||
write_bytes: F,
|
||||
) -> Result<Option<FragmentIdentifier>, forgiving_base64::DecodeError<E>>
|
||||
where
|
||||
F: FnMut(&[u8]) -> Result<(), E>,
|
||||
{
|
||||
let mut decoder = forgiving_base64::Decoder::new(write_bytes);
|
||||
let fragment = decode_without_base64(encoded_body_plus_fragment, |bytes| decoder.feed(bytes))?;
|
||||
|
||||
+12
-9
@@ -7,14 +7,16 @@ pub struct Mime {
|
||||
pub type_: String,
|
||||
pub subtype: String,
|
||||
/// (name, value)
|
||||
pub parameters: Vec<(String, String)>
|
||||
pub parameters: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl Mime {
|
||||
pub fn get_parameter<P>(&self, name: &P) -> Option<&str>
|
||||
where P: ?Sized + PartialEq<str>
|
||||
where
|
||||
P: ?Sized + PartialEq<str>,
|
||||
{
|
||||
self.parameters.iter()
|
||||
self.parameters
|
||||
.iter()
|
||||
.find(|&&(ref n, _)| name == &**n)
|
||||
.map(|&(_, ref v)| &**v)
|
||||
}
|
||||
@@ -67,11 +69,11 @@ fn parse_parameters(s: &str, parameters: &mut Vec<(String, String)>) {
|
||||
let piece = piece.trim_left_matches(ascii_whitespace);
|
||||
let (name, value) = split2(piece, '=');
|
||||
if name.is_empty() || !only_http_token_code_points(name) || contains(¶meters, name) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
if let Some(value) = value {
|
||||
let value = if value.starts_with('"') {
|
||||
let max_len = value.len().saturating_sub(2); // without start or end quotes
|
||||
let max_len = value.len().saturating_sub(2); // without start or end quotes
|
||||
let mut unescaped_value = String::with_capacity(max_len);
|
||||
let mut chars = value[1..].chars();
|
||||
'until_closing_quote: loop {
|
||||
@@ -79,7 +81,7 @@ fn parse_parameters(s: &str, parameters: &mut Vec<(String, String)>) {
|
||||
match c {
|
||||
'"' => break 'until_closing_quote,
|
||||
'\\' => unescaped_value.push(chars.next().unwrap_or('\\')),
|
||||
_ => unescaped_value.push(c)
|
||||
_ => unescaped_value.push(c),
|
||||
}
|
||||
}
|
||||
if let Some(piece) = semicolon_separated.next() {
|
||||
@@ -88,17 +90,17 @@ fn parse_parameters(s: &str, parameters: &mut Vec<(String, String)>) {
|
||||
unescaped_value.push(';');
|
||||
chars = piece.chars()
|
||||
} else {
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !valid_value(&unescaped_value) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
unescaped_value
|
||||
} else {
|
||||
let value = value.trim_right_matches(ascii_whitespace);
|
||||
if !valid_value(value) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
value.to_owned()
|
||||
};
|
||||
@@ -160,6 +162,7 @@ macro_rules! byte_map {
|
||||
}
|
||||
|
||||
// Copied from https://github.com/hyperium/mime/blob/v0.3.5/src/parse.rs#L293
|
||||
#[rustfmt::skip]
|
||||
static IS_HTTP_TOKEN: [bool; 256] = byte_map![
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
|
||||
+17
-18
@@ -1,6 +1,7 @@
|
||||
extern crate data_url;
|
||||
extern crate rustc_test;
|
||||
#[macro_use] extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
|
||||
fn run_data_url(input: String, expected_mime: Option<String>, expected_body: Option<Vec<u8>>) {
|
||||
@@ -22,11 +23,10 @@ fn run_data_url(input: String, expected_mime: Option<String>, expected_body: Opt
|
||||
}
|
||||
|
||||
fn collect_data_url<F>(add_test: &mut F)
|
||||
where F: FnMut(String, bool, rustc_test::TestFn)
|
||||
where
|
||||
F: FnMut(String, bool, rustc_test::TestFn),
|
||||
{
|
||||
let known_failures = [
|
||||
"data://test:test/,X",
|
||||
];
|
||||
let known_failures = ["data://test:test/,X"];
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
@@ -47,7 +47,7 @@ fn collect_data_url<F>(add_test: &mut F)
|
||||
should_panic,
|
||||
rustc_test::TestFn::dyn_test_fn(move || {
|
||||
run_data_url(input, expected_mime, expected_body)
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -62,9 +62,9 @@ fn run_base64(input: String, expected: Option<Vec<u8>>) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn collect_base64<F>(add_test: &mut F)
|
||||
where F: FnMut(String, bool, rustc_test::TestFn)
|
||||
where
|
||||
F: FnMut(String, bool, rustc_test::TestFn),
|
||||
{
|
||||
let known_failures = [];
|
||||
|
||||
@@ -75,9 +75,7 @@ fn collect_base64<F>(add_test: &mut F)
|
||||
add_test(
|
||||
format!("base64 {:?}", input),
|
||||
should_panic,
|
||||
rustc_test::TestFn::dyn_test_fn(move || {
|
||||
run_base64(input, expected)
|
||||
})
|
||||
rustc_test::TestFn::dyn_test_fn(move || run_base64(input, expected)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -92,9 +90,9 @@ fn run_mime(input: String, expected: Option<String>) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn collect_mime<F>(add_test: &mut F)
|
||||
where F: FnMut(String, bool, rustc_test::TestFn)
|
||||
where
|
||||
F: FnMut(String, bool, rustc_test::TestFn),
|
||||
{
|
||||
let known_failures = [];
|
||||
|
||||
@@ -102,7 +100,10 @@ fn collect_mime<F>(add_test: &mut F)
|
||||
#[serde(untagged)]
|
||||
enum Entry {
|
||||
Comment(String),
|
||||
TestCase { input: String, output: Option<String> }
|
||||
TestCase {
|
||||
input: String,
|
||||
output: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
let v: Vec<Entry> = serde_json::from_str(include_str!("mime-types.json")).unwrap();
|
||||
@@ -115,7 +116,7 @@ fn collect_mime<F>(add_test: &mut F)
|
||||
Entry::TestCase { input, output } => (input, output),
|
||||
Entry::Comment(s) => {
|
||||
last_comment = Some(s);
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -127,9 +128,7 @@ fn collect_mime<F>(add_test: &mut F)
|
||||
format!("MIME type {:?}", input)
|
||||
},
|
||||
should_panic,
|
||||
rustc_test::TestFn::dyn_test_fn(move || {
|
||||
run_mime(input, expected)
|
||||
})
|
||||
rustc_test::TestFn::dyn_test_fn(move || run_mime(input, expected)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<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>
|
||||
@@ -1,3 +0,0 @@
|
||||
<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>
|
||||
+2
-1
@@ -1,10 +1,11 @@
|
||||
[package]
|
||||
name = "idna"
|
||||
version = "0.1.5"
|
||||
version = "0.2.0"
|
||||
authors = ["The rust-url developers"]
|
||||
description = "IDNA (Internationalizing Domain Names in Applications) and Punycode."
|
||||
repository = "https://github.com/servo/rust-url/"
|
||||
license = "MIT/Apache-2.0"
|
||||
autotests = false
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
+16
-14
@@ -32,12 +32,15 @@
|
||||
//! > that minimizes the impact of this transition for client software,
|
||||
//! > allowing client software to access domains that are valid under either system.
|
||||
|
||||
#[macro_use] extern crate matches;
|
||||
#[macro_use]
|
||||
extern crate matches;
|
||||
extern crate unicode_bidi;
|
||||
extern crate unicode_normalization;
|
||||
|
||||
pub mod punycode;
|
||||
pub mod uts46;
|
||||
mod uts46;
|
||||
|
||||
pub use uts46::{Config, Errors};
|
||||
|
||||
/// The [domain to ASCII](https://url.spec.whatwg.org/#concept-domain-to-ascii) algorithm.
|
||||
///
|
||||
@@ -47,11 +50,16 @@ pub mod uts46;
|
||||
///
|
||||
/// This process may fail.
|
||||
pub fn domain_to_ascii(domain: &str) -> Result<String, uts46::Errors> {
|
||||
uts46::to_ascii(domain, uts46::Flags {
|
||||
use_std3_ascii_rules: false,
|
||||
transitional_processing: false,
|
||||
verify_dns_length: false,
|
||||
})
|
||||
Config::default().to_ascii(domain)
|
||||
}
|
||||
|
||||
/// The [domain to ASCII](https://url.spec.whatwg.org/#concept-domain-to-ascii) algorithm,
|
||||
/// with the `beStrict` flag set.
|
||||
pub fn domain_to_ascii_strict(domain: &str) -> Result<String, uts46::Errors> {
|
||||
Config::default()
|
||||
.use_std3_ascii_rules(true)
|
||||
.verify_dns_length(true)
|
||||
.to_ascii(domain)
|
||||
}
|
||||
|
||||
/// The [domain to Unicode](https://url.spec.whatwg.org/#concept-domain-to-unicode) algorithm.
|
||||
@@ -63,11 +71,5 @@ pub fn domain_to_ascii(domain: &str) -> Result<String, uts46::Errors> {
|
||||
/// This may indicate [syntax violations](https://url.spec.whatwg.org/#syntax-violation)
|
||||
/// but always returns a string for the mapped domain.
|
||||
pub fn domain_to_unicode(domain: &str) -> (String, Result<(), uts46::Errors>) {
|
||||
uts46::to_unicode(domain, uts46::Flags {
|
||||
use_std3_ascii_rules: false,
|
||||
|
||||
// Unused:
|
||||
transitional_processing: false,
|
||||
verify_dns_length: false,
|
||||
})
|
||||
Config::default().to_unicode(domain)
|
||||
}
|
||||
|
||||
+47
-38
@@ -13,10 +13,8 @@
|
||||
//! `encode_str` and `decode_to_string` provide convenience wrappers
|
||||
//! that convert from and to Rust’s UTF-8 based `str` and `String` types.
|
||||
|
||||
use std::u32;
|
||||
use std::char;
|
||||
#[allow(unused_imports, deprecated)]
|
||||
use std::ascii::AsciiExt;
|
||||
use std::u32;
|
||||
|
||||
// Bootstring parameters for Punycode
|
||||
static BASE: u32 = 36;
|
||||
@@ -28,7 +26,6 @@ static INITIAL_BIAS: u32 = 72;
|
||||
static INITIAL_N: u32 = 0x80;
|
||||
static DELIMITER: char = '-';
|
||||
|
||||
|
||||
#[inline]
|
||||
fn adapt(mut delta: u32, num_points: u32, first_time: bool) -> u32 {
|
||||
delta /= if first_time { DAMP } else { 2 };
|
||||
@@ -41,7 +38,6 @@ fn adapt(mut delta: u32, num_points: u32, first_time: bool) -> u32 {
|
||||
k + (((BASE - T_MIN + 1) * delta) / (delta + SKEW))
|
||||
}
|
||||
|
||||
|
||||
/// Convert Punycode to an Unicode `String`.
|
||||
///
|
||||
/// This is a convenience wrapper around `decode`.
|
||||
@@ -50,7 +46,6 @@ pub fn decode_to_string(input: &str) -> Option<String> {
|
||||
decode(input).map(|chars| chars.into_iter().collect())
|
||||
}
|
||||
|
||||
|
||||
/// Convert Punycode to Unicode.
|
||||
///
|
||||
/// Return None on malformed input or overflow.
|
||||
@@ -63,8 +58,12 @@ pub fn decode(input: &str) -> Option<Vec<char>> {
|
||||
None => (Vec::new(), input),
|
||||
Some(position) => (
|
||||
input[..position].chars().collect(),
|
||||
if position > 0 { &input[position + 1..] } else { input }
|
||||
)
|
||||
if position > 0 {
|
||||
&input[position + 1..]
|
||||
} else {
|
||||
input
|
||||
},
|
||||
),
|
||||
};
|
||||
let mut code_point = INITIAL_N;
|
||||
let mut bias = INITIAL_BIAS;
|
||||
@@ -82,35 +81,39 @@ pub fn decode(input: &str) -> Option<Vec<char>> {
|
||||
// which gets added to i.
|
||||
loop {
|
||||
let digit = match byte {
|
||||
byte @ b'0' ... b'9' => byte - b'0' + 26,
|
||||
byte @ b'A' ... b'Z' => byte - b'A',
|
||||
byte @ b'a' ... b'z' => byte - b'a',
|
||||
_ => return None
|
||||
byte @ b'0'..=b'9' => byte - b'0' + 26,
|
||||
byte @ b'A'..=b'Z' => byte - b'A',
|
||||
byte @ b'a'..=b'z' => byte - b'a',
|
||||
_ => return None,
|
||||
} as u32;
|
||||
if digit > (u32::MAX - i) / weight {
|
||||
return None // Overflow
|
||||
return None; // Overflow
|
||||
}
|
||||
i += digit * weight;
|
||||
let t = if k <= bias { T_MIN }
|
||||
else if k >= bias + T_MAX { T_MAX }
|
||||
else { k - bias };
|
||||
let t = if k <= bias {
|
||||
T_MIN
|
||||
} else if k >= bias + T_MAX {
|
||||
T_MAX
|
||||
} else {
|
||||
k - bias
|
||||
};
|
||||
if digit < t {
|
||||
break
|
||||
break;
|
||||
}
|
||||
if weight > u32::MAX / (BASE - t) {
|
||||
return None // Overflow
|
||||
return None; // Overflow
|
||||
}
|
||||
weight *= BASE - t;
|
||||
k += BASE;
|
||||
byte = match iter.next() {
|
||||
None => return None, // End of input before the end of this delta
|
||||
None => return None, // End of input before the end of this delta
|
||||
Some(byte) => byte,
|
||||
};
|
||||
}
|
||||
let length = output.len() as u32;
|
||||
bias = adapt(i - previous_i, length + 1, previous_i == 0);
|
||||
if i / (length + 1) > u32::MAX - code_point {
|
||||
return None // Overflow
|
||||
return None; // Overflow
|
||||
}
|
||||
// i was supposed to wrap around from length+1 to 0,
|
||||
// incrementing code_point each time.
|
||||
@@ -118,7 +121,7 @@ pub fn decode(input: &str) -> Option<Vec<char>> {
|
||||
i %= length + 1;
|
||||
let c = match char::from_u32(code_point) {
|
||||
Some(c) => c,
|
||||
None => return None
|
||||
None => return None,
|
||||
};
|
||||
output.insert(i as usize, c);
|
||||
i += 1;
|
||||
@@ -126,7 +129,6 @@ pub fn decode(input: &str) -> Option<Vec<char>> {
|
||||
Some(output)
|
||||
}
|
||||
|
||||
|
||||
/// Convert an Unicode `str` to Punycode.
|
||||
///
|
||||
/// This is a convenience wrapper around `encode`.
|
||||
@@ -135,16 +137,16 @@ pub fn encode_str(input: &str) -> Option<String> {
|
||||
encode(&input.chars().collect::<Vec<char>>())
|
||||
}
|
||||
|
||||
|
||||
/// Convert Unicode to Punycode.
|
||||
///
|
||||
/// Return None on overflow, which can only happen on inputs that would take more than
|
||||
/// 63 encoded bytes, the DNS limit on domain name labels.
|
||||
pub fn encode(input: &[char]) -> Option<String> {
|
||||
// Handle "basic" (ASCII) code points. They are encoded as-is.
|
||||
let output_bytes = input.iter().filter_map(|&c|
|
||||
if c.is_ascii() { Some(c as u8) } else { None }
|
||||
).collect();
|
||||
let output_bytes = input
|
||||
.iter()
|
||||
.filter_map(|&c| if c.is_ascii() { Some(c as u8) } else { None })
|
||||
.collect();
|
||||
let mut output = unsafe { String::from_utf8_unchecked(output_bytes) };
|
||||
let basic_length = output.len() as u32;
|
||||
if basic_length > 0 {
|
||||
@@ -158,10 +160,14 @@ pub fn encode(input: &[char]) -> Option<String> {
|
||||
while processed < input_length {
|
||||
// All code points < code_point have been handled already.
|
||||
// Find the next larger one.
|
||||
let min_code_point = input.iter().map(|&c| c as u32)
|
||||
.filter(|&c| c >= code_point).min().unwrap();
|
||||
let min_code_point = input
|
||||
.iter()
|
||||
.map(|&c| c as u32)
|
||||
.filter(|&c| c >= code_point)
|
||||
.min()
|
||||
.unwrap();
|
||||
if min_code_point - code_point > (u32::MAX - delta) / (processed + 1) {
|
||||
return None // Overflow
|
||||
return None; // Overflow
|
||||
}
|
||||
// Increase delta to advance the decoder’s <code_point,i> state to <min_code_point,0>
|
||||
delta += (min_code_point - code_point) * (processed + 1);
|
||||
@@ -171,7 +177,7 @@ pub fn encode(input: &[char]) -> Option<String> {
|
||||
if c < code_point {
|
||||
delta += 1;
|
||||
if delta == 0 {
|
||||
return None // Overflow
|
||||
return None; // Overflow
|
||||
}
|
||||
}
|
||||
if c == code_point {
|
||||
@@ -179,11 +185,15 @@ pub fn encode(input: &[char]) -> Option<String> {
|
||||
let mut q = delta;
|
||||
let mut k = BASE;
|
||||
loop {
|
||||
let t = if k <= bias { T_MIN }
|
||||
else if k >= bias + T_MAX { T_MAX }
|
||||
else { k - bias };
|
||||
let t = if k <= bias {
|
||||
T_MIN
|
||||
} else if k >= bias + T_MAX {
|
||||
T_MAX
|
||||
} else {
|
||||
k - bias
|
||||
};
|
||||
if q < t {
|
||||
break
|
||||
break;
|
||||
}
|
||||
let value = t + ((q - t) % (BASE - t));
|
||||
output.push(value_to_digit(value));
|
||||
@@ -202,12 +212,11 @@ pub fn encode(input: &[char]) -> Option<String> {
|
||||
Some(output)
|
||||
}
|
||||
|
||||
|
||||
#[inline]
|
||||
fn value_to_digit(value: u32) -> char {
|
||||
match value {
|
||||
0 ... 25 => (value as u8 + 'a' as u8) as char, // a..z
|
||||
26 ... 35 => (value as u8 - 26 + '0' as u8) as char, // 0..9
|
||||
_ => panic!()
|
||||
0..=25 => (value as u8 + 'a' as u8) as char, // a..z
|
||||
26..=35 => (value as u8 - 26 + '0' as u8) as char, // 0..9
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
+195
-133
@@ -11,18 +11,14 @@
|
||||
|
||||
use self::Mapping::*;
|
||||
use punycode;
|
||||
#[allow(unused_imports, deprecated)]
|
||||
use std::ascii::AsciiExt;
|
||||
use std::cmp::Ordering::{Equal, Less, Greater};
|
||||
use unicode_bidi::{BidiClass, bidi_class};
|
||||
use unicode_normalization::UnicodeNormalization;
|
||||
use std::cmp::Ordering::{Equal, Greater, Less};
|
||||
use unicode_bidi::{bidi_class, BidiClass};
|
||||
use unicode_normalization::char::is_combining_mark;
|
||||
use unicode_normalization::UnicodeNormalization;
|
||||
|
||||
include!("uts46_mapping_table.rs");
|
||||
|
||||
|
||||
pub static PUNYCODE_PREFIX: &'static str = "xn--";
|
||||
|
||||
const PUNYCODE_PREFIX: &'static str = "xn--";
|
||||
|
||||
#[derive(Debug)]
|
||||
struct StringTableSlice {
|
||||
@@ -68,28 +64,30 @@ fn find_char(codepoint: char) -> &'static Mapping {
|
||||
Equal
|
||||
}
|
||||
});
|
||||
r.ok().map(|i| {
|
||||
const SINGLE_MARKER: u16 = 1 << 15;
|
||||
r.ok()
|
||||
.map(|i| {
|
||||
const SINGLE_MARKER: u16 = 1 << 15;
|
||||
|
||||
let x = INDEX_TABLE[i];
|
||||
let single = (x & SINGLE_MARKER) != 0;
|
||||
let offset = !SINGLE_MARKER & x;
|
||||
let x = INDEX_TABLE[i];
|
||||
let single = (x & SINGLE_MARKER) != 0;
|
||||
let offset = !SINGLE_MARKER & x;
|
||||
|
||||
if single {
|
||||
&MAPPING_TABLE[offset as usize]
|
||||
} else {
|
||||
&MAPPING_TABLE[(offset + (codepoint as u16 - TABLE[i].from as u16)) as usize]
|
||||
}
|
||||
}).unwrap()
|
||||
if single {
|
||||
&MAPPING_TABLE[offset as usize]
|
||||
} else {
|
||||
&MAPPING_TABLE[(offset + (codepoint as u16 - TABLE[i].from as u16)) as usize]
|
||||
}
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn map_char(codepoint: char, flags: Flags, output: &mut String, errors: &mut Vec<Error>) {
|
||||
fn map_char(codepoint: char, config: Config, output: &mut String, errors: &mut Vec<Error>) {
|
||||
match *find_char(codepoint) {
|
||||
Mapping::Valid => output.push(codepoint),
|
||||
Mapping::Ignored => {},
|
||||
Mapping::Ignored => {}
|
||||
Mapping::Mapped(ref slice) => output.push_str(decode_slice(slice)),
|
||||
Mapping::Deviation(ref slice) => {
|
||||
if flags.transitional_processing {
|
||||
if config.transitional_processing {
|
||||
output.push_str(decode_slice(slice))
|
||||
} else {
|
||||
output.push(codepoint)
|
||||
@@ -100,13 +98,13 @@ fn map_char(codepoint: char, flags: Flags, output: &mut String, errors: &mut Vec
|
||||
output.push(codepoint);
|
||||
}
|
||||
Mapping::DisallowedStd3Valid => {
|
||||
if flags.use_std3_ascii_rules {
|
||||
if config.use_std3_ascii_rules {
|
||||
errors.push(Error::DissallowedByStd3AsciiRules);
|
||||
}
|
||||
output.push(codepoint)
|
||||
}
|
||||
Mapping::DisallowedStd3Mapped(ref slice) => {
|
||||
if flags.use_std3_ascii_rules {
|
||||
if config.use_std3_ascii_rules {
|
||||
errors.push(Error::DissallowedMappedInStd3);
|
||||
}
|
||||
output.push_str(decode_slice(slice))
|
||||
@@ -135,16 +133,23 @@ fn passes_bidi(label: &str, is_bidi_domain: bool) -> bool {
|
||||
loop {
|
||||
match chars.next() {
|
||||
Some(c) => {
|
||||
if !matches!(bidi_class(c),
|
||||
BidiClass::L | BidiClass::EN |
|
||||
BidiClass::ES | BidiClass::CS |
|
||||
BidiClass::ET | BidiClass::ON |
|
||||
BidiClass::BN | BidiClass::NSM
|
||||
) {
|
||||
if !matches!(
|
||||
bidi_class(c),
|
||||
BidiClass::L
|
||||
| BidiClass::EN
|
||||
| BidiClass::ES
|
||||
| BidiClass::CS
|
||||
| BidiClass::ET
|
||||
| BidiClass::ON
|
||||
| BidiClass::BN
|
||||
| BidiClass::NSM
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
None => { break; },
|
||||
}
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,16 +163,18 @@ fn passes_bidi(label: &str, is_bidi_domain: bool) -> bool {
|
||||
last_non_nsm = rev_chars.next();
|
||||
continue;
|
||||
}
|
||||
_ => { break; },
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
match last_non_nsm {
|
||||
Some(c) if bidi_class(c) == BidiClass::L
|
||||
|| bidi_class(c) == BidiClass::EN => {},
|
||||
Some(_) => { return false; },
|
||||
Some(c) if bidi_class(c) == BidiClass::L || bidi_class(c) == BidiClass::EN => {}
|
||||
Some(_) => {
|
||||
return false;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// RTL label
|
||||
@@ -188,33 +195,51 @@ fn passes_bidi(label: &str, is_bidi_domain: bool) -> bool {
|
||||
found_an = true;
|
||||
}
|
||||
|
||||
if !matches!(char_class, BidiClass::R | BidiClass::AL |
|
||||
BidiClass::AN | BidiClass::EN |
|
||||
BidiClass::ES | BidiClass::CS |
|
||||
BidiClass::ET | BidiClass::ON |
|
||||
BidiClass::BN | BidiClass::NSM) {
|
||||
if !matches!(
|
||||
char_class,
|
||||
BidiClass::R
|
||||
| BidiClass::AL
|
||||
| BidiClass::AN
|
||||
| BidiClass::EN
|
||||
| BidiClass::ES
|
||||
| BidiClass::CS
|
||||
| BidiClass::ET
|
||||
| BidiClass::ON
|
||||
| BidiClass::BN
|
||||
| BidiClass::NSM
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
None => { break; },
|
||||
}
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Rule 3
|
||||
let mut rev_chars = label.chars().rev();
|
||||
let mut last = rev_chars.next();
|
||||
loop { // must end in L or EN followed by 0 or more NSM
|
||||
loop {
|
||||
// must end in L or EN followed by 0 or more NSM
|
||||
match last {
|
||||
Some(c) if bidi_class(c) == BidiClass::NSM => {
|
||||
last = rev_chars.next();
|
||||
continue;
|
||||
}
|
||||
_ => { break; },
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
match last {
|
||||
Some(c) if matches!(bidi_class(c), BidiClass::R | BidiClass::AL |
|
||||
BidiClass::EN | BidiClass::AN) => {},
|
||||
_ => { return false; }
|
||||
Some(c)
|
||||
if matches!(
|
||||
bidi_class(c),
|
||||
BidiClass::R | BidiClass::AL | BidiClass::EN | BidiClass::AN
|
||||
) => {}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Rule 4
|
||||
@@ -233,34 +258,30 @@ fn passes_bidi(label: &str, is_bidi_domain: bool) -> bool {
|
||||
}
|
||||
|
||||
/// http://www.unicode.org/reports/tr46/#Validity_Criteria
|
||||
fn validate_full(label: &str, is_bidi_domain: bool, flags: Flags, errors: &mut Vec<Error>) {
|
||||
fn validate_full(label: &str, is_bidi_domain: bool, config: Config, errors: &mut Vec<Error>) {
|
||||
// V1: Must be in NFC form.
|
||||
if label.nfc().ne(label.chars()) {
|
||||
errors.push(Error::ValidityCriteria);
|
||||
} else {
|
||||
validate(label, is_bidi_domain, flags, errors);
|
||||
validate(label, is_bidi_domain, config, errors);
|
||||
}
|
||||
}
|
||||
|
||||
fn validate(label: &str, is_bidi_domain: bool, flags: Flags, errors: &mut Vec<Error>) {
|
||||
fn validate(label: &str, is_bidi_domain: bool, config: Config, errors: &mut Vec<Error>) {
|
||||
let first_char = label.chars().next();
|
||||
if first_char == None {
|
||||
// Empty string, pass
|
||||
}
|
||||
|
||||
// V2: No U+002D HYPHEN-MINUS in both third and fourth positions.
|
||||
//
|
||||
// NOTE: Spec says that the label must not contain a HYPHEN-MINUS character in both the
|
||||
// third and fourth positions. But nobody follows this criteria. See the spec issue below:
|
||||
// https://github.com/whatwg/url/issues/53
|
||||
//
|
||||
// TODO: Add *CheckHyphens* flag.
|
||||
|
||||
// V3: neither begin nor end with a U+002D HYPHEN-MINUS
|
||||
else if label.starts_with("-") || label.ends_with("-") {
|
||||
else if config.check_hyphens && (label.starts_with("-") || label.ends_with("-")) {
|
||||
errors.push(Error::ValidityCriteria);
|
||||
}
|
||||
|
||||
// V4: not contain a U+002E FULL STOP
|
||||
//
|
||||
// Here, label can't contain '.' since the input is from .split('.')
|
||||
@@ -269,17 +290,15 @@ fn validate(label: &str, is_bidi_domain: bool, flags: Flags, errors: &mut Vec<Er
|
||||
else if is_combining_mark(first_char.unwrap()) {
|
||||
errors.push(Error::ValidityCriteria);
|
||||
}
|
||||
|
||||
// V6: Check against Mapping Table
|
||||
else if label.chars().any(|c| match *find_char(c) {
|
||||
Mapping::Valid => false,
|
||||
Mapping::Deviation(_) => flags.transitional_processing,
|
||||
Mapping::DisallowedStd3Valid => flags.use_std3_ascii_rules,
|
||||
Mapping::Deviation(_) => config.transitional_processing,
|
||||
Mapping::DisallowedStd3Valid => config.use_std3_ascii_rules,
|
||||
_ => true,
|
||||
}) {
|
||||
errors.push(Error::ValidityCriteria);
|
||||
}
|
||||
|
||||
// V7: ContextJ rules
|
||||
//
|
||||
// TODO: Implement rules and add *CheckJoiners* flag.
|
||||
@@ -287,17 +306,16 @@ fn validate(label: &str, is_bidi_domain: bool, flags: Flags, errors: &mut Vec<Er
|
||||
// V8: Bidi rules
|
||||
//
|
||||
// TODO: Add *CheckBidi* flag
|
||||
else if !passes_bidi(label, is_bidi_domain)
|
||||
{
|
||||
else if !passes_bidi(label, is_bidi_domain) {
|
||||
errors.push(Error::ValidityCriteria);
|
||||
}
|
||||
}
|
||||
|
||||
/// http://www.unicode.org/reports/tr46/#Processing
|
||||
fn processing(domain: &str, flags: Flags, errors: &mut Vec<Error>) -> String {
|
||||
fn processing(domain: &str, config: Config, errors: &mut Vec<Error>) -> String {
|
||||
let mut mapped = String::with_capacity(domain.len());
|
||||
for c in domain.chars() {
|
||||
map_char(c, flags, &mut mapped, errors)
|
||||
map_char(c, config, &mut mapped, errors)
|
||||
}
|
||||
let mut normalized = String::with_capacity(mapped.len());
|
||||
normalized.extend(mapped.nfc());
|
||||
@@ -305,18 +323,18 @@ fn processing(domain: &str, flags: Flags, errors: &mut Vec<Error>) -> String {
|
||||
// Find out if it's a Bidi Domain Name
|
||||
//
|
||||
// First, check for literal bidi chars
|
||||
let mut is_bidi_domain = domain.chars().any(|c|
|
||||
matches!(bidi_class(c), BidiClass::R | BidiClass::AL | BidiClass::AN)
|
||||
);
|
||||
let mut is_bidi_domain = domain
|
||||
.chars()
|
||||
.any(|c| matches!(bidi_class(c), BidiClass::R | BidiClass::AL | BidiClass::AN));
|
||||
if !is_bidi_domain {
|
||||
// Then check for punycode-encoded bidi chars
|
||||
for label in normalized.split('.') {
|
||||
if label.starts_with(PUNYCODE_PREFIX) {
|
||||
match punycode::decode_to_string(&label[PUNYCODE_PREFIX.len()..]) {
|
||||
Some(decoded_label) => {
|
||||
if decoded_label.chars().any(|c|
|
||||
if decoded_label.chars().any(|c| {
|
||||
matches!(bidi_class(c), BidiClass::R | BidiClass::AL | BidiClass::AN)
|
||||
) {
|
||||
}) {
|
||||
is_bidi_domain = true;
|
||||
}
|
||||
}
|
||||
@@ -338,26 +356,124 @@ fn processing(domain: &str, flags: Flags, errors: &mut Vec<Error>) -> String {
|
||||
if label.starts_with(PUNYCODE_PREFIX) {
|
||||
match punycode::decode_to_string(&label[PUNYCODE_PREFIX.len()..]) {
|
||||
Some(decoded_label) => {
|
||||
let flags = Flags { transitional_processing: false, ..flags };
|
||||
validate_full(&decoded_label, is_bidi_domain, flags, errors);
|
||||
let config = config.transitional_processing(false);
|
||||
validate_full(&decoded_label, is_bidi_domain, config, errors);
|
||||
validated.push_str(&decoded_label)
|
||||
}
|
||||
None => errors.push(Error::PunycodeError)
|
||||
None => errors.push(Error::PunycodeError),
|
||||
}
|
||||
} else {
|
||||
// `normalized` is already `NFC` so we can skip that check
|
||||
validate(label, is_bidi_domain, flags, errors);
|
||||
validate(label, is_bidi_domain, config, errors);
|
||||
validated.push_str(label)
|
||||
}
|
||||
}
|
||||
validated
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Flags {
|
||||
pub use_std3_ascii_rules: bool,
|
||||
pub transitional_processing: bool,
|
||||
pub verify_dns_length: bool,
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Config {
|
||||
use_std3_ascii_rules: bool,
|
||||
transitional_processing: bool,
|
||||
verify_dns_length: bool,
|
||||
check_hyphens: bool,
|
||||
}
|
||||
|
||||
/// The defaults are that of https://url.spec.whatwg.org/#idna
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
use_std3_ascii_rules: false,
|
||||
transitional_processing: false,
|
||||
check_hyphens: false,
|
||||
// check_bidi: true,
|
||||
// check_joiners: true,
|
||||
|
||||
// Only use for to_ascii, not to_unicode
|
||||
verify_dns_length: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
#[inline]
|
||||
pub fn use_std3_ascii_rules(mut self, value: bool) -> Self {
|
||||
self.use_std3_ascii_rules = value;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn transitional_processing(mut self, value: bool) -> Self {
|
||||
self.transitional_processing = value;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn verify_dns_length(mut self, value: bool) -> Self {
|
||||
self.verify_dns_length = value;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn check_hyphens(mut self, value: bool) -> Self {
|
||||
self.check_hyphens = value;
|
||||
self
|
||||
}
|
||||
|
||||
/// http://www.unicode.org/reports/tr46/#ToASCII
|
||||
pub fn to_ascii(self, domain: &str) -> Result<String, Errors> {
|
||||
let mut errors = Vec::new();
|
||||
let mut result = String::new();
|
||||
let mut first = true;
|
||||
for label in processing(domain, self, &mut errors).split('.') {
|
||||
if !first {
|
||||
result.push('.');
|
||||
}
|
||||
first = false;
|
||||
if label.is_ascii() {
|
||||
result.push_str(label);
|
||||
} else {
|
||||
match punycode::encode_str(label) {
|
||||
Some(x) => {
|
||||
result.push_str(PUNYCODE_PREFIX);
|
||||
result.push_str(&x);
|
||||
}
|
||||
None => errors.push(Error::PunycodeError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.verify_dns_length {
|
||||
let domain = if result.ends_with(".") {
|
||||
&result[..result.len() - 1]
|
||||
} else {
|
||||
&*result
|
||||
};
|
||||
if domain.len() < 1 || domain.split('.').any(|label| label.len() < 1) {
|
||||
errors.push(Error::TooShortForDns)
|
||||
}
|
||||
if domain.len() > 253 || domain.split('.').any(|label| label.len() > 63) {
|
||||
errors.push(Error::TooLongForDns)
|
||||
}
|
||||
}
|
||||
if errors.is_empty() {
|
||||
Ok(result)
|
||||
} else {
|
||||
Err(Errors(errors))
|
||||
}
|
||||
}
|
||||
|
||||
/// http://www.unicode.org/reports/tr46/#ToUnicode
|
||||
pub fn to_unicode(self, domain: &str) -> (String, Result<(), Errors>) {
|
||||
let mut errors = Vec::new();
|
||||
let domain = processing(domain, self, &mut errors);
|
||||
let errors = if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Errors(errors))
|
||||
};
|
||||
(domain, errors)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
@@ -377,57 +493,3 @@ enum Error {
|
||||
/// More details may be exposed in the future.
|
||||
#[derive(Debug)]
|
||||
pub struct Errors(Vec<Error>);
|
||||
|
||||
/// http://www.unicode.org/reports/tr46/#ToASCII
|
||||
pub fn to_ascii(domain: &str, flags: Flags) -> Result<String, Errors> {
|
||||
let mut errors = Vec::new();
|
||||
let mut result = String::new();
|
||||
let mut first = true;
|
||||
for label in processing(domain, flags, &mut errors).split('.') {
|
||||
if !first {
|
||||
result.push('.');
|
||||
}
|
||||
first = false;
|
||||
if label.is_ascii() {
|
||||
result.push_str(label);
|
||||
} else {
|
||||
match punycode::encode_str(label) {
|
||||
Some(x) => {
|
||||
result.push_str(PUNYCODE_PREFIX);
|
||||
result.push_str(&x);
|
||||
},
|
||||
None => errors.push(Error::PunycodeError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if flags.verify_dns_length {
|
||||
let domain = if result.ends_with(".") { &result[..result.len()-1] } else { &*result };
|
||||
if domain.len() < 1 || domain.split('.').any(|label| label.len() < 1) {
|
||||
errors.push(Error::TooShortForDns)
|
||||
}
|
||||
if domain.len() > 253 || domain.split('.').any(|label| label.len() > 63) {
|
||||
errors.push(Error::TooLongForDns)
|
||||
}
|
||||
}
|
||||
if errors.is_empty() {
|
||||
Ok(result)
|
||||
} else {
|
||||
Err(Errors(errors))
|
||||
}
|
||||
}
|
||||
|
||||
/// http://www.unicode.org/reports/tr46/#ToUnicode
|
||||
///
|
||||
/// Only `use_std3_ascii_rules` is used in `flags`.
|
||||
pub fn to_unicode(domain: &str, mut flags: Flags) -> (String, Result<(), Errors>) {
|
||||
flags.transitional_processing = false;
|
||||
let mut errors = Vec::new();
|
||||
let domain = processing(domain, flags, &mut errors);
|
||||
let errors = if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Errors(errors))
|
||||
};
|
||||
(domain, errors)
|
||||
}
|
||||
|
||||
+35
-25
@@ -15,19 +15,25 @@ fn one_test(decoded: &str, encoded: &str) {
|
||||
None => panic!("Decoding {} failed.", encoded),
|
||||
Some(result) => {
|
||||
let result = result.into_iter().collect::<String>();
|
||||
assert!(result == decoded,
|
||||
format!("Incorrect decoding of \"{}\":\n \"{}\"\n!= \"{}\"\n",
|
||||
encoded, result, decoded))
|
||||
assert!(
|
||||
result == decoded,
|
||||
format!(
|
||||
"Incorrect decoding of \"{}\":\n \"{}\"\n!= \"{}\"\n",
|
||||
encoded, result, decoded
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
match encode_str(decoded) {
|
||||
None => panic!("Encoding {} failed.", decoded),
|
||||
Some(result) => {
|
||||
assert!(result == encoded,
|
||||
format!("Incorrect encoding of \"{}\":\n \"{}\"\n!= \"{}\"\n",
|
||||
decoded, result, encoded))
|
||||
}
|
||||
Some(result) => assert!(
|
||||
result == encoded,
|
||||
format!(
|
||||
"Incorrect encoding of \"{}\":\n \"{}\"\n!= \"{}\"\n",
|
||||
decoded, result, encoded
|
||||
)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,25 +47,29 @@ fn get_string<'a>(map: &'a Object, key: &str) -> &'a str {
|
||||
|
||||
pub fn collect_tests<F: FnMut(String, TestFn)>(add_test: &mut F) {
|
||||
match Json::from_str(include_str!("punycode_tests.json")) {
|
||||
Ok(Json::Array(tests)) => for (i, test) in tests.into_iter().enumerate() {
|
||||
match test {
|
||||
Json::Object(o) => {
|
||||
let test_name = {
|
||||
let desc = get_string(&o, "description");
|
||||
Ok(Json::Array(tests)) => {
|
||||
for (i, test) in tests.into_iter().enumerate() {
|
||||
match test {
|
||||
Json::Object(o) => {
|
||||
let test_name = {
|
||||
let desc = get_string(&o, "description");
|
||||
if desc.is_empty() {
|
||||
format!("Punycode {}", i + 1)
|
||||
} else {
|
||||
format!("Punycode {}: {}", i + 1, desc)
|
||||
}
|
||||
};
|
||||
add_test(test_name, TestFn::dyn_test_fn(move || one_test(
|
||||
get_string(&o, "decoded"),
|
||||
get_string(&o, "encoded"),
|
||||
)))
|
||||
format!("Punycode {}", i + 1)
|
||||
} else {
|
||||
format!("Punycode {}: {}", i + 1, desc)
|
||||
}
|
||||
};
|
||||
add_test(
|
||||
test_name,
|
||||
TestFn::dyn_test_fn(move || {
|
||||
one_test(get_string(&o, "decoded"), get_string(&o, "encoded"))
|
||||
}),
|
||||
)
|
||||
}
|
||||
_ => panic!(),
|
||||
}
|
||||
_ => panic!(),
|
||||
}
|
||||
},
|
||||
other => panic!("{:?}", other)
|
||||
}
|
||||
other => panic!("{:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
+9
-9
@@ -1,16 +1,13 @@
|
||||
extern crate idna;
|
||||
extern crate unicode_normalization;
|
||||
|
||||
use idna::uts46;
|
||||
use unicode_normalization::char::is_combining_mark;
|
||||
|
||||
|
||||
fn _to_ascii(domain: &str) -> Result<String, uts46::Errors> {
|
||||
uts46::to_ascii(domain, uts46::Flags {
|
||||
transitional_processing: false,
|
||||
use_std3_ascii_rules: true,
|
||||
verify_dns_length: true,
|
||||
})
|
||||
fn _to_ascii(domain: &str) -> Result<String, idna::Errors> {
|
||||
idna::Config::default()
|
||||
.verify_dns_length(true)
|
||||
.use_std3_ascii_rules(true)
|
||||
.to_ascii(domain)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -29,7 +26,10 @@ fn test_v8_bidi_rules() {
|
||||
assert_eq!(_to_ascii("אבּג").unwrap(), "xn--kdb3bdf");
|
||||
assert_eq!(_to_ascii("ابج").unwrap(), "xn--mgbcm");
|
||||
assert_eq!(_to_ascii("abc.ابج").unwrap(), "abc.xn--mgbcm");
|
||||
assert_eq!(_to_ascii("אבּג.ابج").unwrap(), "xn--kdb3bdf.xn--mgbcm");
|
||||
assert_eq!(
|
||||
_to_ascii("אבּג.ابج").unwrap(),
|
||||
"xn--kdb3bdf.xn--mgbcm"
|
||||
);
|
||||
|
||||
// Bidi domain names cannot start with digits
|
||||
assert!(_to_ascii("0a.\u{05D0}").is_err());
|
||||
|
||||
+75
-49
@@ -7,19 +7,18 @@
|
||||
// except according to those terms.
|
||||
|
||||
use std::char;
|
||||
use idna::uts46;
|
||||
use test::TestFn;
|
||||
|
||||
pub fn collect_tests<F: FnMut(String, TestFn)>(add_test: &mut F) {
|
||||
// http://www.unicode.org/Public/idna/latest/IdnaTest.txt
|
||||
for (i, line) in include_str!("IdnaTest.txt").lines().enumerate() {
|
||||
if line == "" || line.starts_with("#") {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
// Remove comments
|
||||
let mut line = match line.find("#") {
|
||||
Some(index) => &line[0..index],
|
||||
None => line
|
||||
None => line,
|
||||
};
|
||||
|
||||
let mut expected_failure = false;
|
||||
@@ -35,61 +34,85 @@ pub fn collect_tests<F: FnMut(String, TestFn)>(add_test: &mut F) {
|
||||
let source = unescape(original);
|
||||
let to_unicode = pieces.remove(0);
|
||||
let to_ascii = pieces.remove(0);
|
||||
let nv8 = if pieces.len() > 0 { pieces.remove(0) } else { "" };
|
||||
let nv8 = if pieces.len() > 0 {
|
||||
pieces.remove(0)
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
if expected_failure {
|
||||
continue;
|
||||
}
|
||||
|
||||
let test_name = format!("UTS #46 line {}", i + 1);
|
||||
add_test(test_name, TestFn::dyn_test_fn(move || {
|
||||
let result = uts46::to_ascii(&source, uts46::Flags {
|
||||
use_std3_ascii_rules: true,
|
||||
transitional_processing: test_type == "T",
|
||||
verify_dns_length: true,
|
||||
});
|
||||
add_test(
|
||||
test_name,
|
||||
TestFn::dyn_test_fn(move || {
|
||||
let result = idna::Config::default()
|
||||
.use_std3_ascii_rules(true)
|
||||
.verify_dns_length(true)
|
||||
.check_hyphens(true)
|
||||
.transitional_processing(test_type == "T")
|
||||
.to_ascii(&source);
|
||||
|
||||
if to_ascii.starts_with("[") {
|
||||
if to_ascii.starts_with("[C") {
|
||||
// http://unicode.org/reports/tr46/#Deviations
|
||||
// applications that perform IDNA2008 lookup are not required to check
|
||||
// for these contexts
|
||||
if to_ascii.starts_with("[") {
|
||||
if to_ascii.starts_with("[C") {
|
||||
// http://unicode.org/reports/tr46/#Deviations
|
||||
// applications that perform IDNA2008 lookup are not required to check
|
||||
// for these contexts
|
||||
return;
|
||||
}
|
||||
if to_ascii == "[V2]" {
|
||||
// Everybody ignores V2
|
||||
// https://github.com/servo/rust-url/pull/240
|
||||
// https://github.com/whatwg/url/issues/53#issuecomment-181528158
|
||||
// http://www.unicode.org/review/pri317/
|
||||
return;
|
||||
}
|
||||
let res = result.ok();
|
||||
assert!(
|
||||
res == None,
|
||||
"Expected error. result: {} | original: {} | source: {}",
|
||||
res.unwrap(),
|
||||
original,
|
||||
source
|
||||
);
|
||||
return;
|
||||
}
|
||||
if to_ascii == "[V2]" {
|
||||
// Everybody ignores V2
|
||||
// https://github.com/servo/rust-url/pull/240
|
||||
// https://github.com/whatwg/url/issues/53#issuecomment-181528158
|
||||
// http://www.unicode.org/review/pri317/
|
||||
return;
|
||||
}
|
||||
let res = result.ok();
|
||||
assert!(res == None, "Expected error. result: {} | original: {} | source: {}",
|
||||
res.unwrap(), original, source);
|
||||
return;
|
||||
}
|
||||
|
||||
let to_ascii = if to_ascii.len() > 0 {
|
||||
to_ascii.to_string()
|
||||
} else {
|
||||
if to_unicode.len() > 0 {
|
||||
to_unicode.to_string()
|
||||
let to_ascii = if to_ascii.len() > 0 {
|
||||
to_ascii.to_string()
|
||||
} else {
|
||||
source.clone()
|
||||
if to_unicode.len() > 0 {
|
||||
to_unicode.to_string()
|
||||
} else {
|
||||
source.clone()
|
||||
}
|
||||
};
|
||||
|
||||
if nv8 == "NV8" {
|
||||
// This result isn't valid under IDNA2008. Skip it
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if nv8 == "NV8" {
|
||||
// This result isn't valid under IDNA2008. Skip it
|
||||
return;
|
||||
}
|
||||
|
||||
assert!(result.is_ok(), "Couldn't parse {} | original: {} | error: {:?}",
|
||||
source, original, result.err());
|
||||
let output = result.ok().unwrap();
|
||||
assert!(output == to_ascii, "result: {} | expected: {} | original: {} | source: {}",
|
||||
output, to_ascii, original, source);
|
||||
}))
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Couldn't parse {} | original: {} | error: {:?}",
|
||||
source,
|
||||
original,
|
||||
result.err()
|
||||
);
|
||||
let output = result.ok().unwrap();
|
||||
assert!(
|
||||
output == to_ascii,
|
||||
"result: {} | expected: {} | original: {} | source: {}",
|
||||
output,
|
||||
to_ascii,
|
||||
original,
|
||||
source
|
||||
);
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +122,7 @@ fn unescape(input: &str) -> String {
|
||||
loop {
|
||||
match chars.next() {
|
||||
None => return output,
|
||||
Some(c) =>
|
||||
Some(c) => {
|
||||
if c == '\\' {
|
||||
match chars.next().unwrap() {
|
||||
'\\' => output.push('\\'),
|
||||
@@ -108,10 +131,12 @@ fn unescape(input: &str) -> String {
|
||||
let c2 = chars.next().unwrap().to_digit(16).unwrap();
|
||||
let c3 = chars.next().unwrap().to_digit(16).unwrap();
|
||||
let c4 = chars.next().unwrap().to_digit(16).unwrap();
|
||||
match char::from_u32(((c1 * 16 + c2) * 16 + c3) * 16 + c4)
|
||||
{
|
||||
match char::from_u32(((c1 * 16 + c2) * 16 + c3) * 16 + c4) {
|
||||
Some(c) => output.push(c),
|
||||
None => { output.push_str(&format!("\\u{:X}{:X}{:X}{:X}",c1,c2,c3,c4)); }
|
||||
None => {
|
||||
output
|
||||
.push_str(&format!("\\u{:X}{:X}{:X}{:X}", c1, c2, c3, c4));
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => panic!("Invalid test data input"),
|
||||
@@ -119,6 +144,7 @@ fn unescape(input: &str) -> String {
|
||||
} else {
|
||||
output.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+37
-48
@@ -32,7 +32,6 @@
|
||||
//! assert_eq!(utf8_percent_encode("foo bar?", DEFAULT_ENCODE_SET).to_string(), "foo%20bar%3F");
|
||||
//! ```
|
||||
|
||||
use std::ascii::AsciiExt;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::slice;
|
||||
@@ -176,23 +175,23 @@ define_encode_set! {
|
||||
pub fn percent_encode_byte(byte: u8) -> &'static str {
|
||||
let index = usize::from(byte) * 3;
|
||||
&"\
|
||||
%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F\
|
||||
%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F\
|
||||
%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F\
|
||||
%30%31%32%33%34%35%36%37%38%39%3A%3B%3C%3D%3E%3F\
|
||||
%40%41%42%43%44%45%46%47%48%49%4A%4B%4C%4D%4E%4F\
|
||||
%50%51%52%53%54%55%56%57%58%59%5A%5B%5C%5D%5E%5F\
|
||||
%60%61%62%63%64%65%66%67%68%69%6A%6B%6C%6D%6E%6F\
|
||||
%70%71%72%73%74%75%76%77%78%79%7A%7B%7C%7D%7E%7F\
|
||||
%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F\
|
||||
%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F\
|
||||
%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF\
|
||||
%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF\
|
||||
%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF\
|
||||
%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF\
|
||||
%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF\
|
||||
%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF\
|
||||
"[index..index + 3]
|
||||
%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F\
|
||||
%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F\
|
||||
%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F\
|
||||
%30%31%32%33%34%35%36%37%38%39%3A%3B%3C%3D%3E%3F\
|
||||
%40%41%42%43%44%45%46%47%48%49%4A%4B%4C%4D%4E%4F\
|
||||
%50%51%52%53%54%55%56%57%58%59%5A%5B%5C%5D%5E%5F\
|
||||
%60%61%62%63%64%65%66%67%68%69%6A%6B%6C%6D%6E%6F\
|
||||
%70%71%72%73%74%75%76%77%78%79%7A%7B%7C%7D%7E%7F\
|
||||
%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F\
|
||||
%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F\
|
||||
%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF\
|
||||
%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF\
|
||||
%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF\
|
||||
%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF\
|
||||
%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF\
|
||||
%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF\
|
||||
"[index..index + 3]
|
||||
}
|
||||
|
||||
/// Percent-encode the given bytes with the given encode set.
|
||||
@@ -260,7 +259,7 @@ impl<'a, E: EncodeSet> Iterator for PercentEncode<'a, E> {
|
||||
// 1 for first_byte + i for previous iterations of this loop
|
||||
let (unchanged_slice, remaining) = self.bytes.split_at(1 + i);
|
||||
self.bytes = remaining;
|
||||
return Some(unsafe { str::from_utf8_unchecked(unchanged_slice) })
|
||||
return Some(unsafe { str::from_utf8_unchecked(unchanged_slice) });
|
||||
} else {
|
||||
assert!(byte.is_ascii());
|
||||
}
|
||||
@@ -296,17 +295,15 @@ impl<'a, E: EncodeSet> From<PercentEncode<'a, E>> for Cow<'a, str> {
|
||||
fn from(mut iter: PercentEncode<'a, E>) -> Self {
|
||||
match iter.next() {
|
||||
None => "".into(),
|
||||
Some(first) => {
|
||||
match iter.next() {
|
||||
None => first.into(),
|
||||
Some(second) => {
|
||||
let mut string = first.to_owned();
|
||||
string.push_str(second);
|
||||
string.extend(iter);
|
||||
string.into()
|
||||
}
|
||||
Some(first) => match iter.next() {
|
||||
None => first.into(),
|
||||
Some(second) => {
|
||||
let mut string = first.to_owned();
|
||||
string.push_str(second);
|
||||
string.extend(iter);
|
||||
string.into()
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -328,7 +325,7 @@ impl<'a, E: EncodeSet> From<PercentEncode<'a, E>> for Cow<'a, str> {
|
||||
#[inline]
|
||||
pub fn percent_decode(input: &[u8]) -> PercentDecode {
|
||||
PercentDecode {
|
||||
bytes: input.iter()
|
||||
bytes: input.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,10 +385,8 @@ impl<'a> PercentDecode<'a> {
|
||||
let unchanged_bytes_len = initial_bytes.len() - bytes_iter.len() - 3;
|
||||
let mut decoded = initial_bytes[..unchanged_bytes_len].to_owned();
|
||||
decoded.push(decoded_byte);
|
||||
decoded.extend(PercentDecode {
|
||||
bytes: bytes_iter
|
||||
});
|
||||
return Some(decoded)
|
||||
decoded.extend(PercentDecode { bytes: bytes_iter });
|
||||
return Some(decoded);
|
||||
}
|
||||
}
|
||||
// Nothing to decode
|
||||
@@ -403,18 +398,14 @@ impl<'a> PercentDecode<'a> {
|
||||
/// This is return `Err` when the percent-decoded bytes are not well-formed in UTF-8.
|
||||
pub fn decode_utf8(self) -> Result<Cow<'a, str>, str::Utf8Error> {
|
||||
match self.clone().into() {
|
||||
Cow::Borrowed(bytes) => {
|
||||
match str::from_utf8(bytes) {
|
||||
Ok(s) => Ok(s.into()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
Cow::Owned(bytes) => {
|
||||
match String::from_utf8(bytes) {
|
||||
Ok(s) => Ok(s.into()),
|
||||
Err(e) => Err(e.utf8_error()),
|
||||
}
|
||||
}
|
||||
Cow::Borrowed(bytes) => match str::from_utf8(bytes) {
|
||||
Ok(s) => Ok(s.into()),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
Cow::Owned(bytes) => match String::from_utf8(bytes) {
|
||||
Ok(s) => Ok(s.into()),
|
||||
Err(e) => Err(e.utf8_error()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,5 +434,3 @@ fn decode_utf8_lossy(input: Cow<[u8]>) -> Cow<str> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
-146
@@ -1,146 +0,0 @@
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
+78
-142
@@ -13,13 +13,11 @@
|
||||
//! 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 percent_encoding::{percent_decode, percent_encode_byte};
|
||||
use query_encoding::{self, decode_utf8_lossy, EncodingOverride};
|
||||
use std::borrow::{Borrow, Cow};
|
||||
use std::fmt;
|
||||
use std::str;
|
||||
|
||||
|
||||
/// Convert a byte string in the `application/x-www-form-urlencoded` syntax
|
||||
/// into a iterator of (name, value) pairs.
|
||||
///
|
||||
@@ -29,63 +27,12 @@ use std::str;
|
||||
/// converted to `[("#first", "%try%")]`.
|
||||
#[inline]
|
||||
pub fn parse(input: &[u8]) -> Parse {
|
||||
Parse {
|
||||
input: input,
|
||||
encoding: EncodingOverride::utf8(),
|
||||
}
|
||||
Parse { input: input }
|
||||
}
|
||||
|
||||
|
||||
/// 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)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Parse<'a> {
|
||||
input: &'a [u8],
|
||||
encoding: EncodingOverride,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Parse<'a> {
|
||||
@@ -94,28 +41,25 @@ impl<'a> Iterator for Parse<'a> {
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
if self.input.is_empty() {
|
||||
return None
|
||||
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
|
||||
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),
|
||||
))
|
||||
return Some((decode(name), decode(value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode(input: &[u8], encoding: EncodingOverride) -> Cow<str> {
|
||||
fn decode(input: &[u8]) -> Cow<str> {
|
||||
let replaced = replace_plus(input);
|
||||
encoding.decode(match percent_decode(&replaced).if_any() {
|
||||
decode_utf8_lossy(match percent_decode(&replaced).if_any() {
|
||||
Some(vec) => Cow::Owned(vec),
|
||||
None => replaced,
|
||||
})
|
||||
@@ -146,16 +90,17 @@ impl<'a> Parse<'a> {
|
||||
}
|
||||
|
||||
/// Like `Parse`, but yields pairs of `String` instead of pairs of `Cow<str>`.
|
||||
#[derive(Debug)]
|
||||
pub struct ParseIntoOwned<'a> {
|
||||
inner: Parse<'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()))
|
||||
self.inner
|
||||
.next()
|
||||
.map(|(k, v)| (k.into_owned(), v.into_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,9 +109,7 @@ impl<'a> Iterator for ParseIntoOwned<'a> {
|
||||
///
|
||||
/// Return an iterator of `&str` slices.
|
||||
pub fn byte_serialize(input: &[u8]) -> ByteSerialize {
|
||||
ByteSerialize {
|
||||
bytes: input,
|
||||
}
|
||||
ByteSerialize { bytes: input }
|
||||
}
|
||||
|
||||
/// Return value of `byte_serialize()`.
|
||||
@@ -176,7 +119,7 @@ pub struct ByteSerialize<'a> {
|
||||
}
|
||||
|
||||
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')
|
||||
matches!(byte, b'*' | b'-' | b'.' | b'0' ..= b'9' | b'A' ..= b'Z' | b'_' | b'a' ..= b'z')
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ByteSerialize<'a> {
|
||||
@@ -186,7 +129,11 @@ impl<'a> Iterator for ByteSerialize<'a> {
|
||||
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) })
|
||||
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 {
|
||||
@@ -212,20 +159,10 @@ impl<'a> Iterator for ByteSerialize<'a> {
|
||||
|
||||
/// The [`application/x-www-form-urlencoded` serializer](
|
||||
/// https://url.spec.whatwg.org/#concept-urlencoded-serializer).
|
||||
#[derive(Debug)]
|
||||
pub struct Serializer<T: Target> {
|
||||
pub struct Serializer<'a, 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("…")
|
||||
}
|
||||
encoding: EncodingOverride<'a>,
|
||||
}
|
||||
|
||||
pub trait Target {
|
||||
@@ -235,14 +172,22 @@ pub trait Target {
|
||||
}
|
||||
|
||||
impl Target for String {
|
||||
fn as_mut_string(&mut self) -> &mut String { self }
|
||||
fn finish(self) -> Self { self }
|
||||
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 }
|
||||
fn as_mut_string(&mut self) -> &mut String {
|
||||
&mut **self
|
||||
}
|
||||
fn finish(self) -> Self {
|
||||
self
|
||||
}
|
||||
type Finished = Self;
|
||||
}
|
||||
|
||||
@@ -270,7 +215,7 @@ impl<'a> Target for ::UrlQuery<'a> {
|
||||
type Finished = &'a mut ::Url;
|
||||
}
|
||||
|
||||
impl<T: Target> Serializer<T> {
|
||||
impl<'a, T: Target> Serializer<'a, T> {
|
||||
/// Create a new `application/x-www-form-urlencoded` serializer for the given target.
|
||||
///
|
||||
/// If the target is non-empty,
|
||||
@@ -285,12 +230,11 @@ impl<T: Target> Serializer<T> {
|
||||
/// If that suffix is non-empty,
|
||||
/// its content is assumed to already be in `application/x-www-form-urlencoded` syntax.
|
||||
pub fn for_suffix(mut target: T, start_position: usize) -> Self {
|
||||
&target.as_mut_string()[start_position..]; // Panic if out of bounds
|
||||
&target.as_mut_string()[start_position..]; // Panic if out of bounds
|
||||
Serializer {
|
||||
target: Some(target),
|
||||
start_position: start_position,
|
||||
encoding: EncodingOverride::utf8(),
|
||||
custom_encoding: None,
|
||||
encoding: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,17 +247,8 @@ impl<T: Target> Serializer<T> {
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// 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)));
|
||||
pub fn encoding_override(&mut self, new: EncodingOverride<'a>) -> &mut Self {
|
||||
self.encoding = new;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -321,8 +256,13 @@ impl<T: Target> Serializer<T> {
|
||||
///
|
||||
/// 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,
|
||||
&mut self.custom_encoding, name, value);
|
||||
append_pair(
|
||||
string(&mut self.target),
|
||||
self.start_position,
|
||||
self.encoding,
|
||||
name,
|
||||
value,
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -334,36 +274,28 @@ impl<T: Target> Serializer<T> {
|
||||
///
|
||||
/// 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> {
|
||||
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,
|
||||
&mut self.custom_encoding, k.as_ref(), v.as_ref());
|
||||
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 {
|
||||
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);
|
||||
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
|
||||
@@ -377,7 +309,10 @@ impl<T: Target> Serializer<T> {
|
||||
///
|
||||
/// Panics if called more than once.
|
||||
pub fn finish(&mut self) -> T::Finished {
|
||||
self.target.take().expect("url::form_urlencoded::Serializer double finish").finish()
|
||||
self.target
|
||||
.take()
|
||||
.expect("url::form_urlencoded::Serializer double finish")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,24 +323,25 @@ fn append_separator_if_needed(string: &mut String, start_position: usize) {
|
||||
}
|
||||
|
||||
fn string<T: Target>(target: &mut Option<T>) -> &mut String {
|
||||
target.as_mut().expect("url::form_urlencoded::Serializer finished").as_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,
|
||||
custom_encoding: &mut Option<SilentDebug<Box<FnMut(&str) -> Cow<[u8]>>>>,
|
||||
name: &str, value: &str) {
|
||||
fn append_pair(
|
||||
string: &mut String,
|
||||
start_position: usize,
|
||||
encoding: EncodingOverride,
|
||||
name: &str,
|
||||
value: &str,
|
||||
) {
|
||||
append_separator_if_needed(string, start_position);
|
||||
append_encoded(name, string, encoding, custom_encoding);
|
||||
append_encoded(name, string, encoding);
|
||||
string.push('=');
|
||||
append_encoded(value, string, encoding, custom_encoding);
|
||||
append_encoded(value, string, 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));
|
||||
fn append_encoded(s: &str, string: &mut String, encoding: EncodingOverride) {
|
||||
string.extend(byte_serialize(&query_encoding::encode(encoding, s.into())))
|
||||
}
|
||||
|
||||
+118
-154
@@ -6,15 +6,12 @@
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
#[cfg(feature = "heapsize")] use heapsize::HeapSizeOf;
|
||||
use idna;
|
||||
use parser::{ParseError, ParseResult};
|
||||
use percent_encoding::{percent_decode, utf8_percent_encode, SIMPLE_ENCODE_SET};
|
||||
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, utf8_percent_encode, SIMPLE_ENCODE_SET};
|
||||
use idna;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum HostInternal {
|
||||
@@ -24,12 +21,12 @@ pub enum HostInternal {
|
||||
Ipv6(Ipv6Addr),
|
||||
}
|
||||
|
||||
#[cfg(feature = "heapsize")]
|
||||
known_heap_size!(0, HostInternal);
|
||||
|
||||
#[cfg(feature="serde")]
|
||||
#[cfg(feature = "serde")]
|
||||
impl ::serde::Serialize for HostInternal {
|
||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: ::serde::Serializer {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, 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.
|
||||
@@ -42,13 +39,17 @@ impl ::serde::Serialize for HostInternal {
|
||||
HostInternal::Domain => Some(None),
|
||||
HostInternal::Ipv4(addr) => Some(Some(IpAddr::V4(addr))),
|
||||
HostInternal::Ipv6(addr) => Some(Some(IpAddr::V6(addr))),
|
||||
}.serialize(serializer)
|
||||
}
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature="serde")]
|
||||
impl ::serde::Deserialize for HostInternal {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error> where D: ::serde::Deserializer {
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> ::serde::Deserialize<'de> for HostInternal {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: ::serde::Deserializer<'de>,
|
||||
{
|
||||
use std::net::IpAddr;
|
||||
Ok(match ::serde::Deserialize::deserialize(deserializer)? {
|
||||
None => HostInternal::None,
|
||||
@@ -71,7 +72,7 @@ impl<S> From<Host<S>> for HostInternal {
|
||||
|
||||
/// The host name of an URL.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum Host<S=String> {
|
||||
pub enum Host<S = String> {
|
||||
/// A DNS domain name, as '.' dot-separated labels.
|
||||
/// Non-ASCII labels are encoded in punycode per IDNA if this is the host of
|
||||
/// a special URL, or percent encoded for non-special URLs. Hosts for
|
||||
@@ -91,21 +92,28 @@ pub enum Host<S=String> {
|
||||
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 {
|
||||
#[cfg(feature = "serde")]
|
||||
impl<S: ::serde::Serialize> ::serde::Serialize for Host<S> {
|
||||
fn serialize<R>(&self, serializer: R) -> Result<R::Ok, 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)
|
||||
}
|
||||
.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 {
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de, S: ::serde::Deserialize<'de>> ::serde::Deserialize<'de> for Host<S> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: ::serde::Deserializer<'de>,
|
||||
{
|
||||
use std::net::IpAddr;
|
||||
Ok(match ::serde::Deserialize::deserialize(deserializer)? {
|
||||
Ok(s) => Host::Domain(s),
|
||||
@@ -115,16 +123,6 @@ impl<S: ::serde::Deserialize> ::serde::Deserialize for Host<S> {
|
||||
}
|
||||
}
|
||||
|
||||
#[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> {
|
||||
@@ -143,16 +141,34 @@ impl Host<String> {
|
||||
pub fn parse(input: &str) -> Result<Self, ParseError> {
|
||||
if input.starts_with('[') {
|
||||
if !input.ends_with(']') {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
return Err(ParseError::InvalidIpv6Address);
|
||||
}
|
||||
return parse_ipv6addr(&input[1..input.len() - 1]).map(Host::Ipv6)
|
||||
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 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))
|
||||
@@ -165,14 +181,31 @@ impl Host<String> {
|
||||
pub fn parse_opaque(input: &str) -> Result<Self, ParseError> {
|
||||
if input.starts_with('[') {
|
||||
if !input.ends_with(']') {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
return Err(ParseError::InvalidIpv6Address);
|
||||
}
|
||||
return parse_ipv6addr(&input[1..input.len() - 1]).map(Host::Ipv6)
|
||||
return parse_ipv6addr(&input[1..input.len() - 1]).map(Host::Ipv6);
|
||||
}
|
||||
if input.find(|c| matches!(c,
|
||||
'\0' | '\t' | '\n' | '\r' | ' ' | '#' | '/' | ':' | '?' | '@' | '[' | '\\' | ']'
|
||||
)).is_some() {
|
||||
return Err(ParseError::InvalidDomainCharacter)
|
||||
if input
|
||||
.find(|c| {
|
||||
matches!(
|
||||
c,
|
||||
'\0' | '\t'
|
||||
| '\n'
|
||||
| '\r'
|
||||
| ' '
|
||||
| '#'
|
||||
| '/'
|
||||
| ':'
|
||||
| '?'
|
||||
| '@'
|
||||
| '['
|
||||
| '\\'
|
||||
| ']'
|
||||
)
|
||||
})
|
||||
.is_some()
|
||||
{
|
||||
return Err(ParseError::InvalidDomainCharacter);
|
||||
}
|
||||
let s = utf8_percent_encode(input, SIMPLE_ENCODE_SET).to_string();
|
||||
Ok(Host::Domain(s))
|
||||
@@ -193,80 +226,6 @@ impl<S: AsRef<str>> fmt::Display for Host<S> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
@@ -344,10 +303,12 @@ fn parse_ipv4number(mut input: &str) -> Result<Option<u32>, ()> {
|
||||
// So instead we check if the input looks like a real number and only return
|
||||
// an error when it's an overflow.
|
||||
let valid_number = match r {
|
||||
8 => input.chars().all(|c| c >= '0' && c <='7'),
|
||||
10 => input.chars().all(|c| c >= '0' && c <='9'),
|
||||
16 => input.chars().all(|c| (c >= '0' && c <='9') || (c >='a' && c <= 'f') || (c >= 'A' && c <= 'F')),
|
||||
_ => false
|
||||
8 => input.chars().all(|c| c >= '0' && c <= '7'),
|
||||
10 => input.chars().all(|c| c >= '0' && c <= '9'),
|
||||
16 => input
|
||||
.chars()
|
||||
.all(|c| (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if !valid_number {
|
||||
@@ -369,7 +330,7 @@ fn parse_ipv4number(mut input: &str) -> Result<Option<u32>, ()> {
|
||||
/// <https://url.spec.whatwg.org/#concept-ipv4-parser>
|
||||
fn parse_ipv4addr(input: &str) -> ParseResult<Option<Ipv4Addr>> {
|
||||
if input.is_empty() {
|
||||
return Ok(None)
|
||||
return Ok(None);
|
||||
}
|
||||
let mut parts: Vec<&str> = input.split('.').collect();
|
||||
if parts.last() == Some(&"") {
|
||||
@@ -387,7 +348,7 @@ fn parse_ipv4addr(input: &str) -> ParseResult<Option<Ipv4Addr>> {
|
||||
match parse_ipv4number(part) {
|
||||
Ok(Some(n)) => numbers.push(n),
|
||||
Ok(None) => return Ok(None),
|
||||
Err(()) => overflow = true
|
||||
Err(()) => overflow = true,
|
||||
};
|
||||
}
|
||||
if overflow {
|
||||
@@ -395,7 +356,7 @@ fn parse_ipv4addr(input: &str) -> ParseResult<Option<Ipv4Addr>> {
|
||||
}
|
||||
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) {
|
||||
if ipv4 > u32::max_value() >> (8 * numbers.len() as u32) {
|
||||
return Err(ParseError::InvalidIpv4Address);
|
||||
}
|
||||
if numbers.iter().any(|x| *x > 255) {
|
||||
@@ -418,12 +379,12 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
|
||||
let mut i = 0;
|
||||
|
||||
if len < 2 {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
return Err(ParseError::InvalidIpv6Address);
|
||||
}
|
||||
|
||||
if input[0] == b':' {
|
||||
if input[1] != b':' {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
return Err(ParseError::InvalidIpv6Address);
|
||||
}
|
||||
i = 2;
|
||||
piece_pointer = 1;
|
||||
@@ -432,16 +393,16 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
|
||||
|
||||
while i < len {
|
||||
if piece_pointer == 8 {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
return Err(ParseError::InvalidIpv6Address);
|
||||
}
|
||||
if input[i] == b':' {
|
||||
if compress_pointer.is_some() {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
return Err(ParseError::InvalidIpv6Address);
|
||||
}
|
||||
i += 1;
|
||||
piece_pointer += 1;
|
||||
compress_pointer = Some(piece_pointer);
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
let start = i;
|
||||
let end = cmp::min(len, start + 4);
|
||||
@@ -451,33 +412,33 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
|
||||
Some(digit) => {
|
||||
value = value * 0x10 + digit as u16;
|
||||
i += 1;
|
||||
},
|
||||
None => break
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
if i < len {
|
||||
match input[i] {
|
||||
b'.' => {
|
||||
if i == start {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
return Err(ParseError::InvalidIpv6Address);
|
||||
}
|
||||
i = start;
|
||||
if piece_pointer > 6 {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
return Err(ParseError::InvalidIpv6Address);
|
||||
}
|
||||
is_ip_v4 = true;
|
||||
},
|
||||
}
|
||||
b':' => {
|
||||
i += 1;
|
||||
if i == len {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
return Err(ParseError::InvalidIpv6Address);
|
||||
}
|
||||
},
|
||||
_ => return Err(ParseError::InvalidIpv6Address)
|
||||
}
|
||||
_ => return Err(ParseError::InvalidIpv6Address),
|
||||
}
|
||||
}
|
||||
if is_ip_v4 {
|
||||
break
|
||||
break;
|
||||
}
|
||||
pieces[piece_pointer] = value;
|
||||
piece_pointer += 1;
|
||||
@@ -485,7 +446,7 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
|
||||
|
||||
if is_ip_v4 {
|
||||
if piece_pointer > 6 {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
return Err(ParseError::InvalidIpv6Address);
|
||||
}
|
||||
let mut numbers_seen = 0;
|
||||
while i < len {
|
||||
@@ -493,23 +454,23 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
|
||||
if numbers_seen < 4 && (i < len && input[i] == b'.') {
|
||||
i += 1
|
||||
} else {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
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
|
||||
c @ b'0'..=b'9' => c - b'0',
|
||||
_ => break,
|
||||
};
|
||||
match ipv4_piece {
|
||||
None => ipv4_piece = Some(digit as u16),
|
||||
Some(0) => return Err(ParseError::InvalidIpv6Address), // No leading zero
|
||||
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)
|
||||
return Err(ParseError::InvalidIpv6Address);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -519,7 +480,7 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
|
||||
pieces[piece_pointer] = if let Some(v) = ipv4_piece {
|
||||
pieces[piece_pointer] * 0x100 + v
|
||||
} else {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
return Err(ParseError::InvalidIpv6Address);
|
||||
};
|
||||
numbers_seen += 1;
|
||||
|
||||
@@ -529,12 +490,12 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
|
||||
}
|
||||
|
||||
if numbers_seen != 4 {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
return Err(ParseError::InvalidIpv6Address);
|
||||
}
|
||||
}
|
||||
|
||||
if i < len {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
return Err(ParseError::InvalidIpv6Address);
|
||||
}
|
||||
|
||||
match compress_pointer {
|
||||
@@ -547,10 +508,13 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
|
||||
piece_pointer -= 1;
|
||||
}
|
||||
}
|
||||
_ => if piece_pointer != 8 {
|
||||
return Err(ParseError::InvalidIpv6Address)
|
||||
_ => {
|
||||
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]))
|
||||
Ok(Ipv6Addr::new(
|
||||
pieces[0], pieces[1], pieces[2], pieces[3], pieces[4], pieces[5], pieces[6], pieces[7],
|
||||
))
|
||||
}
|
||||
|
||||
+289
-315
File diff suppressed because it is too large
Load Diff
+12
-29
@@ -6,11 +6,10 @@
|
||||
// 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 std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use Url;
|
||||
|
||||
pub fn url_origin(url: &Url) -> Origin {
|
||||
@@ -20,16 +19,17 @@ pub fn url_origin(url: &Url) -> Origin {
|
||||
let result = Url::parse(url.path());
|
||||
match result {
|
||||
Ok(ref url) => url_origin(url),
|
||||
Err(_) => Origin::new_opaque()
|
||||
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())
|
||||
},
|
||||
}
|
||||
"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()
|
||||
_ => Origin::new_opaque(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,27 +56,13 @@ pub enum Origin {
|
||||
Opaque(OpaqueOrigin),
|
||||
|
||||
/// Consists of the URL's scheme, host and port
|
||||
Tuple(String, Host<String>, u16)
|
||||
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;
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||
Origin::Opaque(OpaqueOrigin(COUNTER.fetch_add(1, Ordering::SeqCst)))
|
||||
}
|
||||
|
||||
@@ -110,7 +96,7 @@ impl Origin {
|
||||
let (domain, _errors) = domain_to_unicode(domain);
|
||||
Host::Domain(domain)
|
||||
}
|
||||
_ => host.clone()
|
||||
_ => host.clone(),
|
||||
};
|
||||
if default_port(scheme) == Some(port) {
|
||||
format!("{}://{}", scheme, host)
|
||||
@@ -125,6 +111,3 @@ impl Origin {
|
||||
/// 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);
|
||||
|
||||
+330
-257
File diff suppressed because it is too large
Load Diff
+21
-9
@@ -6,7 +6,7 @@
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use parser::{self, SchemeType, to_u32};
|
||||
use parser::{self, to_u32, SchemeType};
|
||||
use std::str;
|
||||
use Url;
|
||||
|
||||
@@ -56,7 +56,8 @@ pub fn new(url: &mut Url) -> PathSegmentsMut {
|
||||
|
||||
impl<'a> Drop for PathSegmentsMut<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.url.restore_after_path(self.old_after_path_position, &self.after_path)
|
||||
self.url
|
||||
.restore_after_path(self.old_after_path_position, &self.after_path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,8 +127,12 @@ impl<'a> PathSegmentsMut<'a> {
|
||||
///
|
||||
/// 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);
|
||||
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
|
||||
}
|
||||
|
||||
@@ -194,7 +199,10 @@ impl<'a> PathSegmentsMut<'a> {
|
||||
/// # run().unwrap();
|
||||
/// ```
|
||||
pub fn extend<I>(&mut self, segments: I) -> &mut Self
|
||||
where I: IntoIterator, I::Item: AsRef<str> {
|
||||
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| {
|
||||
@@ -202,14 +210,18 @@ impl<'a> PathSegmentsMut<'a> {
|
||||
for segment in segments {
|
||||
let segment = segment.as_ref();
|
||||
if matches!(segment, "." | "..") {
|
||||
continue
|
||||
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));
|
||||
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,35 @@
|
||||
// Copyright 2019 The rust-url developers.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub type EncodingOverride<'a> = Option<&'a dyn Fn(&str) -> Cow<[u8]>>;
|
||||
|
||||
pub(crate) fn encode<'a>(encoding_override: EncodingOverride, input: &'a str) -> Cow<'a, [u8]> {
|
||||
if let Some(o) = encoding_override {
|
||||
return o(input);
|
||||
}
|
||||
input.as_bytes().into()
|
||||
}
|
||||
|
||||
pub(crate) fn decode_utf8_lossy(input: Cow<[u8]>) -> Cow<str> {
|
||||
match input {
|
||||
Cow::Borrowed(bytes) => String::from_utf8_lossy(bytes),
|
||||
Cow::Owned(bytes) => {
|
||||
let raw_utf8: *const [u8];
|
||||
match String::from_utf8_lossy(&bytes) {
|
||||
Cow::Borrowed(utf8) => raw_utf8 = utf8.as_bytes(),
|
||||
Cow::Owned(s) => return s.into(),
|
||||
}
|
||||
// from_utf8_lossy returned a borrow of `bytes` unchanged.
|
||||
debug_assert!(raw_utf8 == &*bytes as *const [u8]);
|
||||
// Reuse the existing `Vec` allocation.
|
||||
unsafe { String::from_utf8_unchecked(bytes) }.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
-10
@@ -11,8 +11,8 @@
|
||||
//! 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};
|
||||
use parser::{default_port, Context, Input, Parser, SchemeType};
|
||||
use {idna, Host, ParseError, Position, Url};
|
||||
|
||||
/// https://url.spec.whatwg.org/#dom-url-domaintoascii
|
||||
pub fn domain_to_ascii(domain: &str) -> String {
|
||||
@@ -84,7 +84,11 @@ pub fn password(url: &Url) -> &str {
|
||||
|
||||
/// 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) })
|
||||
url.set_password(if new_password.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(new_password)
|
||||
})
|
||||
}
|
||||
|
||||
/// Getter for https://url.spec.whatwg.org/#dom-url-host
|
||||
@@ -96,7 +100,7 @@ pub fn host(url: &Url) -> &str {
|
||||
/// 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(())
|
||||
return Err(());
|
||||
}
|
||||
let host;
|
||||
let opt_port;
|
||||
@@ -108,12 +112,13 @@ pub fn set_host(url: &mut Url, new_host: &str) -> Result<(), ()> {
|
||||
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)
|
||||
.ok()
|
||||
.map(|(port, _remaining)| port)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
Err(_) => return Err(())
|
||||
Err(_) => return Err(()),
|
||||
}
|
||||
}
|
||||
url.set_host_internal(host, opt_port);
|
||||
@@ -129,7 +134,7 @@ pub fn hostname(url: &Url) -> &str {
|
||||
/// 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(())
|
||||
return Err(());
|
||||
}
|
||||
let result = Parser::parse_host(Input::new(new_hostname), SchemeType::from(url.scheme()));
|
||||
if let Ok((host, _remaining)) = result {
|
||||
@@ -153,9 +158,13 @@ pub fn set_port(url: &mut Url, new_port: &str) -> Result<(), ()> {
|
||||
// has_host implies !cannot_be_a_base
|
||||
let scheme = url.scheme();
|
||||
if !url.has_host() || url.host() == Some(Host::Domain("")) || scheme == "file" {
|
||||
return Err(())
|
||||
return Err(());
|
||||
}
|
||||
result = Parser::parse_port(Input::new(new_port), || default_port(scheme), Context::Setter)
|
||||
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);
|
||||
@@ -168,7 +177,7 @@ pub fn set_port(url: &mut Url, new_port: &str) -> Result<(), ()> {
|
||||
/// Getter for https://url.spec.whatwg.org/#dom-url-pathname
|
||||
#[inline]
|
||||
pub fn pathname(url: &Url) -> &str {
|
||||
url.path()
|
||||
url.path()
|
||||
}
|
||||
|
||||
/// Setter for https://url.spec.whatwg.org/#dom-url-pathname
|
||||
|
||||
+36
-31
@@ -6,7 +6,7 @@
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use std::ops::{Range, RangeFrom, RangeTo, RangeFull, Index};
|
||||
use std::ops::{Index, Range, RangeFrom, RangeFull, RangeTo};
|
||||
use Url;
|
||||
|
||||
impl Index<RangeFull> for Url {
|
||||
@@ -94,7 +94,7 @@ pub enum Position {
|
||||
BeforeQuery,
|
||||
AfterQuery,
|
||||
BeforeFragment,
|
||||
AfterFragment
|
||||
AfterFragment,
|
||||
}
|
||||
|
||||
impl Url {
|
||||
@@ -105,43 +105,49 @@ impl Url {
|
||||
|
||||
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::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::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::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::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,
|
||||
|
||||
@@ -179,4 +185,3 @@ impl Url {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+47
-37
@@ -8,29 +8,29 @@
|
||||
|
||||
//! Data-driven tests
|
||||
|
||||
extern crate rustc_serialize;
|
||||
extern crate rustc_test as test;
|
||||
extern crate serde_json;
|
||||
extern crate url;
|
||||
|
||||
use rustc_serialize::json::{self, Json};
|
||||
use url::{Url, quirks};
|
||||
use serde_json::Value;
|
||||
use std::str::FromStr;
|
||||
use url::{quirks, Url};
|
||||
|
||||
fn check_invariants(url: &Url) {
|
||||
url.check_invariants().unwrap();
|
||||
#[cfg(feature="serde")] {
|
||||
extern crate serde_json;
|
||||
#[cfg(feature = "serde")]
|
||||
{
|
||||
let bytes = serde_json::to_vec(url).unwrap();
|
||||
let new_url: Url = serde_json::from_slice(&bytes).unwrap();
|
||||
assert_eq!(url, &new_url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn run_parsing(input: &str, base: &str, expected: Result<ExpectedAttributes, ()>) {
|
||||
let base = match Url::parse(&base) {
|
||||
Ok(base) => base,
|
||||
Err(_) if expected.is_err() => return,
|
||||
Err(message) => panic!("Error parsing base {:?}: {}", base, message)
|
||||
Err(message) => panic!("Error parsing base {:?}: {}", base, message),
|
||||
};
|
||||
let (url, expected) = match (base.join(&input), expected) {
|
||||
(Ok(url), Ok(expected)) => (url, expected),
|
||||
@@ -42,14 +42,18 @@ fn run_parsing(input: &str, base: &str, expected: Result<ExpectedAttributes, ()>
|
||||
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);
|
||||
}
|
||||
}
|
||||
($expected: expr, $got: expr) => {{
|
||||
let expected = $expected;
|
||||
let got = $got;
|
||||
assert!(
|
||||
expected == got,
|
||||
"{:?} != {} {:?} for URL {:?}",
|
||||
got,
|
||||
stringify!($expected),
|
||||
expected,
|
||||
url
|
||||
);
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! assert_attributes {
|
||||
@@ -84,46 +88,45 @@ struct ExpectedAttributes {
|
||||
}
|
||||
|
||||
trait JsonExt {
|
||||
fn take(&mut self, key: &str) -> Option<Json>;
|
||||
fn object(self) -> json::Object;
|
||||
fn take_key(&mut self, key: &str) -> Option<Value>;
|
||||
fn string(self) -> String;
|
||||
fn take_string(&mut self, key: &str) -> String;
|
||||
}
|
||||
|
||||
impl JsonExt for Json {
|
||||
fn take(&mut self, key: &str) -> Option<Json> {
|
||||
impl JsonExt for Value {
|
||||
fn take_key(&mut self, key: &str) -> Option<Value> {
|
||||
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") }
|
||||
if let Value::String(s) = self {
|
||||
s
|
||||
} else {
|
||||
panic!("Not a Value::String")
|
||||
}
|
||||
}
|
||||
|
||||
fn take_string(&mut self, key: &str) -> String {
|
||||
self.take(key).unwrap().string()
|
||||
self.take_key(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"))
|
||||
let mut json = Value::from_str(include_str!("urltestdata.json"))
|
||||
.expect("JSON parse error in urltestdata.json");
|
||||
for entry in json.as_array_mut().unwrap() {
|
||||
if entry.is_string() {
|
||||
continue // ignore comments
|
||||
continue; // ignore comments
|
||||
}
|
||||
let base = entry.take_string("base");
|
||||
let input = entry.take_string("input");
|
||||
let expected = if entry.find("failure").is_some() {
|
||||
let expected = if entry.take_key("failure").is_some() {
|
||||
Err(())
|
||||
} else {
|
||||
Ok(ExpectedAttributes {
|
||||
href: entry.take_string("href"),
|
||||
origin: entry.take("origin").map(Json::string),
|
||||
origin: entry.take_key("origin").map(|s| s.string()),
|
||||
protocol: entry.take_string("protocol"),
|
||||
username: entry.take_string("username"),
|
||||
password: entry.take_string("password"),
|
||||
@@ -135,24 +138,31 @@ fn collect_parsing<F: FnMut(String, test::TestFn)>(add_test: &mut F) {
|
||||
hash: entry.take_string("hash"),
|
||||
})
|
||||
};
|
||||
add_test(format!("{:?} @ base {:?}", input, base),
|
||||
test::TestFn::dyn_test_fn(move || run_parsing(&input, &base, expected)));
|
||||
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"))
|
||||
fn collect_setters<F>(add_test: &mut F)
|
||||
where
|
||||
F: FnMut(String, test::TestFn),
|
||||
{
|
||||
let mut json = Value::from_str(include_str!("setters_tests.json"))
|
||||
.expect("JSON parse error in setters_tests.json");
|
||||
|
||||
macro_rules! setter {
|
||||
($attr: expr, $setter: ident) => {{
|
||||
let mut tests = json.take($attr).unwrap();
|
||||
let mut tests = json.take_key($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 comment = test.take_key("comment")
|
||||
.map(|s| s.string())
|
||||
.unwrap_or(String::new());
|
||||
let href = test.take_string("href");
|
||||
let new_value = test.take_string("new_value");
|
||||
let name = format!("{:?}.{} = {:?} {}", href, $attr, new_value, comment);
|
||||
let mut expected = test.take("expected").unwrap();
|
||||
let mut expected = test.take_key("expected").unwrap();
|
||||
add_test(name, test::TestFn::dyn_test_fn(move || {
|
||||
let mut url = Url::parse(&href).unwrap();
|
||||
check_invariants(&url);
|
||||
@@ -167,7 +177,7 @@ fn collect_setters<F>(add_test: &mut F) where F: FnMut(String, test::TestFn) {
|
||||
macro_rules! assert_attributes {
|
||||
($url: expr, $expected: expr, $($attr: ident)+) => {
|
||||
$(
|
||||
if let Some(value) = $expected.take(stringify!($attr)) {
|
||||
if let Some(value) = $expected.take_key(stringify!($attr)) {
|
||||
assert_eq!(quirks::$attr(&$url), value.string())
|
||||
}
|
||||
)+
|
||||
|
||||
+127
-96
@@ -8,15 +8,15 @@
|
||||
|
||||
//! Unit tests
|
||||
|
||||
#[macro_use]
|
||||
extern crate url;
|
||||
#[macro_use]
|
||||
extern crate percent_encoding;
|
||||
|
||||
use std::ascii::AsciiExt;
|
||||
use std::borrow::Cow;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::path::{Path, PathBuf};
|
||||
use url::{Host, HostAndPort, Url, form_urlencoded};
|
||||
use url::{form_urlencoded, Host, Url};
|
||||
|
||||
#[test]
|
||||
fn size() {
|
||||
@@ -25,7 +25,9 @@ fn size() {
|
||||
}
|
||||
|
||||
macro_rules! assert_from_file_path {
|
||||
($path: expr) => { assert_from_file_path!($path, $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);
|
||||
@@ -34,8 +36,6 @@ macro_rules! assert_from_file_path {
|
||||
}};
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[test]
|
||||
fn new_file_paths() {
|
||||
if cfg!(unix) {
|
||||
@@ -74,7 +74,10 @@ fn new_path_windows_fun() {
|
||||
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());
|
||||
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");
|
||||
@@ -86,7 +89,6 @@ fn new_path_windows_fun() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn new_directory_paths() {
|
||||
if cfg!(unix) {
|
||||
@@ -100,7 +102,10 @@ fn new_directory_paths() {
|
||||
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"\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();
|
||||
@@ -127,10 +132,16 @@ fn from_str() {
|
||||
|
||||
#[test]
|
||||
fn parse_with_params() {
|
||||
let url = Url::parse_with_params("http://testing.com/this?dont=clobberme",
|
||||
&[("lang", "rust")]).unwrap();
|
||||
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");
|
||||
assert_eq!(
|
||||
url.as_str(),
|
||||
"http://testing.com/this?dont=clobberme&lang=rust"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -145,8 +156,8 @@ fn issue_124() {
|
||||
|
||||
#[test]
|
||||
fn test_equality() {
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
fn check_eq(a: &Url, b: &Url) {
|
||||
assert_eq!(a, b);
|
||||
@@ -196,13 +207,29 @@ fn host() {
|
||||
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::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://[::]",
|
||||
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"));
|
||||
@@ -217,15 +244,26 @@ fn host_serialization() {
|
||||
// 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]"));
|
||||
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());
|
||||
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]
|
||||
@@ -236,9 +274,18 @@ fn test_serialization() {
|
||||
("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")
|
||||
(
|
||||
"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();
|
||||
@@ -251,11 +298,16 @@ fn test_form_urlencoded() {
|
||||
let pairs: &[(Cow<str>, Cow<str>)] = &[
|
||||
("foo".into(), "é&".into()),
|
||||
("bar".into(), "".into()),
|
||||
("foo".into(), "#".into())
|
||||
("foo".into(), "#".into()),
|
||||
];
|
||||
let encoded = form_urlencoded::Serializer::new(String::new()).extend_pairs(pairs).finish();
|
||||
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());
|
||||
assert_eq!(
|
||||
form_urlencoded::parse(encoded.as_bytes()).collect::<Vec<_>>(),
|
||||
pairs.to_vec()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -269,44 +321,14 @@ fn test_form_serialize() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn form_urlencoded_custom_encoding_override() {
|
||||
fn form_urlencoded_encoding_override() {
|
||||
let encoded = form_urlencoded::Serializer::new(String::new())
|
||||
.custom_encoding_override(|s| s.as_bytes().to_ascii_uppercase().into())
|
||||
.encoding_override(Some(&|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!(
|
||||
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/61
|
||||
fn issue_61() {
|
||||
@@ -323,8 +345,13 @@ fn issue_61() {
|
||||
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();
|
||||
assert_eq!(
|
||||
url,
|
||||
Url::parse("file:///").expect("Failed to parse path + protocol")
|
||||
);
|
||||
url.path_segments_mut()
|
||||
.expect("path_segments_mut")
|
||||
.pop_if_empty();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -346,12 +373,19 @@ fn append_trailing_slash() {
|
||||
/// 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.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");
|
||||
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");
|
||||
assert_eq!(
|
||||
url.to_string(),
|
||||
"http://localhost:6767/foo/bar/some_other_path?auth=my-token"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -388,7 +422,10 @@ fn test_set_host() {
|
||||
#[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!(
|
||||
Host::parse(".org").unwrap(),
|
||||
Host::Domain(".org".to_owned())
|
||||
);
|
||||
assert_eq!(Url::parse("file://./foo").unwrap().domain(), Some("."));
|
||||
}
|
||||
|
||||
@@ -396,17 +433,20 @@ fn test_leading_dots() {
|
||||
// inside both a module and a function
|
||||
#[test]
|
||||
fn define_encode_set_scopes() {
|
||||
use url::percent_encoding::{utf8_percent_encode, SIMPLE_ENCODE_SET};
|
||||
use 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");
|
||||
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};
|
||||
use percent_encoding::{utf8_percent_encode, SIMPLE_ENCODE_SET};
|
||||
|
||||
define_encode_set! {
|
||||
/// This encode set is used in the URL parser for query strings.
|
||||
@@ -414,7 +454,10 @@ fn define_encode_set_scopes() {
|
||||
}
|
||||
|
||||
pub fn test() {
|
||||
assert_eq!(utf8_percent_encode("foo bar", QUERY_ENCODE_SET).collect::<String>(), "foo%20bar");
|
||||
assert_eq!(
|
||||
utf8_percent_encode("foo bar", QUERY_ENCODE_SET).collect::<String>(),
|
||||
"foo%20bar"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,8 +467,8 @@ fn define_encode_set_scopes() {
|
||||
#[test]
|
||||
/// https://github.com/servo/rust-url/issues/302
|
||||
fn test_origin_hash() {
|
||||
use std::hash::{Hash,Hasher};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
fn hash<T: Hash>(value: &T) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
@@ -444,7 +487,9 @@ fn test_origin_hash() {
|
||||
Url::parse("ftp://example.net").unwrap().origin(),
|
||||
Url::parse("file://example.net").unwrap().origin(),
|
||||
Url::parse("http://user@example.net/").unwrap().origin(),
|
||||
Url::parse("http://user:pass@example.net/").unwrap().origin(),
|
||||
Url::parse("http://user:pass@example.net/")
|
||||
.unwrap()
|
||||
.origin(),
|
||||
];
|
||||
|
||||
for origin_to_compare in &origins_to_compare {
|
||||
@@ -466,7 +511,7 @@ fn test_origin_hash() {
|
||||
#[test]
|
||||
fn test_windows_unc_path() {
|
||||
if !cfg!(windows) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
let url = Url::from_file_path(Path::new(r"\\host\share\path\file.txt")).unwrap();
|
||||
@@ -490,28 +535,14 @@ fn test_windows_unc_path() {
|
||||
assert!(url.is_err());
|
||||
}
|
||||
|
||||
// Test the now deprecated log_syntax_violation method for backward
|
||||
// compatibility
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn test_old_log_violation_option() {
|
||||
let violation = Cell::new(None);
|
||||
let url = Url::options()
|
||||
.log_syntax_violation(Some(&|s| violation.set(Some(s.to_owned()))))
|
||||
.parse("http:////mozilla.org:42").unwrap();
|
||||
assert_eq!(url.port(), Some(42));
|
||||
|
||||
let violation = violation.take();
|
||||
assert_eq!(violation, Some("expected //".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_syntax_violation_callback() {
|
||||
use url::SyntaxViolation::*;
|
||||
let violation = Cell::new(None);
|
||||
let url = Url::options()
|
||||
.syntax_violation_callback(Some(&|v| violation.set(Some(v))))
|
||||
.parse("http:////mozilla.org:42").unwrap();
|
||||
.parse("http:////mozilla.org:42")
|
||||
.unwrap();
|
||||
assert_eq!(url.port(), Some(42));
|
||||
|
||||
let v = violation.take().unwrap();
|
||||
@@ -527,13 +558,15 @@ fn test_syntax_violation_callback_lifetimes() {
|
||||
|
||||
let url = Url::options()
|
||||
.syntax_violation_callback(Some(&vfn))
|
||||
.parse("http:////mozilla.org:42").unwrap();
|
||||
.parse("http:////mozilla.org:42")
|
||||
.unwrap();
|
||||
assert_eq!(url.port(), Some(42));
|
||||
assert_eq!(violation.take(), Some(ExpectedDoubleSlash));
|
||||
|
||||
let url = Url::options()
|
||||
.syntax_violation_callback(Some(&vfn))
|
||||
.parse("http://mozilla.org\\path").unwrap();
|
||||
.parse("http://mozilla.org\\path")
|
||||
.unwrap();
|
||||
assert_eq!(url.path(), "/path");
|
||||
assert_eq!(violation.take(), Some(Backslash));
|
||||
}
|
||||
@@ -544,13 +577,11 @@ fn test_options_reuse() {
|
||||
let violations = RefCell::new(Vec::new());
|
||||
let vfn = |v| violations.borrow_mut().push(v);
|
||||
|
||||
let options = Url::options()
|
||||
.syntax_violation_callback(Some(&vfn));
|
||||
let options = Url::options().syntax_violation_callback(Some(&vfn));
|
||||
let url = options.parse("http:////mozilla.org").unwrap();
|
||||
|
||||
let options = options.base_url(Some(&url));
|
||||
let url = options.parse("/sub\\path").unwrap();
|
||||
assert_eq!(url.as_str(), "http://mozilla.org/sub/path");
|
||||
assert_eq!(*violations.borrow(),
|
||||
vec!(ExpectedDoubleSlash, Backslash));
|
||||
assert_eq!(*violations.borrow(), vec!(ExpectedDoubleSlash, Backslash));
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
[package]
|
||||
|
||||
name = "url_serde"
|
||||
version = "0.2.0"
|
||||
authors = ["The rust-url developers"]
|
||||
|
||||
description = "Serde support for URL types"
|
||||
documentation = "https://docs.rs/url_serde/"
|
||||
repository = "https://github.com/servo/rust-url"
|
||||
readme = "README.md"
|
||||
keywords = ["url", "serde"]
|
||||
license = "MIT/Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
serde = "1.0"
|
||||
url = {version = "1.0.0", path = ".."}
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0"
|
||||
serde_derive = "1.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
@@ -1 +0,0 @@
|
||||
../LICENSE-APACHE
|
||||
@@ -1 +0,0 @@
|
||||
../LICENSE-MIT
|
||||
@@ -1,11 +0,0 @@
|
||||
Serde support for rust-url types
|
||||
================================
|
||||
|
||||
This crate provides wrappers and convenience functions to make `rust-url` and `serde`
|
||||
work hand in hand.
|
||||
|
||||
Version `0.2` or newer of this crate offer support for `serde 1.0`.
|
||||
Version `0.1` of this crate offer support for `serde 0.9`.
|
||||
Versions of `serde` older than `0.9` are natively supported by `rust-url` crate directly.
|
||||
|
||||
For more details, see the crate [documentation](https://docs.rs/url_serde/).
|
||||
@@ -1,421 +0,0 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
/*!
|
||||
|
||||
This crate provides wrappers and convenience functions to make rust-url
|
||||
and Serde work hand in hand.
|
||||
|
||||
The supported types are:
|
||||
|
||||
* `url::Url`
|
||||
|
||||
# How do I use a data type with a `Url` member with Serde?
|
||||
|
||||
Use the serde attributes `deserialize_with` and `serialize_with`.
|
||||
|
||||
```
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
struct MyStruct {
|
||||
#[serde(serialize_with = "serialize")]
|
||||
url: Url,
|
||||
}
|
||||
```
|
||||
|
||||
# How do I use a data type with an unnamed `Url` member with serde?
|
||||
|
||||
Same problem, same solution.
|
||||
|
||||
```
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
enum MyEnum {
|
||||
A(#[serde(with = "url_serde")] Url, OtherType),
|
||||
}
|
||||
```
|
||||
|
||||
# How do I encode a `Url` value with `serde_json::to_string`?
|
||||
|
||||
Use the `Ser` wrapper.
|
||||
|
||||
```
|
||||
serde_json::to_string(&Ser::new(&url))
|
||||
```
|
||||
|
||||
# How do I decode a `Url` value with `serde_json::parse`?
|
||||
|
||||
Use the `De` wrapper.
|
||||
|
||||
```
|
||||
serde_json::from_str(r"http:://www.rust-lang.org").map(De::into_inner)
|
||||
```
|
||||
|
||||
# How do I send `Url` values as part of an IPC channel?
|
||||
|
||||
Use the `Serde` wrapper. It implements `Deref` and `DerefMut` for convenience.
|
||||
|
||||
```
|
||||
ipc::channel::<Serde<Url>>()
|
||||
```
|
||||
*/
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
extern crate serde;
|
||||
#[cfg(test)] #[macro_use] extern crate serde_derive;
|
||||
#[cfg(test)] extern crate serde_json;
|
||||
extern crate url;
|
||||
|
||||
use serde::{Deserialize, Serialize, Serializer, Deserializer};
|
||||
use std::cmp::PartialEq;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::io::Write;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::str;
|
||||
use url::{Url, Host};
|
||||
|
||||
/// Serialises `value` with a given serializer.
|
||||
///
|
||||
/// This is useful to serialize `rust-url` types used in structure fields or
|
||||
/// tuple members with `#[serde(serialize_with = "url_serde::serialize")]`.
|
||||
pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer, for<'a> Ser<'a, T>: Serialize
|
||||
{
|
||||
Ser::new(value).serialize(serializer)
|
||||
}
|
||||
|
||||
/// A wrapper to serialize `rust-url` types.
|
||||
///
|
||||
/// This is useful with functions such as `serde_json::to_string`.
|
||||
///
|
||||
/// Values of this type can only be passed to the `serde::Serialize` trait.
|
||||
#[derive(Debug)]
|
||||
pub struct Ser<'a, T: 'a>(&'a T);
|
||||
|
||||
impl<'a, T> Ser<'a, T> where Ser<'a, T>: Serialize {
|
||||
/// Returns a new `Ser` wrapper.
|
||||
#[inline(always)]
|
||||
pub fn new(value: &'a T) -> Self {
|
||||
Ser(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes this URL into a `serde` stream.
|
||||
impl<'a> Serialize for Ser<'a, Url> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
|
||||
serializer.serialize_str(self.0.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes this Option<URL> into a `serde` stream.
|
||||
impl<'a> Serialize for Ser<'a, Option<Url>> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
|
||||
if let Some(url) = self.0.as_ref() {
|
||||
serializer.serialize_some(url.as_str())
|
||||
} else {
|
||||
serializer.serialize_none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, String> Serialize for Ser<'a, Host<String>> where String: AsRef<str> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
|
||||
match *self.0 {
|
||||
Host::Domain(ref s) => serializer.serialize_str(s.as_ref()),
|
||||
Host::Ipv4(_) | Host::Ipv6(_) => {
|
||||
// max("101.102.103.104".len(),
|
||||
// "[1000:1002:1003:1004:1005:1006:101.102.103.104]".len())
|
||||
const MAX_LEN: usize = 47;
|
||||
let mut buffer = [0; MAX_LEN];
|
||||
serializer.serialize_str(display_into_buffer(&self.0, &mut buffer))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Like .to_string(), but doesn’t allocate memory for a `String`.
|
||||
///
|
||||
/// Panics if `buffer` is too small.
|
||||
fn display_into_buffer<'a, T: fmt::Display>(value: &T, buffer: &'a mut [u8]) -> &'a str {
|
||||
let remaining_len;
|
||||
{
|
||||
let mut remaining = &mut *buffer;
|
||||
write!(remaining, "{}", value).unwrap();
|
||||
remaining_len = remaining.len()
|
||||
}
|
||||
let written_len = buffer.len() - remaining_len;
|
||||
let written = &buffer[..written_len];
|
||||
|
||||
// write! only provides std::fmt::Formatter to Display implementations,
|
||||
// which has methods write_str and write_char but no method to write arbitrary bytes.
|
||||
// Therefore, `written` is well-formed in UTF-8.
|
||||
#[allow(unsafe_code)]
|
||||
unsafe {
|
||||
str::from_utf8_unchecked(written)
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialises a `T` value with a given deserializer.
|
||||
///
|
||||
/// This is useful to deserialize Url types used in structure fields or
|
||||
/// tuple members with `#[serde(deserialize_with = "url_serde::deserialize")]`.
|
||||
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
|
||||
where D: Deserializer<'de>, De<T>: Deserialize<'de>
|
||||
{
|
||||
De::deserialize(deserializer).map(De::into_inner)
|
||||
}
|
||||
|
||||
/// A wrapper to deserialize `rust-url` types.
|
||||
///
|
||||
/// This is useful with functions such as `serde_json::from_str`.
|
||||
///
|
||||
/// Values of this type can only be obtained through
|
||||
/// the `serde::Deserialize` trait.
|
||||
#[derive(Debug)]
|
||||
pub struct De<T>(T);
|
||||
|
||||
impl<'de, T> De<T> where De<T>: serde::Deserialize<'de> {
|
||||
/// Consumes this wrapper, returning the deserialized value.
|
||||
#[inline(always)]
|
||||
pub fn into_inner(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserializes this URL from a `serde` stream.
|
||||
impl<'de> Deserialize<'de> for De<Url> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
|
||||
let string_representation: String = Deserialize::deserialize(deserializer)?;
|
||||
Url::parse(&string_representation).map(De).map_err(|err| {
|
||||
serde::de::Error::custom(err.description())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserializes this Option<URL> from a `serde` stream.
|
||||
impl<'de> Deserialize<'de> for De<Option<Url>> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
|
||||
let option_representation: Option<String> = Deserialize::deserialize(deserializer)?;
|
||||
if let Some(s) = option_representation {
|
||||
return Url::parse(&s)
|
||||
.map(Some)
|
||||
.map(De)
|
||||
.map_err(|err| {serde::de::Error::custom(err.description())});
|
||||
}
|
||||
Ok(De(None))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for De<Host> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
|
||||
let string_representation: String = Deserialize::deserialize(deserializer)?;
|
||||
Host::parse(&string_representation).map(De).map_err(|err| {
|
||||
serde::de::Error::custom(err.description())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A convenience wrapper to be used as a type parameter, for example when
|
||||
/// a `Vec<T>` or an `HashMap<K, V>` need to be passed to serde.
|
||||
#[derive(Clone, Eq, Hash, PartialEq)]
|
||||
pub struct Serde<T>(pub T);
|
||||
|
||||
/// A convenience type alias for Serde<Url>.
|
||||
pub type SerdeUrl = Serde<Url>;
|
||||
|
||||
impl<'de, T> Serde<T>
|
||||
where De<T>: Deserialize<'de>, for<'a> Ser<'a, T>: Serialize
|
||||
{
|
||||
/// Consumes this wrapper, returning the inner value.
|
||||
#[inline(always)]
|
||||
pub fn into_inner(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T> fmt::Debug for Serde<T>
|
||||
where T: fmt::Debug, De<T>: Deserialize<'de>, for<'a> Ser<'a, T>: Serialize
|
||||
{
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
self.0.fmt(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T> Deref for Serde<T>
|
||||
where De<T>: Deserialize<'de>, for<'a> Ser<'a, T>: Serialize
|
||||
{
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T> DerefMut for Serde<T>
|
||||
where De<T>: Deserialize<'de>, for<'a> Ser<'a, T>: Serialize
|
||||
{
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T: PartialEq> PartialEq<T> for Serde<T>
|
||||
where De<T>: Deserialize<'de>, for<'a> Ser<'a, T>: Serialize
|
||||
{
|
||||
fn eq(&self, other: &T) -> bool {
|
||||
self.0 == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T> Deserialize<'de> for Serde<T>
|
||||
where De<T>: Deserialize<'de>, for<'a> Ser<'a, T>: Serialize
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: Deserializer<'de>
|
||||
{
|
||||
De::deserialize(deserializer).map(De::into_inner).map(Serde)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T> Serialize for Serde<T>
|
||||
where De<T>: Deserialize<'de>, for<'a> Ser<'a, T>: Serialize
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer
|
||||
{
|
||||
Ser(&self.0).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ser_de_url() {
|
||||
let url = Url::parse("http://www.test.com/foo/bar?$param=bazz").unwrap();
|
||||
let s = serde_json::to_string(&Ser::new(&url)).unwrap();
|
||||
let new_url: Url = serde_json::from_str(&s).map(De::into_inner).unwrap();
|
||||
assert_eq!(url, new_url);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_derive_deserialize_with_for_url() {
|
||||
#[derive(Deserialize, Debug, Eq, PartialEq)]
|
||||
struct Test {
|
||||
#[serde(deserialize_with = "deserialize", rename = "_url_")]
|
||||
url: Url
|
||||
}
|
||||
|
||||
let url_str = "http://www.test.com/foo/bar?$param=bazz";
|
||||
|
||||
let expected = Test {
|
||||
url: Url::parse(url_str).unwrap()
|
||||
};
|
||||
let json_string = format!(r#"{{"_url_": "{}"}}"#, url_str);
|
||||
let got: Test = serde_json::from_str(&json_string).unwrap();
|
||||
assert_eq!(expected, got);
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_derive_deserialize_with_for_option_url() {
|
||||
#[derive(Deserialize, Debug, Eq, PartialEq)]
|
||||
struct Test {
|
||||
#[serde(deserialize_with = "deserialize", rename = "_url_")]
|
||||
url: Option<Url>
|
||||
}
|
||||
|
||||
let url_str = "http://www.test.com/foo/bar?$param=bazz";
|
||||
|
||||
let expected = Test {
|
||||
url: Some(Url::parse(url_str).unwrap())
|
||||
};
|
||||
let json_string = format!(r#"{{"_url_": "{}"}}"#, url_str);
|
||||
let got: Test = serde_json::from_str(&json_string).unwrap();
|
||||
assert_eq!(expected, got);
|
||||
|
||||
let expected = Test {
|
||||
url: None
|
||||
};
|
||||
let json_string = r#"{"_url_": null}"#;
|
||||
let got: Test = serde_json::from_str(&json_string).unwrap();
|
||||
assert_eq!(expected, got);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_derive_serialize_with_for_url() {
|
||||
#[derive(Serialize, Debug, Eq, PartialEq)]
|
||||
struct Test {
|
||||
#[serde(serialize_with = "serialize", rename = "_url_")]
|
||||
url: Url
|
||||
}
|
||||
|
||||
let url_str = "http://www.test.com/foo/bar?$param=bazz";
|
||||
|
||||
let expected = format!(r#"{{"_url_":"{}"}}"#, url_str);
|
||||
let input = Test {url: Url::parse(url_str).unwrap()};
|
||||
let got = serde_json::to_string(&input).unwrap();
|
||||
assert_eq!(expected, got);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_derive_serialize_with_for_option_url() {
|
||||
#[derive(Serialize, Debug, Eq, PartialEq)]
|
||||
struct Test {
|
||||
#[serde(serialize_with = "serialize", rename = "_url_")]
|
||||
url: Option<Url>
|
||||
}
|
||||
|
||||
let url_str = "http://www.test.com/foo/bar?$param=bazz";
|
||||
|
||||
let expected = format!(r#"{{"_url_":"{}"}}"#, url_str);
|
||||
let input = Test {url: Some(Url::parse(url_str).unwrap())};
|
||||
let got = serde_json::to_string(&input).unwrap();
|
||||
assert_eq!(expected, got);
|
||||
|
||||
let expected = format!(r#"{{"_url_":null}}"#);
|
||||
let input = Test {url: None};
|
||||
let got = serde_json::to_string(&input).unwrap();
|
||||
assert_eq!(expected, got);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_derive_with_for_url() {
|
||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
struct Test {
|
||||
#[serde(with = "self", rename = "_url_")]
|
||||
url: Url
|
||||
}
|
||||
|
||||
let url_str = "http://www.test.com/foo/bar?$param=bazz";
|
||||
let json_string = format!(r#"{{"_url_":"{}"}}"#, url_str);
|
||||
|
||||
// test deserialization
|
||||
let expected = Test {
|
||||
url: Url::parse(url_str).unwrap()
|
||||
};
|
||||
let got: Test = serde_json::from_str(&json_string).unwrap();
|
||||
assert_eq!(expected, got);
|
||||
|
||||
// test serialization
|
||||
let input = Test {url: Url::parse(url_str).unwrap()};
|
||||
let got = serde_json::to_string(&input).unwrap();
|
||||
assert_eq!(json_string, got);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_host() {
|
||||
for host in &[
|
||||
Host::Domain("foo.com".to_owned()),
|
||||
Host::Ipv4("127.0.0.1".parse().unwrap()),
|
||||
Host::Ipv6("::1".parse().unwrap()),
|
||||
] {
|
||||
let json = serde_json::to_string(&Ser(host)).unwrap();
|
||||
let de: De<Host> = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(de.into_inner(), *host)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user