Skip to main content

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.rsplit(',')
97                    .next()
98                    .map(|token| token.trim().eq_ignore_ascii_case("chunked"))
99                    .unwrap_or(false))
100            } else {
101                Err(ParseError::Header)
102            }
103        } else {
104            Ok(false)
105        }
106    }
107}
108
109impl<T> HttpMessage for &mut T
110where
111    T: HttpMessage,
112{
113    type Stream = T::Stream;
114
115    fn headers(&self) -> &HeaderMap {
116        (**self).headers()
117    }
118
119    /// Message payload stream
120    fn take_payload(&mut self) -> Payload<Self::Stream> {
121        (**self).take_payload()
122    }
123
124    /// Request's extensions container
125    fn extensions(&self) -> Ref<'_, Extensions> {
126        (**self).extensions()
127    }
128
129    /// Mutable reference to a the request's extensions container
130    fn extensions_mut(&self) -> RefMut<'_, Extensions> {
131        (**self).extensions_mut()
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use bytes::Bytes;
138    use encoding_rs::ISO_8859_2;
139
140    use super::*;
141    use crate::test::TestRequest;
142
143    #[test]
144    fn test_content_type() {
145        let req = TestRequest::default()
146            .insert_header(("content-type", "text/plain"))
147            .finish();
148        assert_eq!(req.content_type(), "text/plain");
149        let req = TestRequest::default()
150            .insert_header(("content-type", "application/json; charset=utf-8"))
151            .finish();
152        assert_eq!(req.content_type(), "application/json");
153        let req = TestRequest::default().finish();
154        assert_eq!(req.content_type(), "");
155    }
156
157    #[test]
158    fn test_mime_type() {
159        let req = TestRequest::default()
160            .insert_header(("content-type", "application/json"))
161            .finish();
162        assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON));
163        let req = TestRequest::default().finish();
164        assert_eq!(req.mime_type().unwrap(), None);
165        let req = TestRequest::default()
166            .insert_header(("content-type", "application/json; charset=utf-8"))
167            .finish();
168        let mt = req.mime_type().unwrap().unwrap();
169        assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8));
170        assert_eq!(mt.type_(), mime::APPLICATION);
171        assert_eq!(mt.subtype(), mime::JSON);
172    }
173
174    #[test]
175    fn test_mime_type_error() {
176        let req = TestRequest::default()
177            .insert_header(("content-type", "applicationadfadsfasdflknadsfklnadsfjson"))
178            .finish();
179        assert_eq!(Err(ContentTypeError::ParseError), req.mime_type());
180    }
181
182    #[test]
183    fn test_encoding() {
184        let req = TestRequest::default().finish();
185        assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
186
187        let req = TestRequest::default()
188            .insert_header(("content-type", "application/json"))
189            .finish();
190        assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
191
192        let req = TestRequest::default()
193            .insert_header(("content-type", "application/json; charset=ISO-8859-2"))
194            .finish();
195        assert_eq!(ISO_8859_2, req.encoding().unwrap());
196    }
197
198    #[test]
199    fn test_encoding_error() {
200        let req = TestRequest::default()
201            .insert_header(("content-type", "applicatjson"))
202            .finish();
203        assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err());
204
205        let req = TestRequest::default()
206            .insert_header(("content-type", "application/json; charset=kkkttktk"))
207            .finish();
208        assert_eq!(
209            Some(ContentTypeError::UnknownEncoding),
210            req.encoding().err()
211        );
212    }
213
214    #[test]
215    fn test_chunked() {
216        let req = TestRequest::default().finish();
217        assert!(!req.chunked().unwrap());
218
219        let req = TestRequest::default()
220            .insert_header((header::TRANSFER_ENCODING, "chunked"))
221            .finish();
222        assert!(req.chunked().unwrap());
223
224        let req = TestRequest::default()
225            .insert_header((header::TRANSFER_ENCODING, "gzip, chunked"))
226            .finish();
227        assert!(req.chunked().unwrap());
228
229        let req = TestRequest::default()
230            .insert_header((header::TRANSFER_ENCODING, "chunked, gzip"))
231            .finish();
232        assert!(!req.chunked().unwrap());
233
234        let req = TestRequest::default()
235            .insert_header((header::TRANSFER_ENCODING, "not-chunked"))
236            .finish();
237        assert!(!req.chunked().unwrap());
238
239        let req = TestRequest::default()
240            .insert_header((header::TRANSFER_ENCODING, "chunked-fake"))
241            .finish();
242        assert!(!req.chunked().unwrap());
243
244        let req = TestRequest::default()
245            .insert_header((
246                header::TRANSFER_ENCODING,
247                Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"),
248            ))
249            .finish();
250        assert!(req.chunked().is_err());
251    }
252}