scrappy_router/
url.rs

1use crate::ResourcePath;
2
3#[allow(dead_code)]
4const GEN_DELIMS: &[u8] = b":/?#[]@";
5#[allow(dead_code)]
6const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,";
7#[allow(dead_code)]
8const SUB_DELIMS: &[u8] = b"!$'()*,+?=;";
9#[allow(dead_code)]
10const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;";
11#[allow(dead_code)]
12const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
13    ABCDEFGHIJKLMNOPQRSTUVWXYZ
14    1234567890
15    -._~";
16const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
17    ABCDEFGHIJKLMNOPQRSTUVWXYZ
18    1234567890
19    -._~
20    !$'()*,";
21const QS: &[u8] = b"+&=;b";
22
23#[inline]
24fn bit_at(array: &[u8], ch: u8) -> bool {
25    array[(ch >> 3) as usize] & (1 << (ch & 7)) != 0
26}
27
28#[inline]
29fn set_bit(array: &mut [u8], ch: u8) {
30    array[(ch >> 3) as usize] |= 1 << (ch & 7)
31}
32
33thread_local! {
34    static DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") };
35}
36
37#[derive(Default, Clone, Debug)]
38pub struct Url {
39    uri: http::Uri,
40    path: Option<String>,
41}
42
43impl Url {
44    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
50    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
57    pub fn uri(&self) -> &http::Uri {
58        &self.uri
59    }
60
61    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]
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]
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
82impl ResourcePath for Url {
83    #[inline]
84    fn path(&self) -> &str {
85        self.path()
86    }
87}
88
89pub struct Quoter {
90    safe_table: [u8; 16],
91    protected_table: [u8; 16],
92}
93
94impl Quoter {
95    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
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]
184fn 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]
197fn 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)]
202mod tests {
203    use http::Uri;
204    use std::convert::TryFrom;
205
206    use super::*;
207    use crate::{Path, ResourceDef};
208
209    #[test]
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}