actix-web 0.7.17

Actix web is a simple, pragmatic and extremely fast web framework for Rust.
Documentation
use std;
use std::ops::Index;
use std::path::PathBuf;
use std::rc::Rc;
use std::str::FromStr;

use http::StatusCode;
use smallvec::SmallVec;

use error::{InternalError, ResponseError, UriSegmentError};
use uri::{Url, RESERVED_QUOTER};

/// A trait to abstract the idea of creating a new instance of a type from a
/// path parameter.
pub trait FromParam: Sized {
    /// The associated error which can be returned from parsing.
    type Err: ResponseError;

    /// Parses a string `s` to return a value of this type.
    fn from_param(s: &str) -> Result<Self, Self::Err>;
}

#[derive(Debug, Clone)]
pub(crate) enum ParamItem {
    Static(&'static str),
    UrlSegment(u16, u16),
}

/// Route match information
///
/// If resource path contains variable patterns, `Params` stores this variables.
#[derive(Debug, Clone)]
pub struct Params {
    url: Url,
    pub(crate) tail: u16,
    pub(crate) segments: SmallVec<[(Rc<String>, ParamItem); 3]>,
}

impl Params {
    pub(crate) fn new() -> Params {
        Params {
            url: Url::default(),
            tail: 0,
            segments: SmallVec::new(),
        }
    }

    pub(crate) fn with_url(url: &Url) -> Params {
        Params {
            url: url.clone(),
            tail: 0,
            segments: SmallVec::new(),
        }
    }

    pub(crate) fn clear(&mut self) {
        self.segments.clear();
    }

    pub(crate) fn set_tail(&mut self, tail: u16) {
        self.tail = tail;
    }

    pub(crate) fn set_url(&mut self, url: Url) {
        self.url = url;
    }

    pub(crate) fn add(&mut self, name: Rc<String>, value: ParamItem) {
        self.segments.push((name, value));
    }

    pub(crate) fn add_static(&mut self, name: &str, value: &'static str) {
        self.segments
            .push((Rc::new(name.to_string()), ParamItem::Static(value)));
    }

    /// Check if there are any matched patterns
    pub fn is_empty(&self) -> bool {
        self.segments.is_empty()
    }

    /// Check number of extracted parameters
    pub fn len(&self) -> usize {
        self.segments.len()
    }

    /// Get matched parameter by name without type conversion
    pub fn get(&self, key: &str) -> Option<&str> {
        for item in self.segments.iter() {
            if key == item.0.as_str() {
                return match item.1 {
                    ParamItem::Static(ref s) => Some(&s),
                    ParamItem::UrlSegment(s, e) => {
                        Some(&self.url.path()[(s as usize)..(e as usize)])
                    }
                };
            }
        }
        if key == "tail" {
            Some(&self.url.path()[(self.tail as usize)..])
        } else {
            None
        }
    }

    /// Get URL-decoded matched parameter by name without type conversion
    pub fn get_decoded(&self, key: &str) -> Option<String> {
        self.get(key).map(|value| {
            if let Some(ref mut value) = RESERVED_QUOTER.requote(value.as_bytes()) {
                Rc::make_mut(value).to_string()
            } else {
                value.to_string()
            }
        })
    }

    /// Get unprocessed part of path
    pub fn unprocessed(&self) -> &str {
        &self.url.path()[(self.tail as usize)..]
    }

    /// Get matched `FromParam` compatible parameter by name.
    ///
    /// If keyed parameter is not available empty string is used as default
    /// value.
    ///
    /// ```rust
    /// # extern crate actix_web;
    /// # use actix_web::*;
    /// fn index(req: HttpRequest) -> Result<String> {
    ///     let ivalue: isize = req.match_info().query("val")?;
    ///     Ok(format!("isuze value: {:?}", ivalue))
    /// }
    /// # fn main() {}
    /// ```
    pub fn query<T: FromParam>(&self, key: &str) -> Result<T, <T as FromParam>::Err> {
        if let Some(s) = self.get(key) {
            T::from_param(s)
        } else {
            T::from_param("")
        }
    }

    /// Return iterator to items in parameter container
    pub fn iter(&self) -> ParamsIter {
        ParamsIter {
            idx: 0,
            params: self,
        }
    }
}

#[derive(Debug)]
pub struct ParamsIter<'a> {
    idx: usize,
    params: &'a Params,
}

impl<'a> Iterator for ParamsIter<'a> {
    type Item = (&'a str, &'a str);

    #[inline]
    fn next(&mut self) -> Option<(&'a str, &'a str)> {
        if self.idx < self.params.len() {
            let idx = self.idx;
            let res = match self.params.segments[idx].1 {
                ParamItem::Static(ref s) => &s,
                ParamItem::UrlSegment(s, e) => {
                    &self.params.url.path()[(s as usize)..(e as usize)]
                }
            };
            self.idx += 1;
            return Some((&self.params.segments[idx].0, res));
        }
        None
    }
}

impl<'a> Index<&'a str> for Params {
    type Output = str;

    fn index(&self, name: &'a str) -> &str {
        self.get(name)
            .expect("Value for parameter is not available")
    }
}

