1use std::cell::{Ref, RefMut};
2use std::str;
3
4use encoding_rs::{Encoding, UTF_8};
5use mime::Mime;
6use ntex_http::header;
7
8#[cfg(feature = "cookie")]
9use coo_kie::Cookie;
10
11use super::error::{ContentTypeError, DecodeError};
12use super::header::HeaderMap;
13use crate::util::Extensions;
14
15#[cfg(feature = "cookie")]
16struct Cookies(Vec<Cookie<'static>>);
17
18pub trait HttpMessage: Sized {
20 fn message_headers(&self) -> &HeaderMap;
22
23 fn message_extensions(&self) -> Ref<'_, Extensions>;
25
26 fn message_extensions_mut(&self) -> RefMut<'_, Extensions>;
28
29 fn content_type(&self) -> &str {
34 if let Some(content_type) = self.message_headers().get(header::CONTENT_TYPE)
35 && let Ok(content_type) = content_type.to_str()
36 {
37 return content_type.split(';').next().unwrap().trim();
38 }
39 ""
40 }
41
42 fn encoding(&self) -> Result<&'static Encoding, ContentTypeError> {
51 if let Some(mime_type) = self.mime_type()? {
52 if let Some(charset) = mime_type.get_param("charset") {
53 if let Some(enc) = Encoding::for_label_no_replacement(charset.as_str().as_bytes()) {
54 Ok(enc)
55 } else {
56 Err(ContentTypeError::UnknownEncoding)
57 }
58 } else {
59 Ok(UTF_8)
60 }
61 } else {
62 Ok(UTF_8)
63 }
64 }
65
66 fn mime_type(&self) -> Result<Option<Mime>, ContentTypeError> {
73 if let Some(content_type) = self.message_headers().get(header::CONTENT_TYPE) {
74 if let Ok(content_type) = content_type.to_str() {
75 return match content_type.parse() {
76 Ok(mt) => Ok(Some(mt)),
77 Err(_) => Err(ContentTypeError::ParseError),
78 };
79 }
80 Err(ContentTypeError::ParseError)
81 } else {
82 Ok(None)
83 }
84 }
85
86 fn chunked(&self) -> Result<bool, DecodeError> {
93 if let Some(encodings) = self.message_headers().get(header::TRANSFER_ENCODING) {
94 if let Ok(s) = encodings.to_str() {
95 Ok(s.to_lowercase().contains("chunked"))
96 } else {
97 Err(DecodeError::Header)
98 }
99 } else {
100 Ok(false)
101 }
102 }
103
104 #[cfg(feature = "cookie")]
105 fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, coo_kie::ParseError> {
112 if self.message_extensions().get::<Cookies>().is_none() {
113 let mut cookies = Vec::new();
114 for hdr in self.message_headers().get_all(header::COOKIE) {
115 let s = str::from_utf8(hdr.as_bytes()).map_err(coo_kie::ParseError::from)?;
116 for cookie_str in s.split(';').map(str::trim) {
117 if !cookie_str.is_empty() {
118 cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned());
119 }
120 }
121 }
122 self.message_extensions_mut().insert(Cookies(cookies));
123 }
124 Ok(Ref::map(self.message_extensions(), |ext| {
125 &ext.get::<Cookies>().unwrap().0
126 }))
127 }
128
129 #[cfg(feature = "cookie")]
130 fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
132 if let Ok(cookies) = self.cookies() {
133 for cookie in cookies.iter() {
134 if cookie.name() == name {
135 return Some(cookie.to_owned());
136 }
137 }
138 }
139 None
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use encoding_rs::ISO_8859_2;
146
147 use super::*;
148 use crate::http::test::TestRequest;
149
150 #[test]
151 fn test_content_type() {
152 let req = TestRequest::with_header("content-type", "text/plain").build();
153 assert_eq!(req.content_type(), "text/plain");
154 let req =
155 TestRequest::with_header("content-type", "application/json; charset=utf=8").build();
156 assert_eq!(req.content_type(), "application/json");
157 let req = TestRequest::builder().build();
158 assert_eq!(req.content_type(), "");
159 }
160
161 #[test]
162 fn test_mime_type() {
163 let req = TestRequest::with_header("content-type", "application/json").build();
164 assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON));
165 let req = TestRequest::builder().build();
166 assert_eq!(req.mime_type().unwrap(), None);
167 let req =
168 TestRequest::with_header("content-type", "application/json; charset=utf-8").build();
169 let mt = req.mime_type().unwrap().unwrap();
170 assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8));
171 assert_eq!(mt.type_(), mime::APPLICATION);
172 assert_eq!(mt.subtype(), mime::JSON);
173 }
174
175 #[test]
176 fn test_mime_type_error() {
177 let req =
178 TestRequest::with_header("content-type", "applicationadfadsfasdflknadsfklnadsfjson")
179 .build();
180 assert_eq!(Err(ContentTypeError::ParseError), req.mime_type());
181 }
182
183 #[test]
184 fn test_encoding() {
185 let req = TestRequest::builder().build();
186 assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
187
188 let req = TestRequest::with_header("content-type", "application/json").build();
189 assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
190
191 let req = TestRequest::with_header("content-type", "application/json; charset=ISO-8859-2")
192 .build();
193 assert_eq!(ISO_8859_2, req.encoding().unwrap());
194 }
195
196 #[test]
197 fn test_encoding_error() {
198 let req = TestRequest::with_header("content-type", "applicatjson").build();
199 assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err());
200
201 let req =
202 TestRequest::with_header("content-type", "application/json; charset=kkkttktk").build();
203 assert_eq!(
204 Some(ContentTypeError::UnknownEncoding),
205 req.encoding().err()
206 );
207 }
208
209 #[test]
210 fn test_chunked() {
211 let req = TestRequest::builder().build();
212 assert!(!req.chunked().unwrap());
213
214 let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").build();
215 assert!(req.chunked().unwrap());
216
217 let req = TestRequest::builder()
218 .header(
219 header::TRANSFER_ENCODING,
220 b"some va\xadscc\xacas0xsdasdlue".as_ref(),
221 )
222 .build();
223 assert!(req.chunked().is_err());
224 }
225
226 #[test]
227 fn test_mime_type_not_utf8() {
228 let req = TestRequest::default()
229 .header(
230 header::CONTENT_TYPE,
231 header::HeaderValue::from_bytes(b"text/\xff").unwrap(),
232 )
233 .build();
234 assert_eq!(req.mime_type(), Err(ContentTypeError::ParseError));
235 }
236
237 #[cfg(feature = "cookie")]
238 #[test]
239 fn test_cookie_not_found() {
240 let req = TestRequest::default()
241 .header(header::COOKIE, "c1=v1; ")
242 .build();
243 assert_eq!(req.cookies().unwrap().len(), 1);
244 assert!(req.cookie("c2").is_none());
245 }
246}