|
@@ -6,30 +6,35 @@
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
// except according to those terms.
|
|
// except according to those terms.
|
|
|
|
|
|
|
|
-//! URLs use special chacters to indicate the parts of the request. For example, a forward slash
|
|
|
|
|
-//! indicates a path. In order for that character to exist outside of a path separator, that
|
|
|
|
|
-//! character would need to be encoded.
|
|
|
|
|
|
|
+//! URLs use special chacters to indicate the parts of the request.
|
|
|
|
|
+//! For example, a `?` question mark marks the end of a path and the start of a query string.
|
|
|
|
|
+//! In order for that character to exist inside a path, it needs to be encoded differently.
|
|
|
//!
|
|
//!
|
|
|
-//! Percent encoding replaces reserved characters with the `%` escape character followed by hexidecimal
|
|
|
|
|
-//! ASCII representaton. For non-ASCII character that are percent encoded, a UTF-8 byte sequence
|
|
|
|
|
-//! becomes percent encoded. A simple example can be seen when the space literal is replaced with
|
|
|
|
|
-//! `%20`.
|
|
|
|
|
|
|
+//! Percent encoding replaces reserved characters with the `%` escape character
|
|
|
|
|
+//! followed by a byte value as two hexadecimal digits.
|
|
|
|
|
+//! For example, an ASCII space is replaced with `%20`.
|
|
|
//!
|
|
//!
|
|
|
-//! Percent encoding is further complicated by the fact that different parts of an URL have
|
|
|
|
|
-//! different encoding requirements. In order to support the variety of encoding requirements,
|
|
|
|
|
-//! `url::percent_encoding` includes different *encode sets*.
|
|
|
|
|
-//! See [URL Standard](https://url.spec.whatwg.org/#percent-encoded-bytes) for details.
|
|
|
|
|
|
|
+//! When encoding, the set of characters that can (and should, for readability) be left alone
|
|
|
|
|
+//! depends on the context.
|
|
|
|
|
+//! The `?` question mark mentioned above is not a separator when used literally
|
|
|
|
|
+//! inside of a query string, and therefore does not need to be encoded.
|
|
|
|
|
+//! The [`AsciiSet`] parameter of [`percent_encode`] and [`utf8_percent_encode`]
|
|
|
|
|
+//! lets callers configure this.
|
|
|
//!
|
|
//!
|
|
|
-//! This module provides some `*_ENCODE_SET` constants.
|
|
|
|
|
-//! If a different set is required, it can be created with
|
|
|
|
|
-//! the [`define_encode_set!`](../macro.define_encode_set!.html) macro.
|
|
|
|
|
|
|
+//! This crate delibarately does not provide many different sets.
|
|
|
|
|
+//! Users should consider in what context the encoded string will be used,
|
|
|
|
|
+//! real relevant specifications, and define their own set.
|
|
|
|
|
+//! This is done by using the `add` method of an existing set.
|
|
|
//!
|
|
//!
|
|
|
//! # Examples
|
|
//! # Examples
|
|
|
//!
|
|
//!
|
|
|
//! ```
|
|
//! ```
|
|
|
-//! use url::percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
|
|
|
|
|
|
|
+//! use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
|
|
|
//!
|
|
//!
|
|
|
-//! assert_eq!(utf8_percent_encode("foo bar?", DEFAULT_ENCODE_SET).to_string(), "foo%20bar%3F");
|
|
|
|
|
|
|
+//! /// https://url.spec.whatwg.org/#fragment-percent-encode-set
|
|
|
|
|
+//! const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
|
|
|
|
|
+//!
|
|
|
|
|
+//! assert_eq!(utf8_percent_encode("foo <bar>", FRAGMENT).to_string(), "foo%20%3Cbar%3E");
|
|
|
//! ```
|
|
//! ```
|
|
|
|
|
|
|
|
use std::borrow::Cow;
|
|
use std::borrow::Cow;
|
|
@@ -37,137 +42,127 @@ use std::fmt;
|
|
|
use std::slice;
|
|
use std::slice;
|
|
|
use std::str;
|
|
use std::str;
|
|
|
|
|
|
|
|
-/// Represents a set of characters / bytes that should be percent-encoded.
|
|
|
|
|
-///
|
|
|
|
|
-/// See [encode sets specification](http://url.spec.whatwg.org/#simple-encode-set).
|
|
|
|
|
-///
|
|
|
|
|
-/// Different characters need to be encoded in different parts of an URL.
|
|
|
|
|
-/// For example, a literal `?` question mark in an URL’s path would indicate
|
|
|
|
|
-/// the start of the query string.
|
|
|
|
|
-/// A question mark meant to be part of the path therefore needs to be percent-encoded.
|
|
|
|
|
-/// In the query string however, a question mark does not have any special meaning
|
|
|
|
|
-/// and does not need to be percent-encoded.
|
|
|
|
|
|
|
+/// Represents a set of characters or bytes in the ASCII range.
|
|
|
///
|
|
///
|
|
|
-/// A few sets are defined in this module.
|
|
|
|
|
-/// Use the [`define_encode_set!`](../macro.define_encode_set!.html) macro to define different ones.
|
|
|
|
|
-pub trait EncodeSet: Clone {
|
|
|
|
|
- /// Called with UTF-8 bytes rather than code points.
|
|
|
|
|
- /// Should return true for all non-ASCII bytes.
|
|
|
|
|
- fn contains(&self, byte: u8) -> bool;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/// Define a new struct
|
|
|
|
|
-/// that implements the [`EncodeSet`](percent_encoding/trait.EncodeSet.html) trait,
|
|
|
|
|
-/// for use in [`percent_decode()`](percent_encoding/fn.percent_encode.html)
|
|
|
|
|
-/// and related functions.
|
|
|
|
|
|
|
+/// This used in [`percent_encode`] and [`utf8_percent_encode`].
|
|
|
|
|
+/// This is simlar to [percent-encode sets](https://url.spec.whatwg.org/#percent-encoded-bytes).
|
|
|
///
|
|
///
|
|
|
-/// Parameters are characters to include in the set in addition to those of the base set.
|
|
|
|
|
-/// See [encode sets specification](http://url.spec.whatwg.org/#simple-encode-set).
|
|
|
|
|
|
|
+/// Use the `add` method of an existing set to define a new set. For example:
|
|
|
///
|
|
///
|
|
|
-/// Example
|
|
|
|
|
-/// =======
|
|
|
|
|
|
|
+/// ```
|
|
|
|
|
+/// use percent_encoding::{AsciiSet, CONTROLS};
|
|
|
///
|
|
///
|
|
|
-/// ```rust
|
|
|
|
|
-/// #[macro_use] extern crate percent_encoding;
|
|
|
|
|
-/// 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] | {' ', '"', '#', '<', '>'}
|
|
|
|
|
-/// }
|
|
|
|
|
-/// # fn main() {
|
|
|
|
|
-/// assert_eq!(utf8_percent_encode("foo bar", QUERY_ENCODE_SET).collect::<String>(), "foo%20bar");
|
|
|
|
|
-/// # }
|
|
|
|
|
|
|
+/// /// https://url.spec.whatwg.org/#fragment-percent-encode-set
|
|
|
|
|
+/// const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
|
|
|
/// ```
|
|
/// ```
|
|
|
-#[macro_export]
|
|
|
|
|
-macro_rules! define_encode_set {
|
|
|
|
|
- ($(#[$attr: meta])* pub $name: ident = [$base_set: expr] | {$($ch: pat),*}) => {
|
|
|
|
|
- $(#[$attr])*
|
|
|
|
|
- #[derive(Copy, Clone, Debug)]
|
|
|
|
|
- #[allow(non_camel_case_types)]
|
|
|
|
|
- pub struct $name;
|
|
|
|
|
-
|
|
|
|
|
- impl $crate::EncodeSet for $name {
|
|
|
|
|
- #[inline]
|
|
|
|
|
- fn contains(&self, byte: u8) -> bool {
|
|
|
|
|
- match byte as char {
|
|
|
|
|
- $(
|
|
|
|
|
- $ch => true,
|
|
|
|
|
- )*
|
|
|
|
|
- _ => $base_set.contains(byte)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+pub struct AsciiSet {
|
|
|
|
|
+ mask: [Chunk; ASCII_RANGE_LEN / BITS_PER_CHUNK],
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/// This encode set is used for the path of cannot-be-a-base URLs.
|
|
|
|
|
-///
|
|
|
|
|
-/// All ASCII charcters less than hexidecimal 20 and greater than 7E are encoded. This includes
|
|
|
|
|
-/// special charcters such as line feed, carriage return, NULL, etc.
|
|
|
|
|
-#[derive(Copy, Clone, Debug)]
|
|
|
|
|
-#[allow(non_camel_case_types)]
|
|
|
|
|
-pub struct SIMPLE_ENCODE_SET;
|
|
|
|
|
-
|
|
|
|
|
-impl EncodeSet for SIMPLE_ENCODE_SET {
|
|
|
|
|
- #[inline]
|
|
|
|
|
- fn contains(&self, byte: u8) -> bool {
|
|
|
|
|
- byte < 0x20 || byte > 0x7E
|
|
|
|
|
|
|
+type Chunk = u32;
|
|
|
|
|
+
|
|
|
|
|
+const ASCII_RANGE_LEN: usize = 0x80;
|
|
|
|
|
+
|
|
|
|
|
+const BITS_PER_CHUNK: usize = 8 * std::mem::size_of::<Chunk>();
|
|
|
|
|
+
|
|
|
|
|
+impl AsciiSet {
|
|
|
|
|
+ /// Called with UTF-8 bytes rather than code points.
|
|
|
|
|
+ /// Not used for non-ASCII bytes.
|
|
|
|
|
+ const fn contains(&self, byte: u8) -> bool {
|
|
|
|
|
+ let chunk = self.mask[byte as usize / BITS_PER_CHUNK];
|
|
|
|
|
+ let mask = 1 << (byte as usize % BITS_PER_CHUNK);
|
|
|
|
|
+ (chunk & mask) != 0
|
|
|
}
|
|
}
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
-define_encode_set! {
|
|
|
|
|
- /// This encode set is used in the URL parser for query strings.
|
|
|
|
|
- ///
|
|
|
|
|
- /// Aside from special chacters defined in the [`SIMPLE_ENCODE_SET`](struct.SIMPLE_ENCODE_SET.html),
|
|
|
|
|
- /// space, double quote ("), hash (#), and inequality qualifiers (<), (>) are encoded.
|
|
|
|
|
- pub QUERY_ENCODE_SET = [SIMPLE_ENCODE_SET] | {' ', '"', '#', '<', '>'}
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ fn should_percent_encode(&self, byte: u8) -> bool {
|
|
|
|
|
+ !byte.is_ascii() || self.contains(byte)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
-define_encode_set! {
|
|
|
|
|
- /// This encode set is used for path components.
|
|
|
|
|
- ///
|
|
|
|
|
- /// Aside from special chacters defined in the [`SIMPLE_ENCODE_SET`](struct.SIMPLE_ENCODE_SET.html),
|
|
|
|
|
- /// space, double quote ("), hash (#), inequality qualifiers (<), (>), backtick (`),
|
|
|
|
|
- /// question mark (?), and curly brackets ({), (}) are encoded.
|
|
|
|
|
- pub DEFAULT_ENCODE_SET = [QUERY_ENCODE_SET] | {'`', '?', '{', '}'}
|
|
|
|
|
|
|
+ pub const fn add(&self, byte: u8) -> Self {
|
|
|
|
|
+ let mut mask = self.mask;
|
|
|
|
|
+ mask[byte as usize / BITS_PER_CHUNK] |= 1 << (byte as usize % BITS_PER_CHUNK);
|
|
|
|
|
+ AsciiSet { mask }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-define_encode_set! {
|
|
|
|
|
- /// This encode set is used for on '/'-separated path segment
|
|
|
|
|
- ///
|
|
|
|
|
- /// Aside from special chacters defined in the [`SIMPLE_ENCODE_SET`](struct.SIMPLE_ENCODE_SET.html),
|
|
|
|
|
- /// space, double quote ("), hash (#), inequality qualifiers (<), (>), backtick (`),
|
|
|
|
|
- /// question mark (?), and curly brackets ({), (}), percent sign (%), forward slash (/) are
|
|
|
|
|
- /// encoded.
|
|
|
|
|
- ///
|
|
|
|
|
- /// # Note
|
|
|
|
|
- ///
|
|
|
|
|
- /// For [special URLs](https://url.spec.whatwg.org/#is-special), the backslash (\) character should
|
|
|
|
|
- /// additionally be escaped, but that is *not* included in this encode set.
|
|
|
|
|
- pub PATH_SEGMENT_ENCODE_SET = [DEFAULT_ENCODE_SET] | {'%', '/'}
|
|
|
|
|
-}
|
|
|
|
|
|
|
+/// The set of 0x00 to 0x1F (C0 controls), and 0x7F (DEL).
|
|
|
|
|
+///
|
|
|
|
|
+/// Note that this includes the newline and tab characters, but not the space 0x20.
|
|
|
|
|
+///
|
|
|
|
|
+/// <https://url.spec.whatwg.org/#c0-control-percent-encode-set>
|
|
|
|
|
+pub const CONTROLS: &AsciiSet = &AsciiSet {
|
|
|
|
|
+ mask: [
|
|
|
|
|
+ !0_u32, // C0: 0x00 to 0x1F (32 bits set)
|
|
|
|
|
+ 0,
|
|
|
|
|
+ 0,
|
|
|
|
|
+ 1 << (0x7F_u32 % 32), // DEL: 0x7F (one bit set)
|
|
|
|
|
+ ],
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
-define_encode_set! {
|
|
|
|
|
- /// This encode set is used for username and password.
|
|
|
|
|
- ///
|
|
|
|
|
- /// Aside from special chacters defined in the [`SIMPLE_ENCODE_SET`](struct.SIMPLE_ENCODE_SET.html),
|
|
|
|
|
- /// space, double quote ("), hash (#), inequality qualifiers (<), (>), backtick (`),
|
|
|
|
|
- /// question mark (?), and curly brackets ({), (}), forward slash (/), colon (:), semi-colon (;),
|
|
|
|
|
- /// equality (=), at (@), backslash (\\), square brackets ([), (]), caret (\^), and pipe (|) are
|
|
|
|
|
- /// encoded.
|
|
|
|
|
- pub USERINFO_ENCODE_SET = [DEFAULT_ENCODE_SET] | {
|
|
|
|
|
- '/', ':', ';', '=', '@', '[', '\\', ']', '^', '|'
|
|
|
|
|
|
|
+macro_rules! static_assert {
|
|
|
|
|
+ ($( $bool: expr, )+) => {
|
|
|
|
|
+ fn _static_assert() {
|
|
|
|
|
+ $(
|
|
|
|
|
+ let _ = std::mem::transmute::<[u8; $bool as usize], u8>;
|
|
|
|
|
+ )+
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/// Return the percent-encoding of the given bytes.
|
|
|
|
|
|
|
+static_assert! {
|
|
|
|
|
+ CONTROLS.contains(0x00),
|
|
|
|
|
+ CONTROLS.contains(0x1F),
|
|
|
|
|
+ !CONTROLS.contains(0x20),
|
|
|
|
|
+ !CONTROLS.contains(0x7E),
|
|
|
|
|
+ CONTROLS.contains(0x7F),
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/// Everything that is not an ASCII letter or digit.
|
|
|
|
|
+///
|
|
|
|
|
+/// This is probably more eager than necessary in any context.
|
|
|
|
|
+pub const NON_ALPHANUMERIC: &AsciiSet = &CONTROLS
|
|
|
|
|
+ .add(b' ')
|
|
|
|
|
+ .add(b'!')
|
|
|
|
|
+ .add(b'"')
|
|
|
|
|
+ .add(b'#')
|
|
|
|
|
+ .add(b'$')
|
|
|
|
|
+ .add(b'%')
|
|
|
|
|
+ .add(b'&')
|
|
|
|
|
+ .add(b'\'')
|
|
|
|
|
+ .add(b'(')
|
|
|
|
|
+ .add(b')')
|
|
|
|
|
+ .add(b'*')
|
|
|
|
|
+ .add(b'+')
|
|
|
|
|
+ .add(b',')
|
|
|
|
|
+ .add(b'-')
|
|
|
|
|
+ .add(b'.')
|
|
|
|
|
+ .add(b'/')
|
|
|
|
|
+ .add(b':')
|
|
|
|
|
+ .add(b';')
|
|
|
|
|
+ .add(b'<')
|
|
|
|
|
+ .add(b'=')
|
|
|
|
|
+ .add(b'>')
|
|
|
|
|
+ .add(b'?')
|
|
|
|
|
+ .add(b'@')
|
|
|
|
|
+ .add(b'[')
|
|
|
|
|
+ .add(b'\\')
|
|
|
|
|
+ .add(b']')
|
|
|
|
|
+ .add(b'^')
|
|
|
|
|
+ .add(b'_')
|
|
|
|
|
+ .add(b'`')
|
|
|
|
|
+ .add(b'{')
|
|
|
|
|
+ .add(b'|')
|
|
|
|
|
+ .add(b'}')
|
|
|
|
|
+ .add(b'~');
|
|
|
|
|
+
|
|
|
|
|
+/// Return the percent-encoding of the given byte.
|
|
|
///
|
|
///
|
|
|
-/// This is unconditional, unlike `percent_encode()` which uses an encode set.
|
|
|
|
|
|
|
+/// This is unconditional, unlike `percent_encode()` which has an `AsciiSet` parameter.
|
|
|
///
|
|
///
|
|
|
/// # Examples
|
|
/// # Examples
|
|
|
///
|
|
///
|
|
|
/// ```
|
|
/// ```
|
|
|
-/// use url::percent_encoding::percent_encode_byte;
|
|
|
|
|
|
|
+/// use percent_encoding::percent_encode_byte;
|
|
|
///
|
|
///
|
|
|
/// assert_eq!("foo bar".bytes().map(percent_encode_byte).collect::<String>(),
|
|
/// assert_eq!("foo bar".bytes().map(percent_encode_byte).collect::<String>(),
|
|
|
/// "%66%6F%6F%20%62%61%72");
|
|
/// "%66%6F%6F%20%62%61%72");
|
|
@@ -194,74 +189,69 @@ pub fn percent_encode_byte(byte: u8) -> &'static str {
|
|
|
"[index..index + 3]
|
|
"[index..index + 3]
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/// Percent-encode the given bytes with the given encode set.
|
|
|
|
|
|
|
+/// Percent-encode the given bytes with the given set.
|
|
|
///
|
|
///
|
|
|
-/// The encode set define which bytes (in addition to non-ASCII and controls)
|
|
|
|
|
-/// need to be percent-encoded.
|
|
|
|
|
-/// The choice of this set depends on context.
|
|
|
|
|
-/// For example, `?` needs to be encoded in an URL path but not in a query string.
|
|
|
|
|
|
|
+/// Non-ASCII bytes and bytes in `ascii_set` are encoded.
|
|
|
///
|
|
///
|
|
|
-/// The return value is an iterator of `&str` slices (so it has a `.collect::<String>()` method)
|
|
|
|
|
-/// that also implements `Display` and `Into<Cow<str>>`.
|
|
|
|
|
-/// The latter returns `Cow::Borrowed` when none of the bytes in `input`
|
|
|
|
|
-/// are in the given encode set.
|
|
|
|
|
|
|
+/// The return type:
|
|
|
|
|
+///
|
|
|
|
|
+/// * Implements `Iterator<Item = &str>` and therefore has a `.collect::<String>()` method,
|
|
|
|
|
+/// * Implements `Display` and therefore has a `.to_string()` method,
|
|
|
|
|
+/// * Implements `Into<Cow<str>>` borrowing `input` when none of its bytes are encoded.
|
|
|
///
|
|
///
|
|
|
/// # Examples
|
|
/// # Examples
|
|
|
///
|
|
///
|
|
|
/// ```
|
|
/// ```
|
|
|
-/// use url::percent_encoding::{percent_encode, DEFAULT_ENCODE_SET};
|
|
|
|
|
|
|
+/// use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
|
|
|
///
|
|
///
|
|
|
-/// assert_eq!(percent_encode(b"foo bar?", DEFAULT_ENCODE_SET).to_string(), "foo%20bar%3F");
|
|
|
|
|
|
|
+/// assert_eq!(percent_encode(b"foo bar?", NON_ALPHANUMERIC).to_string(), "foo%20bar%3F");
|
|
|
/// ```
|
|
/// ```
|
|
|
#[inline]
|
|
#[inline]
|
|
|
-pub fn percent_encode<E: EncodeSet>(input: &[u8], encode_set: E) -> PercentEncode<E> {
|
|
|
|
|
|
|
+pub fn percent_encode<'a>(input: &'a [u8], ascii_set: &'static AsciiSet) -> PercentEncode<'a> {
|
|
|
PercentEncode {
|
|
PercentEncode {
|
|
|
bytes: input,
|
|
bytes: input,
|
|
|
- encode_set: encode_set,
|
|
|
|
|
|
|
+ ascii_set,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// Percent-encode the UTF-8 encoding of the given string.
|
|
/// Percent-encode the UTF-8 encoding of the given string.
|
|
|
///
|
|
///
|
|
|
-/// See `percent_encode()` for how to use the return value.
|
|
|
|
|
|
|
+/// See [`percent_encode`] regarding the return type.
|
|
|
///
|
|
///
|
|
|
/// # Examples
|
|
/// # Examples
|
|
|
///
|
|
///
|
|
|
/// ```
|
|
/// ```
|
|
|
-/// use url::percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
|
|
|
|
|
|
|
+/// use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
|
|
///
|
|
///
|
|
|
-/// assert_eq!(utf8_percent_encode("foo bar?", DEFAULT_ENCODE_SET).to_string(), "foo%20bar%3F");
|
|
|
|
|
|
|
+/// assert_eq!(utf8_percent_encode("foo bar?", NON_ALPHANUMERIC).to_string(), "foo%20bar%3F");
|
|
|
/// ```
|
|
/// ```
|
|
|
#[inline]
|
|
#[inline]
|
|
|
-pub fn utf8_percent_encode<E: EncodeSet>(input: &str, encode_set: E) -> PercentEncode<E> {
|
|
|
|
|
- percent_encode(input.as_bytes(), encode_set)
|
|
|
|
|
|
|
+pub fn utf8_percent_encode<'a>(input: &'a str, ascii_set: &'static AsciiSet) -> PercentEncode<'a> {
|
|
|
|
|
+ percent_encode(input.as_bytes(), ascii_set)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/// The return type of `percent_encode()` and `utf8_percent_encode()`.
|
|
|
|
|
-#[derive(Clone, Debug)]
|
|
|
|
|
-pub struct PercentEncode<'a, E: EncodeSet> {
|
|
|
|
|
|
|
+/// The return type of [`percent_encode`] and [`utf8_percent_encode`].
|
|
|
|
|
+#[derive(Clone)]
|
|
|
|
|
+pub struct PercentEncode<'a> {
|
|
|
bytes: &'a [u8],
|
|
bytes: &'a [u8],
|
|
|
- encode_set: E,
|
|
|
|
|
|
|
+ ascii_set: &'static AsciiSet,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-impl<'a, E: EncodeSet> Iterator for PercentEncode<'a, E> {
|
|
|
|
|
|
|
+impl<'a> Iterator for PercentEncode<'a> {
|
|
|
type Item = &'a str;
|
|
type Item = &'a str;
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<&'a str> {
|
|
fn next(&mut self) -> Option<&'a str> {
|
|
|
if let Some((&first_byte, remaining)) = self.bytes.split_first() {
|
|
if let Some((&first_byte, remaining)) = self.bytes.split_first() {
|
|
|
- if self.encode_set.contains(first_byte) {
|
|
|
|
|
|
|
+ if self.ascii_set.should_percent_encode(first_byte) {
|
|
|
self.bytes = remaining;
|
|
self.bytes = remaining;
|
|
|
Some(percent_encode_byte(first_byte))
|
|
Some(percent_encode_byte(first_byte))
|
|
|
} else {
|
|
} else {
|
|
|
- assert!(first_byte.is_ascii());
|
|
|
|
|
for (i, &byte) in remaining.iter().enumerate() {
|
|
for (i, &byte) in remaining.iter().enumerate() {
|
|
|
- if self.encode_set.contains(byte) {
|
|
|
|
|
|
|
+ if self.ascii_set.should_percent_encode(byte) {
|
|
|
// 1 for first_byte + i for previous iterations of this loop
|
|
// 1 for first_byte + i for previous iterations of this loop
|
|
|
let (unchanged_slice, remaining) = self.bytes.split_at(1 + i);
|
|
let (unchanged_slice, remaining) = self.bytes.split_at(1 + i);
|
|
|
self.bytes = remaining;
|
|
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());
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
let unchanged_slice = self.bytes;
|
|
let unchanged_slice = self.bytes;
|
|
@@ -282,7 +272,7 @@ impl<'a, E: EncodeSet> Iterator for PercentEncode<'a, E> {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-impl<'a, E: EncodeSet> fmt::Display for PercentEncode<'a, E> {
|
|
|
|
|
|
|
+impl<'a> fmt::Display for PercentEncode<'a> {
|
|
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
|
for c in (*self).clone() {
|
|
for c in (*self).clone() {
|
|
|
formatter.write_str(c)?
|
|
formatter.write_str(c)?
|
|
@@ -291,8 +281,8 @@ impl<'a, E: EncodeSet> fmt::Display for PercentEncode<'a, E> {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-impl<'a, E: EncodeSet> From<PercentEncode<'a, E>> for Cow<'a, str> {
|
|
|
|
|
- fn from(mut iter: PercentEncode<'a, E>) -> Self {
|
|
|
|
|
|
|
+impl<'a> From<PercentEncode<'a>> for Cow<'a, str> {
|
|
|
|
|
+ fn from(mut iter: PercentEncode<'a>) -> Self {
|
|
|
match iter.next() {
|
|
match iter.next() {
|
|
|
None => "".into(),
|
|
None => "".into(),
|
|
|
Some(first) => match iter.next() {
|
|
Some(first) => match iter.next() {
|
|
@@ -308,19 +298,33 @@ impl<'a, E: EncodeSet> From<PercentEncode<'a, E>> for Cow<'a, str> {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/// Percent-decode the given string.
|
|
|
|
|
+///
|
|
|
|
|
+/// <https://url.spec.whatwg.org/#string-percent-decode>
|
|
|
|
|
+///
|
|
|
|
|
+/// See [`percent_decode`] regarding the return type.
|
|
|
|
|
+#[inline]
|
|
|
|
|
+pub fn percent_decode_str(input: &str) -> PercentDecode {
|
|
|
|
|
+ percent_decode(input.as_bytes())
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/// Percent-decode the given bytes.
|
|
/// Percent-decode the given bytes.
|
|
|
///
|
|
///
|
|
|
-/// The return value is an iterator of decoded `u8` bytes
|
|
|
|
|
-/// that also implements `Into<Cow<u8>>`
|
|
|
|
|
-/// (which returns `Cow::Borrowed` when `input` contains no percent-encoded sequence)
|
|
|
|
|
-/// and has `decode_utf8()` and `decode_utf8_lossy()` methods.
|
|
|
|
|
|
|
+/// <https://url.spec.whatwg.org/#percent-decode>
|
|
|
|
|
+///
|
|
|
|
|
+/// Any sequence of `%` followed by two hexadecimal digits is decoded.
|
|
|
|
|
+/// The return type:
|
|
|
|
|
+///
|
|
|
|
|
+/// * Implements `Into<Cow<u8>>` borrowing `input` when it contains no percent-encoded sequence,
|
|
|
|
|
+/// * Implements `Iterator<Item = u8>` and therefore has a `.collect::<Vec<u8>>()` method,
|
|
|
|
|
+/// * Has `decode_utf8()` and `decode_utf8_lossy()` methods.
|
|
|
///
|
|
///
|
|
|
/// # Examples
|
|
/// # Examples
|
|
|
///
|
|
///
|
|
|
/// ```
|
|
/// ```
|
|
|
-/// use url::percent_encoding::percent_decode;
|
|
|
|
|
|
|
+/// use percent_encoding::percent_decode;
|
|
|
///
|
|
///
|
|
|
-/// assert_eq!(percent_decode(b"foo%20bar%3F").decode_utf8().unwrap(), "foo bar?");
|
|
|
|
|
|
|
+/// assert_eq!(percent_decode(b"foo%20bar%3f").decode_utf8().unwrap(), "foo bar?");
|
|
|
/// ```
|
|
/// ```
|
|
|
#[inline]
|
|
#[inline]
|
|
|
pub fn percent_decode(input: &[u8]) -> PercentDecode {
|
|
pub fn percent_decode(input: &[u8]) -> PercentDecode {
|
|
@@ -329,22 +333,18 @@ pub fn percent_decode(input: &[u8]) -> PercentDecode {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/// The return type of `percent_decode()`.
|
|
|
|
|
|
|
+/// The return type of [`percent_decode`].
|
|
|
#[derive(Clone, Debug)]
|
|
#[derive(Clone, Debug)]
|
|
|
pub struct PercentDecode<'a> {
|
|
pub struct PercentDecode<'a> {
|
|
|
bytes: slice::Iter<'a, u8>,
|
|
bytes: slice::Iter<'a, u8>,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
fn after_percent_sign(iter: &mut slice::Iter<u8>) -> Option<u8> {
|
|
fn after_percent_sign(iter: &mut slice::Iter<u8>) -> Option<u8> {
|
|
|
- let initial_iter = iter.clone();
|
|
|
|
|
- let h = iter.next().and_then(|&b| (b as char).to_digit(16));
|
|
|
|
|
- let l = iter.next().and_then(|&b| (b as char).to_digit(16));
|
|
|
|
|
- if let (Some(h), Some(l)) = (h, l) {
|
|
|
|
|
- Some(h as u8 * 0x10 + l as u8)
|
|
|
|
|
- } else {
|
|
|
|
|
- *iter = initial_iter;
|
|
|
|
|
- None
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ let mut cloned_iter = iter.clone();
|
|
|
|
|
+ let h = char::from(*cloned_iter.next()?).to_digit(16)?;
|
|
|
|
|
+ let l = char::from(*cloned_iter.next()?).to_digit(16)?;
|
|
|
|
|
+ *iter = cloned_iter;
|
|
|
|
|
+ Some(h as u8 * 0x10 + l as u8)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Iterator for PercentDecode<'a> {
|
|
impl<'a> Iterator for PercentDecode<'a> {
|
|
@@ -377,7 +377,7 @@ impl<'a> From<PercentDecode<'a>> for Cow<'a, [u8]> {
|
|
|
|
|
|
|
|
impl<'a> PercentDecode<'a> {
|
|
impl<'a> PercentDecode<'a> {
|
|
|
/// If the percent-decoding is different from the input, return it as a new bytes vector.
|
|
/// If the percent-decoding is different from the input, return it as a new bytes vector.
|
|
|
- pub fn if_any(&self) -> Option<Vec<u8>> {
|
|
|
|
|
|
|
+ fn if_any(&self) -> Option<Vec<u8>> {
|
|
|
let mut bytes_iter = self.bytes.clone();
|
|
let mut bytes_iter = self.bytes.clone();
|
|
|
while bytes_iter.any(|&b| b == b'%') {
|
|
while bytes_iter.any(|&b| b == b'%') {
|
|
|
if let Some(decoded_byte) = after_percent_sign(&mut bytes_iter) {
|
|
if let Some(decoded_byte) = after_percent_sign(&mut bytes_iter) {
|