impl Index<usize> for Params {
    type Output = str;

    fn index(&self, idx: usize) -> &str {
        match self.segments[idx].1 {
            ParamItem::Static(ref s) => &s,
            ParamItem::UrlSegment(s, e) => &self.url.path()[(s as usize)..(e as usize)],
        }
    }
}

/// Creates a `PathBuf` from a path parameter. The returned `PathBuf` is
/// percent-decoded. If a segment is equal to "..", the previous segment (if
/// any) is skipped.
///
/// For security purposes, if a segment meets any of the following conditions,
/// an `Err` is returned indicating the condition met:
///
///   * Decoded segment starts with any of: `.` (except `..`), `*`
///   * Decoded segment ends with any of: `:`, `>`, `<`
///   * Decoded segment contains any of: `/`
///   * On Windows, decoded segment contains any of: '\'
///   * Percent-encoding results in invalid UTF8.
///
/// As a result of these conditions, a `PathBuf` parsed from request path
/// parameter is safe to interpolate within, or use as a suffix of, a path
/// without additional checks.
impl FromParam for PathBuf {
    type Err = UriSegmentError;

    fn from_param(val: &str) -> Result<PathBuf, UriSegmentError> {
        let mut buf = PathBuf::new();
        for segment in val.split('/') {
            if segment == ".." {
                buf.pop();
            } else if segment.starts_with('.') {
                return Err(UriSegmentError::BadStart('.'));
            } else if segment.starts_with('*') {
                return Err(UriSegmentError::BadStart('*'));
            } else if segment.ends_with(':') {
                return Err(UriSegmentError::BadEnd(':'));
            } else if segment.ends_with('>') {
                return Err(UriSegmentError::BadEnd('>'));
            } else if segment.ends_with('<') {
                return Err(UriSegmentError::BadEnd('<'));
            } else if segment.is_empty() {
                continue;
            } else if cfg!(windows) && segment.contains('\\') {
                return Err(UriSegmentError::BadChar('\\'));
            } else {
                buf.push(segment)
            }
        }

        Ok(buf)
    }
}

macro_rules! FROM_STR {
    ($type:ty) => {
        impl FromParam for $type {
            type Err = InternalError<<$type as FromStr>::Err>;
            fn from_param(val: &str) -> Result<Self, Self::Err> {
                <$type as FromStr>::from_str(val)
                    .map_err(|e| InternalError::new(e, StatusCode::BAD_REQUEST))
            }
        }
    };
}

FROM_STR!(u8);
FROM_STR!(u16);
FROM_STR!(u32);
FROM_STR!(u64);
FROM_STR!(usize);
FROM_STR!(i8);
FROM_STR!(i16);
FROM_STR!(i32);
FROM_STR!(i64);
FROM_STR!(isize);
FROM_STR!(f32);
FROM_STR!(f64);
FROM_STR!(String);
FROM_STR!(std::net::IpAddr);
FROM_STR!(std::net::Ipv4Addr);
FROM_STR!(std::net::Ipv6Addr);
FROM_STR!(std::net::SocketAddr);
FROM_STR!(std::net::SocketAddrV4);
FROM_STR!(std::net::SocketAddrV6);

#[cfg(test)]
mod tests {
    use super::*;
    use std::iter::FromIterator;

    #[test]
    fn test_path_buf() {
        assert_eq!(
            PathBuf::from_param("/test/.tt"),
            Err(UriSegmentError::BadStart('.'))
        );
        assert_eq!(
            PathBuf::from_param("/test/*tt"),
            Err(UriSegmentError::BadStart('*'))
        );
        assert_eq!(
            PathBuf::from_param("/test/tt:"),
            Err(UriSegmentError::BadEnd(':'))
        );
        assert_eq!(
            PathBuf::from_param("/test/tt<"),
            Err(UriSegmentError::BadEnd('<'))
        );
        assert_eq!(
            PathBuf::from_param("/test/tt>"),
            Err(UriSegmentError::BadEnd('>'))
        );
        assert_eq!(
            PathBuf::from_param("/seg1/seg2/"),
            Ok(PathBuf::from_iter(vec!["seg1", "seg2"]))
        );
        assert_eq!(
            PathBuf::from_param("/seg1/../seg2/"),
            Ok(PathBuf::from_iter(vec!["seg2"]))
        );
    }

    #[test]
    fn test_get_param_by_name() {
        let mut params = Params::new();
        params.add_static("item1", "path");
        params.add_static("item2", "http%3A%2F%2Flocalhost%3A80%2Ffoo");

        assert_eq!(params.get("item0"), None);
        assert_eq!(params.get_decoded("item0"), None);
        assert_eq!(params.get("item1"), Some("path"));
        assert_eq!(params.get_decoded("item1"), Some("path".to_string()));
        assert_eq!(
            params.get("item2"),
            Some("http%3A%2F%2Flocalhost%3A80%2Ffoo")
        );
        assert_eq!(
            params.get_decoded("item2"),
            Some("http://localhost:80/foo".to_string())
        );
    }
}