urlencoded/
lib.rs

1//! URL Encoded Plugin for Iron.
2//!
3//! Parses "url encoded" data from client requests.
4//! Capable of parsing both URL query strings and POST request bodies.
5
6extern crate iron;
7extern crate bodyparser;
8extern crate url;
9extern crate plugin;
10
11use iron::prelude::*;
12use iron::typemap::Key;
13
14use url::form_urlencoded;
15use std::collections::HashMap;
16use std::collections::hash_map::Entry::*;
17use std::fmt;
18use std::error::Error as StdError;
19
20/// Plugin for `Request` that extracts URL encoded data from the URL query string.
21///
22/// Use it like this: `req.get_ref::<UrlEncodedQuery>()`
23pub struct UrlEncodedQuery;
24
25/// Plugin for `Request` that extracts URL encoded data from the request body.
26///
27/// Use it like this: `req.get_ref::<UrlEncodedBody>()`
28pub struct UrlEncodedBody;
29
30/// An error representing the two possible errors that can occur during URL decoding.
31///
32/// The first and probably most common one is for the query to be empty,
33/// and that goes for both body and url queries.
34///
35/// The second type of error that can occur is that something goes wrong
36/// when parsing the request body.
37#[derive(Debug)]
38pub enum UrlDecodingError{
39    /// An error parsing the request body
40    BodyError(bodyparser::BodyError),
41    /// An empty query string, either in body or url query
42    EmptyQuery
43}
44
45pub use UrlDecodingError::*;
46
47impl fmt::Display for UrlDecodingError {
48    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
49        self.description().fmt(f)
50    }
51}
52
53impl StdError for UrlDecodingError {
54    fn description(&self) -> &str {
55        match *self {
56            BodyError(ref err) => err.description(),
57            EmptyQuery => "Expected query, found empty string"
58        }
59    }
60
61    fn cause(&self) -> Option<&StdError> {
62        match *self {
63            BodyError(ref err) => Some(err),
64            _ => None
65        }
66    }
67}
68
69/// Hashmap mapping strings to vectors of strings.
70pub type QueryMap = HashMap<String, Vec<String>>;
71/// Result type for decoding query parameters.
72pub type QueryResult = Result<QueryMap, UrlDecodingError>;
73
74impl Key for UrlEncodedBody {
75    type Value = QueryMap;
76}
77impl Key for UrlEncodedQuery {
78    type Value = QueryMap;
79}
80
81impl<'a, 'b> plugin::Plugin<Request<'a, 'b>> for UrlEncodedQuery {
82    type Error = UrlDecodingError;
83
84    fn eval(req: &mut Request) -> QueryResult {
85        match req.url.query() {
86            Some(ref query) => create_param_hashmap(&query),
87            None => Err(UrlDecodingError::EmptyQuery)
88        }
89    }
90}
91
92impl<'a, 'b> plugin::Plugin<Request<'a, 'b>> for UrlEncodedBody {
93    type Error = UrlDecodingError;
94
95    fn eval(req: &mut Request) -> QueryResult {
96        req.get::<bodyparser::Raw>()
97            .map(|x| x.unwrap_or("".to_string()))
98            .map_err(|e| UrlDecodingError::BodyError(e))
99            .and_then(|x| create_param_hashmap(&x))
100    }
101}
102
103/// Parse a urlencoded string into an optional HashMap.
104fn create_param_hashmap(data: &str) -> QueryResult {
105    match data {
106        "" => Err(UrlDecodingError::EmptyQuery),
107        _ => Ok(combine_duplicates(form_urlencoded::parse(data.as_bytes()).into_owned()))
108    }
109}
110
111/// Convert a list of (key, value) pairs into a hashmap with vector values.
112fn combine_duplicates<I: Iterator<Item=(String, String)>>(collection: I) -> QueryMap {
113    let mut deduplicated: QueryMap = HashMap::new();
114
115    for (k, v) in collection {
116        match deduplicated.entry(k) {
117            // Already a Vec here, push onto it
118            Occupied(entry) => { entry.into_mut().push(v); },
119
120            // No value, create a one-element Vec.
121            Vacant(entry) => { entry.insert(vec![v]); },
122        };
123    }
124
125    deduplicated
126}
127
128#[test]
129fn test_combine_duplicates() {
130    let my_vec = vec![("band".to_string(), "arctic monkeys".to_string()),
131                      ("band".to_string(), "temper trap".to_string()),
132                      ("color".to_string(),"green".to_string())];
133    let answer = combine_duplicates(my_vec.into_iter());
134    let mut control = HashMap::new();
135    control.insert("band".to_string(),
136                   vec!["arctic monkeys".to_string(), "temper trap".to_string()]);
137    control.insert("color".to_string(), vec!["green".to_string()]);
138    assert_eq!(answer, control);
139}