ascii_set.rs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. // Copyright 2013-2016 The rust-url developers.
  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 core::{mem, ops};
  9. /// Represents a set of characters or bytes in the ASCII range.
  10. ///
  11. /// This is used in [`percent_encode`] and [`utf8_percent_encode`].
  12. /// This is similar to [percent-encode sets](https://url.spec.whatwg.org/#percent-encoded-bytes).
  13. ///
  14. /// Use the `add` method of an existing set to define a new set. For example:
  15. ///
  16. /// [`percent_encode`]: crate::percent_encode
  17. /// [`utf8_percent_encode`]: crate::utf8_percent_encode
  18. ///
  19. /// ```
  20. /// use percent_encoding::{AsciiSet, CONTROLS};
  21. ///
  22. /// /// https://url.spec.whatwg.org/#fragment-percent-encode-set
  23. /// const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
  24. /// ```
  25. #[derive(Debug, PartialEq, Eq)]
  26. pub struct AsciiSet {
  27. mask: [Chunk; ASCII_RANGE_LEN / BITS_PER_CHUNK],
  28. }
  29. type Chunk = u32;
  30. const ASCII_RANGE_LEN: usize = 0x80;
  31. const BITS_PER_CHUNK: usize = 8 * mem::size_of::<Chunk>();
  32. impl AsciiSet {
  33. /// An empty set.
  34. pub const EMPTY: AsciiSet = AsciiSet {
  35. mask: [0; ASCII_RANGE_LEN / BITS_PER_CHUNK],
  36. };
  37. /// Called with UTF-8 bytes rather than code points.
  38. /// Not used for non-ASCII bytes.
  39. pub(crate) const fn contains(&self, byte: u8) -> bool {
  40. let chunk = self.mask[byte as usize / BITS_PER_CHUNK];
  41. let mask = 1 << (byte as usize % BITS_PER_CHUNK);
  42. (chunk & mask) != 0
  43. }
  44. pub(crate) fn should_percent_encode(&self, byte: u8) -> bool {
  45. !byte.is_ascii() || self.contains(byte)
  46. }
  47. pub const fn add(&self, byte: u8) -> Self {
  48. let mut mask = self.mask;
  49. mask[byte as usize / BITS_PER_CHUNK] |= 1 << (byte as usize % BITS_PER_CHUNK);
  50. AsciiSet { mask }
  51. }
  52. pub const fn remove(&self, byte: u8) -> Self {
  53. let mut mask = self.mask;
  54. mask[byte as usize / BITS_PER_CHUNK] &= !(1 << (byte as usize % BITS_PER_CHUNK));
  55. AsciiSet { mask }
  56. }
  57. /// Return the union of two sets.
  58. pub const fn union(&self, other: Self) -> Self {
  59. let mask = [
  60. self.mask[0] | other.mask[0],
  61. self.mask[1] | other.mask[1],
  62. self.mask[2] | other.mask[2],
  63. self.mask[3] | other.mask[3],
  64. ];
  65. AsciiSet { mask }
  66. }
  67. /// Return the negation of the set.
  68. pub const fn complement(&self) -> Self {
  69. let mask = [!self.mask[0], !self.mask[1], !self.mask[2], !self.mask[3]];
  70. AsciiSet { mask }
  71. }
  72. }
  73. impl ops::Add for AsciiSet {
  74. type Output = Self;
  75. fn add(self, other: Self) -> Self {
  76. self.union(other)
  77. }
  78. }
  79. impl ops::Not for AsciiSet {
  80. type Output = Self;
  81. fn not(self) -> Self {
  82. self.complement()
  83. }
  84. }
  85. /// The set of 0x00 to 0x1F (C0 controls), and 0x7F (DEL).
  86. ///
  87. /// Note that this includes the newline and tab characters, but not the space 0x20.
  88. ///
  89. /// <https://url.spec.whatwg.org/#c0-control-percent-encode-set>
  90. pub const CONTROLS: &AsciiSet = &AsciiSet {
  91. mask: [
  92. !0_u32, // C0: 0x00 to 0x1F (32 bits set)
  93. 0,
  94. 0,
  95. 1 << (0x7F_u32 % 32), // DEL: 0x7F (one bit set)
  96. ],
  97. };
  98. macro_rules! static_assert {
  99. ($( $bool: expr, )+) => {
  100. fn _static_assert() {
  101. $(
  102. let _ = mem::transmute::<[u8; $bool as usize], u8>;
  103. )+
  104. }
  105. }
  106. }
  107. static_assert! {
  108. CONTROLS.contains(0x00),
  109. CONTROLS.contains(0x1F),
  110. !CONTROLS.contains(0x20),
  111. !CONTROLS.contains(0x7E),
  112. CONTROLS.contains(0x7F),
  113. }
  114. /// Everything that is not an ASCII letter or digit.
  115. ///
  116. /// This is probably more eager than necessary in any context.
  117. pub const NON_ALPHANUMERIC: &AsciiSet = &CONTROLS
  118. .add(b' ')
  119. .add(b'!')
  120. .add(b'"')
  121. .add(b'#')
  122. .add(b'$')
  123. .add(b'%')
  124. .add(b'&')
  125. .add(b'\'')
  126. .add(b'(')
  127. .add(b')')
  128. .add(b'*')
  129. .add(b'+')
  130. .add(b',')
  131. .add(b'-')
  132. .add(b'.')
  133. .add(b'/')
  134. .add(b':')
  135. .add(b';')
  136. .add(b'<')
  137. .add(b'=')
  138. .add(b'>')
  139. .add(b'?')
  140. .add(b'@')
  141. .add(b'[')
  142. .add(b'\\')
  143. .add(b']')
  144. .add(b'^')
  145. .add(b'_')
  146. .add(b'`')
  147. .add(b'{')
  148. .add(b'|')
  149. .add(b'}')
  150. .add(b'~');
  151. #[cfg(test)]
  152. mod tests {
  153. use super::*;
  154. #[test]
  155. fn add_op() {
  156. let left = AsciiSet::EMPTY.add(b'A');
  157. let right = AsciiSet::EMPTY.add(b'B');
  158. let expected = AsciiSet::EMPTY.add(b'A').add(b'B');
  159. assert_eq!(left + right, expected);
  160. }
  161. #[test]
  162. fn not_op() {
  163. let set = AsciiSet::EMPTY.add(b'A').add(b'B');
  164. let not_set = !set;
  165. assert!(!not_set.contains(b'A'));
  166. assert!(not_set.contains(b'C'));
  167. }
  168. /// This test ensures that we can get the union of two sets as a constant value, which is
  169. /// useful for defining sets in a modular way.
  170. #[test]
  171. fn union() {
  172. const A: AsciiSet = AsciiSet::EMPTY.add(b'A');
  173. const B: AsciiSet = AsciiSet::EMPTY.add(b'B');
  174. const UNION: AsciiSet = A.union(B);
  175. const EXPECTED: AsciiSet = AsciiSet::EMPTY.add(b'A').add(b'B');
  176. assert_eq!(UNION, EXPECTED);
  177. }
  178. /// This test ensures that we can get the complement of a set as a constant value, which is
  179. /// useful for defining sets in a modular way.
  180. #[test]
  181. fn complement() {
  182. const BOTH: AsciiSet = AsciiSet::EMPTY.add(b'A').add(b'B');
  183. const COMPLEMENT: AsciiSet = BOTH.complement();
  184. assert!(!COMPLEMENT.contains(b'A'));
  185. assert!(!COMPLEMENT.contains(b'B'));
  186. assert!(COMPLEMENT.contains(b'C'));
  187. }
  188. }