actix_http/
http_message.rs

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