actori_http/
httpmessage.rs

1use std::cell::{Ref, RefMut};
2use std::str;
3
4use encoding_rs::{Encoding, UTF_8};
5use http::header;
6use mime::Mime;
7
8use crate::cookie::Cookie;
9use crate::error::{ContentTypeError, CookieParseError, ParseError};
10use crate::extensions::Extensions;
11use crate::header::{Header, HeaderMap};
12use crate::payload::Payload;
13
14struct Cookies(Vec<Cookie<'static>>);
15
16/// Trait that implements general purpose operations on http messages
17pub trait HttpMessage: Sized {
18    /// Type of message payload stream
19    type Stream;
20
21    /// Read the message headers.
22    fn headers(&self) -> &HeaderMap;
23
24    /// Message payload stream
25    fn take_payload(&mut self) -> Payload<Self::Stream>;
26
27    /// Request's extensions container
28    fn extensions(&self) -> Ref<'_, Extensions>;
29
30    /// Mutable reference to a the request's extensions container
31    fn extensions_mut(&self) -> RefMut<'_, Extensions>;
32
33    #[doc(hidden)]
34    /// Get a header
35    fn get_header<H: Header>(&self) -> Option<H>
36    where
37        Self: Sized,
38    {
39        if self.headers().contains_key(H::name()) {
40            H::parse(self).ok()
41        } else {
42            None
43        }
44    }
45
46    /// Read the request content type. If request does not contain
47    /// *Content-Type* header, empty str get returned.
48    fn content_type(&self) -> &str {
49        if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
50            if let Ok(content_type) = content_type.to_str() {
51                return content_type.split(';').next().unwrap().trim();
52            }
53        }
54        ""
55    }
56
57    /// Get content type encoding
58    ///
59    /// UTF-8 is used by default, If request charset is not set.
60    fn encoding(&self) -> Result<&'static Encoding, ContentTypeError> {
61        if let Some(mime_type) = self.mime_type()? {
62            if let Some(charset) = mime_type.get_param("charset") {
63                if let Some(enc) =
64                    Encoding::for_label_no_replacement(charset.as_str().as_bytes())
65                {
66                    Ok(enc)
67                } else {
68                    Err(ContentTypeError::UnknownEncoding)
69                }
70            } else {
71                Ok(UTF_8)
72            }
73        } else {
74            Ok(UTF_8)
75        }
76    }
77
78    /// Convert the request content type to a known mime type.
79    fn mime_type(&self) -> Result<Option<Mime>, ContentTypeError> {
80        if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
81            if let Ok(content_type) = content_type.to_str() {
82                return match content_type.parse() {
83                    Ok(mt) => Ok(Some(mt)),
84                    Err(_) => Err(ContentTypeError::ParseError),
85                };
86            } else {
87                return Err(ContentTypeError::ParseError);
88            }
89        }
90        Ok(None)
91    }
92
93    /// Check if request has chunked transfer encoding
94    fn chunked(&self) -> Result<bool, ParseError> {
95        if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) {
96            if let Ok(s) = encodings.to_str() {
97                Ok(s.to_lowercase().contains("chunked"))
98            } else {
99                Err(ParseError::Header)
100            }
101        } else {
102            Ok(false)
103        }
104    }
105
106    /// Load request cookies.
107    #[inline]
108    fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
109        if self.extensions().get::<Cookies>().is_none() {
110            let mut cookies = Vec::new();
111            for hdr in self.headers().get_all(header::COOKIE) {
112                let s =
113                    str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
114                for cookie_str in s.split(';').map(|s| s.trim()) {
115                    if !cookie_str.is_empty() {
116                        cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned());
117                    }
118                }
119            }
120            self.extensions_mut().insert(Cookies(cookies));
121        }
122        Ok(Ref::map(self.extensions(), |ext| {
123            &ext.get::<Cookies>().unwrap().0
124        }))
125    }
126
127    /// Return request cookie.
128    fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
129        if let Ok(cookies) = self.cookies() {
130            for cookie in cookies.iter() {
131                if cookie.name() == name {
132                    return Some(cookie.to_owned());
133                }
134            }
135        }
136        None
137    }
138}
139
140impl<'a, T> HttpMessage for &'a mut T
141where
142    T: HttpMessage,
143{
144    type Stream = T::Stream;
145
146    fn headers(&self) -> &HeaderMap {
147        (**self).headers()
148    }
149
150    /// Message payload stream
151    fn take_payload(&mut self) -> Payload<Self::Stream> {
152        (**self).take_payload()
153    }
154
155    /// Request's extensions container
156    fn extensions(&self) -> Ref<'_, Extensions> {
157        (**self).extensions()
158    }
159
160    /// Mutable reference to a the request's extensions container
161    fn extensions_mut(&self) -> RefMut<'_, Extensions> {
162        (**self).extensions_mut()
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use bytes::Bytes;
169    use encoding_rs::ISO_8859_2;
170    use mime;
171
172    use super::*;
173    use crate::test::TestRequest;
174
175    #[test]
176    fn test_content_type() {
177        let req = TestRequest::with_header("content-type", "text/plain").finish();
178        assert_eq!(req.content_type(), "text/plain");
179        let req =
180            TestRequest::with_header("content-type", "application/json; charset=utf=8")
181                .finish();
182        assert_eq!(req.content_type(), "application/json");
183        let req = TestRequest::default().finish();
184        assert_eq!(req.content_type(), "");
185    }
186
187    #[test]
188    fn test_mime_type() {
189        let req = TestRequest::with_header("content-type", "application/json").finish();
190        assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON));
191        let req = TestRequest::default().finish();
192        assert_eq!(req.mime_type().unwrap(), None);
193        let req =
194            TestRequest::with_header("content-type", "application/json; charset=utf-8")
195                .finish();
196        let mt = req.mime_type().unwrap().unwrap();
197        assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8));
198        assert_eq!(mt.type_(), mime::APPLICATION);
199        assert_eq!(mt.subtype(), mime::JSON);
200    }
201
202    #[test]
203    fn test_mime_type_error() {
204        let req = TestRequest::with_header(
205            "content-type",
206            "applicationadfadsfasdflknadsfklnadsfjson",
207        )
208        .finish();
209        assert_eq!(Err(ContentTypeError::ParseError), req.mime_type());
210    }
211
212    #[test]
213    fn test_encoding() {
214        let req = TestRequest::default().finish();
215        assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
216
217        let req = TestRequest::with_header("content-type", "application/json").finish();
218        assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
219
220        let req = TestRequest::with_header(
221            "content-type",
222            "application/json; charset=ISO-8859-2",
223        )
224        .finish();
225        assert_eq!(ISO_8859_2, req.encoding().unwrap());
226    }
227
228    #[test]
229    fn test_encoding_error() {
230        let req = TestRequest::with_header("content-type", "applicatjson").finish();
231        assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err());
232
233        let req = TestRequest::with_header(
234            "content-type",
235            "application/json; charset=kkkttktk",
236        )
237        .finish();
238        assert_eq!(
239            Some(ContentTypeError::UnknownEncoding),
240            req.encoding().err()
241        );
242    }
243
244    #[test]
245    fn test_chunked() {
246        let req = TestRequest::default().finish();
247        assert!(!req.chunked().unwrap());
248
249        let req =
250            TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
251        assert!(req.chunked().unwrap());
252
253        let req = TestRequest::default()
254            .header(
255                header::TRANSFER_ENCODING,
256                Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"),
257            )
258            .finish();
259        assert!(req.chunked().is_err());
260    }
261}