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:
bors-servo
2019-07-17 08:00:07 -04:00
committed by GitHub
34 changed files with 1626 additions and 2116 deletions
+2 -18
View File
@@ -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+ dont 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
View File
@@ -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"]
+16 -19
View File
@@ -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
View File
@@ -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 URLs body to `write_body_bytes`,
/// and return the URLs 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 URLs 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 Standards 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
View File
@@ -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(&parameters, 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
View File
@@ -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)),
);
}
}
View File
-3
View File
@@ -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>
-3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -13,10 +13,8 @@
//! `encode_str` and `decode_to_string` provide convenience wrappers
//! that convert from and to Rusts 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 decoders <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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -13,13 +13,11 @@
//! Converts between a string (such as an URLs 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 encodings 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
View File
@@ -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 doesnt 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 dont 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 its 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
View File
File diff suppressed because it is too large Load Diff
+12 -29
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+21 -9
View File
@@ -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
+35
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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));
}
-23
View File
@@ -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
View File
@@ -1 +0,0 @@
../LICENSE-APACHE
-1
View File
@@ -1 +0,0 @@
../LICENSE-MIT
-11
View File
@@ -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/).
-421
View File
@@ -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 doesnt 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)
}
}