1 use crate::ResourcePath;
2 
3 #[allow(dead_code)]
4 const GEN_DELIMS: &[u8] = b":/?#[]@";
5 #[allow(dead_code)]
6 const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,";
7 #[allow(dead_code)]
8 const SUB_DELIMS: &[u8] = b"!$'()*,+?=;";
9 #[allow(dead_code)]
10 const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;";
11 #[allow(dead_code)]
12 const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
13     ABCDEFGHIJKLMNOPQRSTUVWXYZ
14     1234567890
15     -._~";
16 const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
17     ABCDEFGHIJKLMNOPQRSTUVWXYZ
18     1234567890
19     -._~
20     !$'()*,";
21 const QS: &[u8] = b"+&=;b";
22 
23 #[inline]
bit_at(array: &[u8], ch: u8) -> bool24 fn bit_at(array: &[u8], ch: u8) -> bool {
25     array[(ch >> 3) as usize] & (1 << (ch & 7)) != 0
26 }
27 
28 #[inline]
set_bit(array: &mut [u8], ch: u8)29 fn set_bit(array: &mut [u8], ch: u8) {
30     array[(ch >> 3) as usize] |= 1 << (ch & 7)
31 }
32 
33 thread_local! {
34     static DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") };
35 }
36 
37 #[derive(Default, Clone, Debug)]
38 pub struct Url {
39     uri: http::Uri,
40     path: Option<String>,
41 }
42 
43 impl Url {
new(uri: http::Uri) -> Url44     pub fn new(uri: http::Uri) -> Url {
45         let path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes()));
46 
47         Url { uri, path }
48     }
49 
with_quoter(uri: http::Uri, quoter: &Quoter) -> Url50     pub fn with_quoter(uri: http::Uri, quoter: &Quoter) -> Url {
51         Url {
52             path: quoter.requote(uri.path().as_bytes()),
53             uri,
54         }
55     }
56 
uri(&self) -> &http::Uri57     pub fn uri(&self) -> &http::Uri {
58         &self.uri
59     }
60 
path(&self) -> &str61     pub fn path(&self) -> &str {
62         if let Some(ref s) = self.path {
63             s
64         } else {
65             self.uri.path()
66         }
67     }
68 
69     #[inline]
update(&mut self, uri: &http::Uri)70     pub fn update(&mut self, uri: &http::Uri) {
71         self.uri = uri.clone();
72         self.path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes()));
73     }
74 
75     #[inline]
update_with_quoter(&mut self, uri: &http::Uri, quoter: &Quoter)76     pub fn update_with_quoter(&mut self, uri: &http::Uri, quoter: &Quoter) {
77         self.uri = uri.clone();
78         self.path = quoter.requote(uri.path().as_bytes());
79     }
80 }
81 
82 impl ResourcePath for Url {
83     #[inline]
path(&self) -> &str84     fn path(&self) -> &str {
85         self.path()
86     }
87 }
88 
89 pub struct Quoter {
90     safe_table: [u8; 16],
91     protected_table: [u8; 16],
92 }
93 
94 impl Quoter {
new(safe: &[u8], protected: &[u8]) -> Quoter95     pub fn new(safe: &[u8], protected: &[u8]) -> Quoter {
96         let mut q = Quoter {
97             safe_table: [0; 16],
98             protected_table: [0; 16],
99         };
100 
101         // prepare safe table
102         for i in 0..128 {
103             if ALLOWED.contains(&i) {
104                 set_bit(&mut q.safe_table, i);
105             }
106             if QS.contains(&i) {
107                 set_bit(&mut q.safe_table, i);
108             }
109         }
110 
111         for ch in safe {
112             set_bit(&mut q.safe_table, *ch)
113         }
114 
115         // prepare protected table
116         for ch in protected {
117             set_bit(&mut q.safe_table, *ch);
118             set_bit(&mut q.protected_table, *ch);
119         }
120 
121         q
122     }
123 
requote(&self, val: &[u8]) -> Option<String>124     pub fn requote(&self, val: &[u8]) -> Option<String> {
125         let mut has_pct = 0;
126         let mut pct = [b'%', 0, 0];
127         let mut idx = 0;
128         let mut cloned: Option<Vec<u8>> = None;
129 
130         let len = val.len();
131         while idx < len {
132             let ch = val[idx];
133 
134             if has_pct != 0 {
135                 pct[has_pct] = val[idx];
136                 has_pct += 1;
137                 if has_pct == 3 {
138                     has_pct = 0;
139                     let buf = cloned.as_mut().unwrap();
140 
141                     if let Some(ch) = restore_ch(pct[1], pct[2]) {
142                         if ch < 128 {
143                             if bit_at(&self.protected_table, ch) {
144                                 buf.extend_from_slice(&pct);
145                                 idx += 1;
146                                 continue;
147                             }
148 
149                             if bit_at(&self.safe_table, ch) {
150                                 buf.push(ch);
151                                 idx += 1;
152                                 continue;
153                             }
154                         }
155                         buf.push(ch);
156                     } else {
157                         buf.extend_from_slice(&pct[..]);
158                     }
159                 }
160             } else if ch == b'%' {
161                 has_pct = 1;
162                 if cloned.is_none() {
163                     let mut c = Vec::with_capacity(len);
164                     c.extend_from_slice(&val[..idx]);
165                     cloned = Some(c);
166                 }
167             } else if let Some(ref mut cloned) = cloned {
168                 cloned.push(ch)
169             }
170             idx += 1;
171         }
172 
173         if let Some(data) = cloned {
174             // Unsafe: we get data from http::Uri, which does utf-8 checks already
175             // this code only decodes valid pct encoded values
176             Some(unsafe { String::from_utf8_unchecked(data) })
177         } else {
178             None
179         }
180     }
181 }
182 
183 #[inline]
from_hex(v: u8) -> Option<u8>184 fn from_hex(v: u8) -> Option<u8> {
185     if v >= b'0' && v <= b'9' {
186         Some(v - 0x30) // ord('0') == 0x30
187     } else if v >= b'A' && v <= b'F' {
188         Some(v - 0x41 + 10) // ord('A') == 0x41
189     } else if v > b'a' && v <= b'f' {
190         Some(v - 0x61 + 10) // ord('a') == 0x61
191     } else {
192         None
193     }
194 }
195 
196 #[inline]
restore_ch(d1: u8, d2: u8) -> Option<u8>197 fn restore_ch(d1: u8, d2: u8) -> Option<u8> {
198     from_hex(d1).and_then(|d1| from_hex(d2).map(move |d2| d1 << 4 | d2))
199 }
200 
201 #[cfg(test)]
202 mod tests {
203     use http::Uri;
204     use std::convert::TryFrom;
205 
206     use super::*;
207     use crate::{Path, ResourceDef};
208 
209     #[test]
test_parse_url()210     fn test_parse_url() {
211         let re = ResourceDef::new("/user/{id}/test");
212 
213         let url = Uri::try_from("/user/2345/test").unwrap();
214         let mut path = Path::new(Url::new(url));
215         assert!(re.match_path(&mut path));
216         assert_eq!(path.get("id").unwrap(), "2345");
217 
218         let url = Uri::try_from("/user/qwe%25/test").unwrap();
219         let mut path = Path::new(Url::new(url));
220         assert!(re.match_path(&mut path));
221         assert_eq!(path.get("id").unwrap(), "qwe%");
222 
223         let url = Uri::try_from("/user/qwe%25rty/test").unwrap();
224         let mut path = Path::new(Url::new(url));
225         assert!(re.match_path(&mut path));
226         assert_eq!(path.get("id").unwrap(), "qwe%rty");
227     }
228 }
229