percent_encoding.rs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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::fmt::Write;
  10. /// Represents a set of characters / bytes that should be percent-encoded.
  11. ///
  12. /// See [encode sets specification](http://url.spec.whatwg.org/#simple-encode-set).
  13. ///
  14. /// Different characters need to be encoded in different parts of an URL.
  15. /// For example, a literal `?` question mark in an URL’s path would indicate
  16. /// the start of the query string.
  17. /// A question mark meant to be part of the path therefore needs to be percent-encoded.
  18. /// In the query string however, a question mark does not have any special meaning
  19. /// and does not need to be percent-encoded.
  20. ///
  21. /// A few sets are defined in this module.
  22. /// Use the [`define_encode_set!`](../macro.define_encode_set!.html) macro to define different ones.
  23. pub trait EncodeSet {
  24. /// Called with UTF-8 bytes rather than code points.
  25. /// Should return false for all non-ASCII bytes.
  26. fn contains(&self, byte: u8) -> bool;
  27. }
  28. /// Define a new struct
  29. /// that implements the [`EncodeSet`](percent_encoding/trait.EncodeSet.html) trait,
  30. /// for use in [`percent_decode()`](percent_encoding/fn.percent_encode.html)
  31. /// and related functions.
  32. ///
  33. /// Parameters are characters to include in the set in addition to those of the base set.
  34. /// See [encode sets specification](http://url.spec.whatwg.org/#simple-encode-set).
  35. ///
  36. /// Example
  37. /// =======
  38. ///
  39. /// ```rust
  40. /// #[macro_use] extern crate url;
  41. /// use url::percent_encoding::{utf8_percent_encode, SIMPLE_ENCODE_SET};
  42. /// define_encode_set! {
  43. /// /// This encode set is used in the URL parser for query strings.
  44. /// pub QUERY_ENCODE_SET = [SIMPLE_ENCODE_SET] | {' ', '"', '#', '<', '>'}
  45. /// }
  46. /// # fn main() {
  47. /// assert_eq!(utf8_percent_encode("foo bar", QUERY_ENCODE_SET), "foo%20bar");
  48. /// # }
  49. /// ```
  50. #[macro_export]
  51. macro_rules! define_encode_set {
  52. ($(#[$attr: meta])* pub $name: ident = [$base_set: expr] | {$($ch: pat),*}) => {
  53. $(#[$attr])*
  54. #[derive(Copy, Clone)]
  55. #[allow(non_camel_case_types)]
  56. pub struct $name;
  57. impl $crate::percent_encoding::EncodeSet for $name {
  58. #[inline]
  59. fn contains(&self, byte: u8) -> bool {
  60. match byte as char {
  61. $(
  62. $ch => true,
  63. )*
  64. _ => $base_set.contains(byte)
  65. }
  66. }
  67. }
  68. }
  69. }
  70. /// This encode set is used for fragment identifier and non-relative scheme data.
  71. #[derive(Copy, Clone)]
  72. #[allow(non_camel_case_types)]
  73. pub struct SIMPLE_ENCODE_SET;
  74. impl EncodeSet for SIMPLE_ENCODE_SET {
  75. #[inline]
  76. fn contains(&self, byte: u8) -> bool {
  77. byte < 0x20 || byte > 0x7E
  78. }
  79. }
  80. define_encode_set! {
  81. /// This encode set is used in the URL parser for query strings.
  82. pub QUERY_ENCODE_SET = [SIMPLE_ENCODE_SET] | {' ', '"', '#', '<', '>'}
  83. }
  84. define_encode_set! {
  85. /// This encode set is used for path components.
  86. pub DEFAULT_ENCODE_SET = [QUERY_ENCODE_SET] | {'`', '?', '{', '}'}
  87. }
  88. define_encode_set! {
  89. /// This encode set is used in the URL parser for usernames and passwords.
  90. pub USERINFO_ENCODE_SET = [DEFAULT_ENCODE_SET] | {'@'}
  91. }
  92. define_encode_set! {
  93. /// This encode set should be used when setting the password field of a parsed URL.
  94. pub PASSWORD_ENCODE_SET = [USERINFO_ENCODE_SET] | {'\\', '/'}
  95. }
  96. define_encode_set! {
  97. /// This encode set should be used when setting the username field of a parsed URL.
  98. pub USERNAME_ENCODE_SET = [PASSWORD_ENCODE_SET] | {':'}
  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 push the result to `output`.
  149. pub fn percent_decode_to(input: &[u8], output: &mut Vec<u8>) {
  150. let mut i = 0;
  151. while i < input.len() {
  152. let c = input[i];
  153. if c == b'%' && i + 2 < input.len() {
  154. if let (Some(h), Some(l)) = (from_hex(input[i + 1]), from_hex(input[i + 2])) {
  155. output.push(h * 0x10 + l);
  156. i += 3;
  157. continue
  158. }
  159. }
  160. output.push(c);
  161. i += 1;
  162. }
  163. }
  164. /// Percent-decode the given bytes.
  165. #[inline]
  166. pub fn percent_decode(input: &[u8]) -> Vec<u8> {
  167. let mut output = Vec::new();
  168. percent_decode_to(input, &mut output);
  169. output
  170. }
  171. /// Percent-decode the given bytes, and decode the result as UTF-8.
  172. ///
  173. /// This is “lossy”: invalid UTF-8 percent-encoded byte sequences
  174. /// will be replaced � U+FFFD, the replacement character.
  175. #[inline]
  176. pub fn lossy_utf8_percent_decode(input: &[u8]) -> String {
  177. String::from_utf8_lossy(&percent_decode(input)).to_string()
  178. }
  179. /// Convert the given hex character into its numeric value.
  180. ///
  181. /// # Examples
  182. ///
  183. /// ```
  184. /// use url::percent_encoding::from_hex;
  185. /// assert_eq!(from_hex('0' as u8), Some(0));
  186. /// assert_eq!(from_hex('1' as u8), Some(1));
  187. /// assert_eq!(from_hex('9' as u8), Some(9));
  188. /// assert_eq!(from_hex('A' as u8), Some(10));
  189. /// assert_eq!(from_hex('a' as u8), Some(10));
  190. /// assert_eq!(from_hex('F' as u8), Some(15));
  191. /// assert_eq!(from_hex('f' as u8), Some(15));
  192. /// assert_eq!(from_hex('G' as u8), None);
  193. /// assert_eq!(from_hex('g' as u8), None);
  194. /// assert_eq!(from_hex('Z' as u8), None);
  195. /// assert_eq!(from_hex('z' as u8), None);
  196. /// ```
  197. #[inline]
  198. pub fn from_hex(byte: u8) -> Option<u8> {
  199. match byte {
  200. b'0' ... b'9' => Some(byte - b'0'), // 0..9
  201. b'A' ... b'F' => Some(byte + 10 - b'A'), // A..F
  202. b'a' ... b'f' => Some(byte + 10 - b'a'), // a..f
  203. _ => None
  204. }
  205. }