percent_encoding.rs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. // Copyright 2013-2014 Simon Sapin.
  2. //
  3. // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
  4. // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
  5. // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
  6. // option. This file may not be copied, modified, or distributed
  7. // except according to those terms.
  8. use std::ascii::AsciiExt;
  9. use std::borrow::Cow;
  10. use std::fmt::Write;
  11. use std::slice;
  12. /// Represents a set of characters / bytes that should be percent-encoded.
  13. ///
  14. /// See [encode sets specification](http://url.spec.whatwg.org/#simple-encode-set).
  15. ///
  16. /// Different characters need to be encoded in different parts of an URL.
  17. /// For example, a literal `?` question mark in an URL’s path would indicate
  18. /// the start of the query string.
  19. /// A question mark meant to be part of the path therefore needs to be percent-encoded.
  20. /// In the query string however, a question mark does not have any special meaning
  21. /// and does not need to be percent-encoded.
  22. ///
  23. /// A few sets are defined in this module.
  24. /// Use the [`define_encode_set!`](../macro.define_encode_set!.html) macro to define different ones.
  25. pub trait EncodeSet {
  26. /// Called with UTF-8 bytes rather than code points.
  27. /// Should return false for all non-ASCII bytes.
  28. fn contains(&self, byte: u8) -> bool;
  29. }
  30. /// Define a new struct
  31. /// that implements the [`EncodeSet`](percent_encoding/trait.EncodeSet.html) trait,
  32. /// for use in [`percent_decode()`](percent_encoding/fn.percent_encode.html)
  33. /// and related functions.
  34. ///
  35. /// Parameters are characters to include in the set in addition to those of the base set.
  36. /// See [encode sets specification](http://url.spec.whatwg.org/#simple-encode-set).
  37. ///
  38. /// Example
  39. /// =======
  40. ///
  41. /// ```rust
  42. /// #[macro_use] extern crate url;
  43. /// use url::percent_encoding::{utf8_percent_encode, SIMPLE_ENCODE_SET};
  44. /// define_encode_set! {
  45. /// /// This encode set is used in the URL parser for query strings.
  46. /// pub QUERY_ENCODE_SET = [SIMPLE_ENCODE_SET] | {' ', '"', '#', '<', '>'}
  47. /// }
  48. /// # fn main() {
  49. /// assert_eq!(utf8_percent_encode("foo bar", QUERY_ENCODE_SET), "foo%20bar");
  50. /// # }
  51. /// ```
  52. #[macro_export]
  53. macro_rules! define_encode_set {
  54. ($(#[$attr: meta])* pub $name: ident = [$base_set: expr] | {$($ch: pat),*}) => {
  55. $(#[$attr])*
  56. #[derive(Copy, Clone)]
  57. #[allow(non_camel_case_types)]
  58. pub struct $name;
  59. impl $crate::percent_encoding::EncodeSet for $name {
  60. #[inline]
  61. fn contains(&self, byte: u8) -> bool {
  62. match byte as char {
  63. $(
  64. $ch => true,
  65. )*
  66. _ => $base_set.contains(byte)
  67. }
  68. }
  69. }
  70. }
  71. }
  72. /// This encode set is used for fragment identifier and non-relative scheme data.
  73. #[derive(Copy, Clone)]
  74. #[allow(non_camel_case_types)]
  75. pub struct SIMPLE_ENCODE_SET;
  76. impl EncodeSet for SIMPLE_ENCODE_SET {
  77. #[inline]
  78. fn contains(&self, byte: u8) -> bool {
  79. byte < 0x20 || byte > 0x7E
  80. }
  81. }
  82. define_encode_set! {
  83. /// This encode set is used in the URL parser for query strings.
  84. pub QUERY_ENCODE_SET = [SIMPLE_ENCODE_SET] | {' ', '"', '#', '<', '>'}
  85. }
  86. define_encode_set! {
  87. /// This encode set is used for path components.
  88. pub DEFAULT_ENCODE_SET = [QUERY_ENCODE_SET] | {'`', '?', '{', '}'}
  89. }
  90. define_encode_set! {
  91. /// This encode set is used for username and password.
  92. pub PATH_SEGMENT_ENCODE_SET = [DEFAULT_ENCODE_SET] | {'%'}
  93. }
  94. define_encode_set! {
  95. /// This encode set is used for username and password.
  96. pub USERINFO_ENCODE_SET = [DEFAULT_ENCODE_SET] | {
  97. '/', ':', ';', '=', '@', '[', '\\', ']', '^', '|'
  98. }
  99. }
  100. define_encode_set! {
  101. /// This encode set is used in `application/x-www-form-urlencoded` serialization.
  102. pub FORM_URLENCODED_ENCODE_SET = [SIMPLE_ENCODE_SET] | {
  103. ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '+', ',', '/', ':', ';',
  104. '<', '=', '>', '?', '@', '[', '\\', ']', '^', '`', '{', '|', '}', '~'
  105. }
  106. }
  107. /// Percent-encode the given bytes, and push the result to `output`.
  108. ///
  109. /// The pushed strings are within the ASCII range.
  110. #[inline]
  111. pub fn percent_encode_to<E: EncodeSet>(input: &[u8], encode_set: E, output: &mut String) {
  112. for &byte in input {
  113. if encode_set.contains(byte) {
  114. write!(output, "%{:02X}", byte).unwrap();
  115. } else {
  116. assert!(byte.is_ascii());
  117. unsafe {
  118. output.as_mut_vec().push(byte)
  119. }
  120. }
  121. }
  122. }
  123. /// Percent-encode the given bytes.
  124. ///
  125. /// The returned string is within the ASCII range.
  126. #[inline]
  127. pub fn percent_encode<E: EncodeSet>(input: &[u8], encode_set: E) -> String {
  128. let mut output = String::new();
  129. percent_encode_to(input, encode_set, &mut output);
  130. output
  131. }
  132. /// Percent-encode the UTF-8 encoding of the given string, and push the result to `output`.
  133. ///
  134. /// The pushed strings are within the ASCII range.
  135. #[inline]
  136. pub fn utf8_percent_encode_to<E: EncodeSet>(input: &str, encode_set: E, output: &mut String) {
  137. percent_encode_to(input.as_bytes(), encode_set, output)
  138. }
  139. /// Percent-encode the UTF-8 encoding of the given string.
  140. ///
  141. /// The returned string is within the ASCII range.
  142. #[inline]
  143. pub fn utf8_percent_encode<E: EncodeSet>(input: &str, encode_set: E) -> String {
  144. let mut output = String::new();
  145. utf8_percent_encode_to(input, encode_set, &mut output);
  146. output
  147. }
  148. /// Percent-decode the given bytes and return an iterator of bytes.
  149. #[inline]
  150. pub fn percent_decode(input: &[u8]) -> PercentDecode {
  151. PercentDecode {
  152. iter: input.iter()
  153. }
  154. }
  155. pub struct PercentDecode<'a> {
  156. iter: slice::Iter<'a, u8>,
  157. }
  158. impl<'a> Iterator for PercentDecode<'a> {
  159. type Item = u8;
  160. fn next(&mut self) -> Option<u8> {
  161. self.iter.next().map(|&byte| {
  162. if byte == b'%' {
  163. let after_percent_sign = self.iter.clone();
  164. let h = self.iter.next().and_then(|&b| (b as char).to_digit(16));
  165. let l = self.iter.next().and_then(|&b| (b as char).to_digit(16));
  166. if let (Some(h), Some(l)) = (h, l) {
  167. return h as u8 * 0x10 + l as u8
  168. }
  169. self.iter = after_percent_sign;
  170. }
  171. byte
  172. })
  173. }
  174. fn size_hint(&self) -> (usize, Option<usize>) {
  175. let (low, high) = self.iter.size_hint();
  176. (low, high.and_then(|high| high.checked_mul(3)))
  177. }
  178. }
  179. /// Percent-decode the given bytes, and decode the result as UTF-8.
  180. ///
  181. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  182. /// will be replaced � U+FFFD, the replacement character.
  183. pub fn lossy_utf8_percent_decode(input: &[u8]) -> String {
  184. let bytes = percent_decode(input).collect::<Vec<u8>>();
  185. match String::from_utf8_lossy(&bytes) {
  186. Cow::Owned(s) => return s,
  187. Cow::Borrowed(_) => {}
  188. }
  189. unsafe {
  190. String::from_utf8_unchecked(bytes)
  191. }
  192. }