1#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
33pub struct HttpUri<'a> {
34 pub scheme: HttpScheme,
39
40 pub authority: &'a str,
45
46 pub resource: HttpResource<'a>,
48}
49
50impl<'a> HttpUri<'a> {
51 pub fn new(s: &'a str) -> Result<Self, ()> {
56 let (scheme, rest) = match s.find("://") {
57 Some(idx) => s.split_at(idx),
58 None => return Err(()),
59 };
60
61 let scheme = scheme.parse()?;
62 let rest = &rest[3..];
63
64 let (authority, rest) = match rest.find('/') {
65 Some(idx) => rest.split_at(idx),
66 None => (rest, ""),
67 };
68
69 if authority.is_empty() {
70 return Err(());
71 }
72
73 Ok(HttpUri {
74 scheme: scheme,
75 authority: authority,
76 resource: HttpResource::new(rest),
77 })
78 }
79
80}
81
82impl<'a> std::fmt::Display for HttpUri<'a> {
84 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
85 write!(f, "{}://{}{}", self.scheme, self.authority, self.resource)
86 }
87}
88
89#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
91pub struct HttpResource<'a> {
92 pub path: &'a str,
96
97 pub query: Option<&'a str>,
99
100 pub fragment: Option<&'a str>,
102}
103
104impl<'a> HttpResource<'a> {
105 pub fn new(s: &'a str) -> Self {
107 let (path, query, fragment) = parts(s, s.find('?'), s.find('#'));
108
109 HttpResource {
110 path: if path.is_empty() {
111 "/"
112 } else {
113 path
114 },
115 query: if query.is_empty() {
116 None
117 } else {
118 Some(query)
119 },
120 fragment: if fragment.is_empty() {
121 None
122 } else {
123 Some(fragment)
124 }
125 }
126 }
127}
128
129impl<'a> std::fmt::Display for HttpResource<'a> {
130 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
131 try!(fmt.write_str(self.path));
132
133 if let Some(q) = self.query {
134 try!(write!(fmt, "?{}", q));
135 }
136
137 if let Some(f) = self.fragment {
138 try!(write!(fmt, "#{}", f));
139 }
140
141 Ok(())
142 }
143}
144
145fn parts<'a>(s: &'a str, qidx: Option<usize>, fidx: Option<usize>)
147 -> (&'a str, &'a str, &'a str)
148{
149 match (qidx, fidx) {
150 (Some(q), Some(f)) => if q < f {
151 let (path, query) = (&s[..f]).split_at(q);
152 let (_, frag) = s.split_at(f);
153
154 (path, &query[1..], &frag[1..])
155 } else {
156 parts(s, None, Some(f))
157 },
158 (Some(q), None) => {
159 let (path, query) = s.split_at(q);
160 (path, &query[1..], "")
161 },
162 (None, Some(f)) => {
163 let (path, frag) = s.split_at(f);
164 (path, "", &frag[1..])
165 },
166 (None, None) => {
167 (s, "", "")
168 },
169 }
170}
171
172#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
174pub enum HttpScheme {
175 Http,
177 Https,
179}
180
181impl std::str::FromStr for HttpScheme {
182 type Err = ();
183
184 fn from_str(s: &str) -> Result<Self, Self::Err> {
185 match s {
186 "http" => Ok(HttpScheme::Http),
187 "https" => Ok(HttpScheme::Https),
188 _ => Err(()),
189 }
190 }
191}
192
193impl std::fmt::Display for HttpScheme {
194 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
195 f.write_str(match *self {
196 HttpScheme::Http => "http",
197 HttpScheme::Https => "https",
198 })
199 }
200}
201
202#[cfg(test)]
203mod test {
204 use super::*;
205
206 #[test]
207 fn test_http_resource() {
208 assert_eq!(HttpResource::new("/a/b/c"),
209 HttpResource {
210 path: "/a/b/c",
211 query: None,
212 fragment: None,
213 });
214
215 assert_eq!(HttpResource::new("/a/b/c?key=val"),
216 HttpResource {
217 path: "/a/b/c",
218 query: Some("key=val"),
219 fragment: None,
220 });
221
222 assert_eq!(HttpResource::new("/a/b/c#frag"),
223 HttpResource {
224 path: "/a/b/c",
225 query: None,
226 fragment: Some("frag"),
227 });
228
229 assert_eq!(HttpResource::new("/a/b/c#frag?frag-param"),
230 HttpResource {
231 path: "/a/b/c",
232 query: None,
233 fragment: Some("frag?frag-param"),
234 });
235
236 assert_eq!(HttpResource::new("/a/b/c?key=val¶m#frag"),
237 HttpResource {
238 path: "/a/b/c",
239 query: Some("key=val¶m"),
240 fragment: Some("frag"),
241 });
242
243 assert_eq!(HttpResource::new("/a/b/c/?key=val¶m#frag"),
244 HttpResource {
245 path: "/a/b/c/",
246 query: Some("key=val¶m"),
247 fragment: Some("frag"),
248 });
249
250 assert_eq!(HttpResource::new("/a/b/c?key=d/e#frag/ment?param"),
251 HttpResource {
252 path: "/a/b/c",
253 query: Some("key=d/e"),
254 fragment: Some("frag/ment?param"),
255 });
256
257 assert_eq!(HttpResource::new("/a/b/c#frag?param&key=val"),
258 HttpResource {
259 path: "/a/b/c",
260 query: None,
261 fragment: Some("frag?param&key=val"),
262 });
263
264 assert_eq!(HttpResource::new("/%02/%03/%04#frag"),
265 HttpResource {
266 path: "/%02/%03/%04",
267 query: None,
268 fragment: Some("frag"),
269 });
270
271 assert_eq!(HttpResource::new("/"),
272 HttpResource {
273 path: "/",
274 query: None,
275 fragment: None,
276 });
277
278 assert_eq!(HttpResource::new(""),
279 HttpResource {
280 path: "/",
281 query: None,
282 fragment: None,
283 });
284
285 assert_eq!(HttpResource::new("?#"),
286 HttpResource {
287 path: "/",
288 query: None,
289 fragment: None,
290 });
291
292 assert_eq!(HttpResource::new("?key=val#"),
293 HttpResource {
294 path: "/",
295 query: Some("key=val"),
296 fragment: None,
297 });
298
299 assert_eq!(HttpResource::new("?#frag"),
300 HttpResource {
301 path: "/",
302 query: None,
303 fragment: Some("frag"),
304 });
305 }
306
307 #[test]
308 fn test_http_uri() {
309 assert_eq!(HttpUri::new("http://example.com").unwrap(),
310 HttpUri {
311 scheme: HttpScheme::Http,
312 authority: "example.com",
313 resource: HttpResource {
314 path: "/",
315 query: None,
316 fragment: None,
317 }
318 });
319
320 assert_eq!(HttpUri::new("http://127.0.0.1:61761/chunks").unwrap(),
321 HttpUri {
322 scheme: HttpScheme::Http,
323 authority: "127.0.0.1:61761",
324 resource: HttpResource {
325 path: "/chunks",
326 query: None,
327 fragment: None,
328 }
329 });
330
331 assert_eq!(HttpUri::new("https://127.0.0.1:61761").unwrap(),
332 HttpUri {
333 scheme: HttpScheme::Https,
334 authority: "127.0.0.1:61761",
335 resource: HttpResource {
336 path: "/",
337 query: None,
338 fragment: None,
339 }
340 });
341
342 assert!(HttpUri::new("http://").is_err());
343 assert!(HttpUri::new("http:///").is_err());
344 assert!(HttpUri::new("://example.com").is_err());
345 assert!(HttpUri::new("ftp://example.com").is_err());
346 assert!(HttpUri::new("file:example").is_err());
347 assert!(HttpUri::new("htt:p//host").is_err());
348 assert!(HttpUri::new("hyper.rs/").is_err());
349 assert!(HttpUri::new("hyper.rs?key=val").is_err());
350
351 assert_eq!(HttpUri::new("http://test.com/nazghul?test=3").unwrap(),
352 HttpUri {
353 scheme: HttpScheme::Http,
354 authority: "test.com",
355 resource: HttpResource {
356 path: "/nazghul",
357 query: Some("test=3"),
358 fragment: None,
359 }
360 });
361 }
362}