product_os_urlencoding/
lib.rs1#![no_std]
2extern crate no_std_compat as std;
3extern crate alloc;
4
5use std::prelude::v1::*;
6
7use std::string::String;
8use std::string::FromUtf8Error;
9use std::fmt::{self, Display};
10
11pub fn encode(data: &str) -> String {
12 let escaped = encode_into(data);
13 let string = String::from_utf8_lossy(escaped.as_slice());
14 string.to_string()
15}
16
17#[inline]
18fn encode_into(data: &str) -> Vec<u8> {
19 let mut escaped = vec![];
20
21 for byte in data.as_bytes().iter() {
22 match byte.to_owned() {
23 b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'-' | b'.' | b'_' | b'~' => {
24 escaped.push(byte.to_owned());
25 },
26 other => {
27 escaped.push(b'%');
28 escaped.push(to_hex_digit(other.to_owned() >> 4));
29 escaped.push(to_hex_digit(other & 15));
30 },
31 }
32 }
33
34 escaped
35}
36
37#[inline]
38fn from_hex_digit(digit: u8) -> Option<u8> {
39 match digit {
40 b'0'..=b'9' => Some(digit - b'0'),
41 b'A'..=b'F' => Some(digit - b'A' + 10),
42 b'a'..=b'f' => Some(digit - b'a' + 10),
43 _ => None,
44 }
45}
46
47#[inline]
48fn to_hex_digit(digit: u8) -> u8 {
49 match digit {
50 0..=9 => b'0' + digit,
51 10..=255 => b'A' - 10 + digit
52 }
53}
54
55pub fn decode(string: &str) -> Result<String, FromUrlEncodingError> {
56 let mut out: Vec<u8> = vec![];
57 let mut bytes = string.as_bytes().iter().copied();
58 while let Some(b) = bytes.next() {
59 match b {
60 b'%' => {
61 match bytes.next() {
62 Some(first) => match from_hex_digit(first.to_owned()) {
63 Some(first_val) => match bytes.next() {
64 Some(second) => match from_hex_digit(second.to_owned()) {
65 Some(second_val) => {
66 out.push((first_val << 4) | second_val);
67 },
68 None => {
69 out.push(b'%');
70 out.push(first);
71 out.push(second);
72 },
73 },
74 None => {
75 out.push(b'%');
76 out.push(first);
77 },
78 },
79 None => {
80 out.push(b'%');
81 out.push(first);
82 },
83 },
84 None => out.push(b'%'),
85 };
86 },
87 other => out.push(other),
88 }
89 }
90 String::from_utf8(out).map_err(|error| FromUrlEncodingError::Utf8CharacterError {error})
91}
92
93#[derive(Debug)]
94pub enum FromUrlEncodingError {
95 UriCharacterError { character: char, index: usize },
96 Utf8CharacterError { error: FromUtf8Error },
97}
98
99
100impl Display for FromUrlEncodingError {
101 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
102 match self {
103 &FromUrlEncodingError::UriCharacterError {character, index} =>
104 write!(f, "invalid URI char [{}] at [{}]", character, index),
105 &FromUrlEncodingError::Utf8CharacterError {ref error} =>
106 write!(f, "invalid utf8 char: {}", error)
107 }
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use alloc::string::String;
114 use super::encode;
115 use super::decode;
116 use super::from_hex_digit;
117
118 #[test]
119 fn it_encodes_successfully() {
120 let expected = "this%20that";
121 assert_eq!(expected, encode("this that"));
122 }
123
124 #[test]
125 fn it_encodes_successfully_emoji() {
126 let emoji_string = "👾 Exterminate!";
127 let expected = "%F0%9F%91%BE%20Exterminate%21";
128 assert_eq!(expected, encode(emoji_string));
129 }
130
131 #[test]
132 fn it_decodes_successfully() {
133 let expected = String::from("this that");
134 let encoded = "this%20that";
135 assert_eq!(expected, decode(encoded).unwrap());
136 }
137
138 #[test]
139 fn it_decodes_successfully_emoji() {
140 let expected = String::from("👾 Exterminate!");
141 let encoded = "%F0%9F%91%BE%20Exterminate%21";
142 assert_eq!(expected, decode(encoded).unwrap());
143 }
144
145 #[test]
146 fn it_decodes_unsuccessfully_emoji() {
147 let bad_encoded_string = "👾 Exterminate!";
148
149 assert_eq!(bad_encoded_string, decode(bad_encoded_string).unwrap());
150 }
151
152
153 #[test]
154 fn misc() {
155 assert_eq!(3, from_hex_digit(b'3').unwrap());
156 assert_eq!(10, from_hex_digit(b'a').unwrap());
157 assert_eq!(15, from_hex_digit(b'F').unwrap());
158 assert_eq!(None, from_hex_digit(b'G'));
159 assert_eq!(None, from_hex_digit(9));
160
161 assert_eq!("pureascii", encode("pureascii"));
162 assert_eq!("pureascii", decode("pureascii").unwrap());
163 assert_eq!("", encode(""));
164 assert_eq!("", decode("").unwrap());
165 assert_eq!("%00", encode("\0"));
166 assert_eq!("\0", decode("\0").unwrap());
167 assert!(decode("%F0%0F%91%BE%20Hello%21").is_err());
168 assert_eq!("this%2that", decode("this%2that").unwrap());
169 assert_eq!("this that", decode("this%20that").unwrap());
170 assert_eq!("this that%", decode("this%20that%").unwrap());
171 assert_eq!("this that%2", decode("this%20that%2").unwrap());
172 }
173}