1 // Copyright (c) 2023 Huawei Device Co., Ltd.
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 //     http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 use core::str;
15 use std::path::Path;
16 
17 use crate::error::{ErrorKind, HttpError};
18 
19 /// A type that defines the general structure of the `MIME` media typing system.
20 ///
21 /// A `MIME` type most-commonly consists of just two parts:
22 ///
23 /// - `Type`
24 /// - `Subtype`
25 ///
26 /// `Type` and `SubType` are separated by a slash (/) — with no whitespace
27 /// between:
28 ///
29 /// ```type/subtype```
30 ///
31 /// It is case-insensitive but are traditionally written in lowercase, such as:
32 /// ```application/octet-stream```.
33 ///
34 /// # Examples
35 ///
36 /// ```
37 /// use ylong_http::body::MimeType;
38 ///
39 /// let mime_type = MimeType::from_bytes(b"application/octet-stream").unwrap();
40 /// assert!(mime_type.is_application());
41 /// ```
42 #[derive(Debug, Eq, PartialEq)]
43 pub struct MimeType<'a> {
44     tag: MimeTypeTag,
45     bytes: &'a [u8],
46     // Index of '/'.
47     slash: usize,
48 }
49 
50 impl<'a> MimeType<'a> {
51     /// Creates a `MimeType` from a bytes slice.
52     ///
53     /// # Examples
54     ///
55     /// ```
56     /// use ylong_http::body::MimeType;
57     ///
58     /// let mime_type = MimeType::from_bytes(b"application/octet-stream").unwrap();
59     /// assert_eq!(mime_type.main_type(), "application");
60     /// assert_eq!(mime_type.sub_type(), "octet-stream");
61     /// ```
from_bytes(bytes: &'a [u8]) -> Result<Self, HttpError>62     pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, HttpError> {
63         // From [RFC6838](http://tools.ietf.org/html/rfc6838#section-4.2):
64         // <type-name> and <subtype-name> SHOULD be limited to 64 characters.
65         // Both top-level type and subtype names are case-insensitive.
66 
67         let (slash, _) = bytes
68             .iter()
69             .enumerate()
70             .find(|(_, &b)| b == b'/')
71             .ok_or_else(|| HttpError::from(ErrorKind::InvalidInput))?;
72 
73         let tag = MimeTypeTag::from_bytes(&bytes[..slash])?;
74 
75         let sub_type = &bytes[slash + 1..];
76         if sub_type.len() > 64 || !is_valid(sub_type) {
77             return Err(ErrorKind::InvalidInput.into());
78         }
79 
80         Ok(MimeType { tag, bytes, slash })
81     }
82 
83     /// Creates a new `MimeType` from a file path. The extension of the file
84     /// will be used to create it.
85     ///
86     /// # Examples
87     ///
88     /// ```
89     /// use std::path::Path;
90     ///
91     /// use ylong_http::body::MimeType;
92     ///
93     /// let path = Path::new("./foo/bar.pdf");
94     /// let mime_type = MimeType::from_path(path).unwrap();
95     /// assert_eq!(mime_type.main_type(), "application");
96     /// assert_eq!(mime_type.sub_type(), "pdf");
97     /// ```
from_path(path: &'a Path) -> Result<Self, HttpError>98     pub fn from_path(path: &'a Path) -> Result<Self, HttpError> {
99         let str = path
100             .extension()
101             .and_then(|ext| ext.to_str())
102             .ok_or_else(|| HttpError::from(ErrorKind::InvalidInput))?;
103         Self::from_extension(str)
104     }
105 
106     /// Returns a `&str` which represents the `MimeType`.
107     ///
108     /// # Examples
109     ///
110     /// ```
111     /// use ylong_http::body::MimeType;
112     ///
113     /// let mime_type = MimeType::from_bytes(b"application/pdf").unwrap();
114     /// assert_eq!(mime_type.as_str(), "application/pdf");
115     /// ```
as_str(&self) -> &str116     pub fn as_str(&self) -> &str {
117         // Safety: The input byte slice is checked, so it can be directly
118         // converted to `&str` here.
119         unsafe { str::from_utf8_unchecked(self.bytes) }
120     }
121 
122     /// Returns main type string, such as `text` of `text/plain`.
123     ///
124     /// # Examples
125     ///
126     /// ```
127     /// use ylong_http::body::MimeType;
128     ///
129     /// let mime_type = MimeType::from_bytes(b"application/pdf").unwrap();
130     /// assert_eq!(mime_type.main_type(), "application");
131     /// ```
main_type(&self) -> &str132     pub fn main_type(&self) -> &str {
133         // Safety: The input byte slice is checked, so it can be directly
134         // converted to `&str` here.
135         unsafe { str::from_utf8_unchecked(&self.bytes[..self.slash]) }
136     }
137 
138     /// Returns sub type string, such as `plain` of `text/plain`.
139     ///
140     /// # Examples
141     ///
142     /// ```
143     /// use ylong_http::body::MimeType;
144     ///
145     /// let mime_type = MimeType::from_bytes(b"application/pdf").unwrap();
146     /// assert_eq!(mime_type.sub_type(), "pdf");
147     /// ```
sub_type(&self) -> &str148     pub fn sub_type(&self) -> &str {
149         // Safety: The input byte slice is checked, so it can be directly
150         // converted to `&str` here.
151         unsafe { str::from_utf8_unchecked(&self.bytes[self.slash + 1..]) }
152     }
153 
154     /// Checks whether the main type is `application`.
155     ///
156     /// # Examples
157     ///
158     /// ```
159     /// use ylong_http::body::MimeType;
160     ///
161     /// let mime_type = MimeType::from_bytes(b"application/pdf").unwrap();
162     /// assert!(mime_type.is_application());
163     /// ```
is_application(&self) -> bool164     pub fn is_application(&self) -> bool {
165         matches!(self.tag, MimeTypeTag::Application)
166     }
167 
168     /// Checks whether the main type is `audio`.
169     ///
170     /// # Examples
171     ///
172     /// ```
173     /// use ylong_http::body::MimeType;
174     ///
175     /// let mime_type = MimeType::from_bytes(b"audio/basic").unwrap();
176     /// assert!(mime_type.is_audio());
177     /// ```
is_audio(&self) -> bool178     pub fn is_audio(&self) -> bool {
179         matches!(self.tag, MimeTypeTag::Audio)
180     }
181 
182     /// Checks whether the main type is `font`.
183     ///
184     /// # Examples
185     ///
186     /// ```
187     /// use ylong_http::body::MimeType;
188     ///
189     /// let mime_type = MimeType::from_bytes(b"font/collection").unwrap();
190     /// assert!(mime_type.is_font());
191     /// ```
is_font(&self) -> bool192     pub fn is_font(&self) -> bool {
193         matches!(self.tag, MimeTypeTag::Font)
194     }
195 
196     /// Checks whether the main type is `image`.
197     ///
198     /// # Examples
199     ///
200     /// ```
201     /// use ylong_http::body::MimeType;
202     ///
203     /// let mime_type = MimeType::from_bytes(b"image/gif").unwrap();
204     /// assert!(mime_type.is_image());
205     /// ```
is_image(&self) -> bool206     pub fn is_image(&self) -> bool {
207         matches!(self.tag, MimeTypeTag::Image)
208     }
209 
210     /// Checks whether the main type is `message`.
211     ///
212     /// # Examples
213     ///
214     /// ```
215     /// use ylong_http::body::MimeType;
216     ///
217     /// let mime_type = MimeType::from_bytes(b"message/rfc822").unwrap();
218     /// assert!(mime_type.is_message());
219     /// ```
is_message(&self) -> bool220     pub fn is_message(&self) -> bool {
221         matches!(self.tag, MimeTypeTag::Message)
222     }
223 
224     /// Checks whether the main type is `model`.
225     ///
226     /// # Examples
227     ///
228     /// ```
229     /// use ylong_http::body::MimeType;
230     ///
231     /// let mime_type = MimeType::from_bytes(b"model/e57").unwrap();
232     /// assert!(mime_type.is_model());
233     /// ```
is_model(&self) -> bool234     pub fn is_model(&self) -> bool {
235         matches!(self.tag, MimeTypeTag::Model)
236     }
237 
238     /// Checks whether the main type is `multipart`.
239     ///
240     /// # Examples
241     ///
242     /// ```
243     /// use ylong_http::body::MimeType;
244     ///
245     /// let mime_type = MimeType::from_bytes(b"multipart/form-data").unwrap();
246     /// assert!(mime_type.is_multipart());
247     /// ```
is_multipart(&self) -> bool248     pub fn is_multipart(&self) -> bool {
249         matches!(self.tag, MimeTypeTag::Multipart)
250     }
251 
252     /// Checks whether the main type is `text`.
253     ///
254     /// # Examples
255     ///
256     /// ```
257     /// use ylong_http::body::MimeType;
258     ///
259     /// let mime_type = MimeType::from_bytes(b"text/richtext").unwrap();
260     /// assert!(mime_type.is_text());
261     /// ```
is_text(&self) -> bool262     pub fn is_text(&self) -> bool {
263         matches!(self.tag, MimeTypeTag::Text)
264     }
265 
266     /// Checks whether the main type is `video`.
267     ///
268     /// # Examples
269     ///
270     /// ```
271     /// use ylong_http::body::MimeType;
272     ///
273     /// let mime_type = MimeType::from_bytes(b"video/mpeg").unwrap();
274     /// assert!(mime_type.is_video());
275     /// ```
is_video(&self) -> bool276     pub fn is_video(&self) -> bool {
277         matches!(self.tag, MimeTypeTag::Video)
278     }
279 
280     /// Checks whether the main type is non-standard type `x-`.
281     ///
282     /// # Examples
283     ///
284     /// ```
285     /// use ylong_http::body::MimeType;
286     ///
287     /// let mime_type = MimeType::from_bytes(b"x-world/x-vrml").unwrap();
288     /// assert!(mime_type.is_xnew());
289     /// ```
is_xnew(&self) -> bool290     pub fn is_xnew(&self) -> bool {
291         matches!(self.tag, MimeTypeTag::Xnew)
292     }
293 }
294 
295 impl<'a> Default for MimeType<'a> {
default() -> Self296     fn default() -> Self {
297         Self {
298             tag: MimeTypeTag::Application,
299             bytes: b"application/octet-stream",
300             slash: 11,
301         }
302     }
303 }
304 
305 macro_rules! mime {
306     ($($ext: expr, $str: expr, $slash: expr, $tag: expr$(;)?)*) => {
307         impl MimeType<'_> {
308             /// Creates a new `MimeType` from file extension.
309             ///
310             /// Returns `application/octet-stream` if extension is not discerned.
311             ///
312             /// # Examples
313             ///
314             /// ```
315             /// use ylong_http::body::MimeType;
316             ///
317             /// let mime_type = MimeType::from_extension("pdf").unwrap();
318             /// assert_eq!(mime_type.main_type(), "application");
319             /// assert_eq!(mime_type.sub_type(), "pdf");
320             /// ```
321             pub fn from_extension(s: &str) -> Result<Self, HttpError> {
322                 Ok(match s {
323                     $(
324                         $ext => MimeType {
325                             tag: $tag,
326                             bytes: $str.as_bytes(),
327                             slash: $slash,
328                         },
329                     )*
330                     _=> MimeType {
331                         tag: MimeTypeTag::Application,
332                         bytes: b"application/octet-stream",
333                         slash: 11,
334                     }
335                 })
336             }
337         }
338 
339         /// UT test cases for `ut_mime_type_from_extension`.
340         ///
341         /// # Brief
342         /// 1. Creates a `MimeType` from file extension.
343         /// 2. Checks if the test results are correct.
344         #[test]
345         fn ut_mime_type_from_extension() {
346             $(
347                 let mime_type = MimeType::from_extension($ext).unwrap();
348                 assert_eq!(mime_type.tag, $tag);
349                 assert_eq!(mime_type.bytes, $str.as_bytes());
350                 assert_eq!(mime_type.slash, $slash);
351             )*
352         }
353     };
354 }
355 
356 mime!(
357     "evy", "application/envoy", 11, MimeTypeTag::Application;
358     "fif", "application/fractals", 11, MimeTypeTag::Application;
359     "spl", "application/futuresplash", 11, MimeTypeTag::Application;
360     "hta", "application/hta", 11, MimeTypeTag::Application;
361     "acx", "application/internet-property-stream", 11, MimeTypeTag::Application;
362     "hqx", "application/mac-binhex40", 11, MimeTypeTag::Application;
363     "doc", "application/msword", 11, MimeTypeTag::Application;
364     "dot", "application/msword", 11, MimeTypeTag::Application;
365     "*", "application/octet-stream", 11, MimeTypeTag::Application;
366     "bin", "application/octet-stream", 11, MimeTypeTag::Application;
367     "class", "application/octet-stream", 11, MimeTypeTag::Application;
368     "dms", "application/octet-stream", 11, MimeTypeTag::Application;
369     "exe", "application/octet-stream", 11, MimeTypeTag::Application;
370     "lha", "application/octet-stream", 11, MimeTypeTag::Application;
371     "lzh", "application/octet-stream", 11, MimeTypeTag::Application;
372     "oda", "application/oda", 11, MimeTypeTag::Application;
373     "axs", "application/olescript", 11, MimeTypeTag::Application;
374     "pdf", "application/pdf", 11, MimeTypeTag::Application;
375     "prf", "application/pics-rules", 11, MimeTypeTag::Application;
376     "p10", "application/pkcs10", 11, MimeTypeTag::Application;
377     "crl", "application/pkix-crl", 11, MimeTypeTag::Application;
378     "ai", "application/postscript", 11, MimeTypeTag::Application;
379     "eps", "application/postscript", 11, MimeTypeTag::Application;
380     "ps", "application/postscript", 11, MimeTypeTag::Application;
381     "rtf", "application/rtf", 11, MimeTypeTag::Application;
382     "setpay", "application/set-payment-initiation", 11, MimeTypeTag::Application;
383     "setreg", "application/set-registration-initiation", 11, MimeTypeTag::Application;
384     "xla", "application/vnd.ms-excel", 11, MimeTypeTag::Application;
385     "xlc", "application/vnd.ms-excel", 11, MimeTypeTag::Application;
386     "xlm", "application/vnd.ms-excel", 11, MimeTypeTag::Application;
387     "xls", "application/vnd.ms-excel", 11, MimeTypeTag::Application;
388     "xlt", "application/vnd.ms-excel", 11, MimeTypeTag::Application;
389     "xlw", "application/vnd.ms-excel", 11, MimeTypeTag::Application;
390     "msg", "application/vnd.ms-outlook", 11, MimeTypeTag::Application;
391     "sst", "application/vnd.ms-pkicertstore", 11, MimeTypeTag::Application;
392     "cat", "application/vnd.ms-pkiseccat", 11, MimeTypeTag::Application;
393     "stl", "application/vnd.ms-pkistl", 11, MimeTypeTag::Application;
394     "pot", "application/vnd.ms-powerpoint", 11, MimeTypeTag::Application;
395     "pps", "application/vnd.ms-powerpoint", 11, MimeTypeTag::Application;
396     "ppt", "application/vnd.ms-powerpoint", 11, MimeTypeTag::Application;
397     "mpp", "application/vnd.ms-project", 11, MimeTypeTag::Application;
398     "wcm", "application/vnd.ms-works", 11, MimeTypeTag::Application;
399     "wdb", "application/vnd.ms-works", 11, MimeTypeTag::Application;
400     "wks", "application/vnd.ms-works", 11, MimeTypeTag::Application;
401     "wps", "application/vnd.ms-works", 11, MimeTypeTag::Application;
402     "hlp", "application/winhlp", 11, MimeTypeTag::Application;
403     "bcpio", "application/x-bcpio", 11, MimeTypeTag::Application;
404     // "cdf" also can be "application/x-netcdf"
405     "cdf", "application/x-cdf", 11, MimeTypeTag::Application;
406     "z", "application/x-compress", 11, MimeTypeTag::Application;
407     "tgz", "application/x-compressed", 11, MimeTypeTag::Application;
408     "cpio", "application/x-cpio", 11, MimeTypeTag::Application;
409     "csh", "application/x-csh", 11, MimeTypeTag::Application;
410     "dcr", "application/x-director", 11, MimeTypeTag::Application;
411     "dir", "application/x-director", 11, MimeTypeTag::Application;
412     "dxr", "application/x-director", 11, MimeTypeTag::Application;
413     "dvi", "application/x-dvi", 11, MimeTypeTag::Application;
414     "gtar", "application/x-gtar", 11, MimeTypeTag::Application;
415     "gz", "application/x-gzip", 11, MimeTypeTag::Application;
416     "hdf", "application/x-hdf", 11, MimeTypeTag::Application;
417     "ins", "application/x-internet-signup", 11, MimeTypeTag::Application;
418     "isp", "application/x-internet-signup", 11, MimeTypeTag::Application;
419     "iii", "application/x-iphone", 11, MimeTypeTag::Application;
420     "js", "application/x-javascript", 11, MimeTypeTag::Application;
421     "latex", "application/x-latex", 11, MimeTypeTag::Application;
422     "mdb", "application/x-msaccess", 11, MimeTypeTag::Application;
423     "crd", "application/x-mscardfile", 11, MimeTypeTag::Application;
424     "clp", "application/x-msclip", 11, MimeTypeTag::Application;
425     "dll", "application/x-msdownload", 11, MimeTypeTag::Application;
426     "m13", "application/x-msmediaview", 11, MimeTypeTag::Application;
427     "m14", "application/x-msmediaview", 11, MimeTypeTag::Application;
428     "mvb", "application/x-msmediaview", 11, MimeTypeTag::Application;
429     "wmf", "application/x-msmetafile", 11, MimeTypeTag::Application;
430     "mny", "application/x-msmoney", 11, MimeTypeTag::Application;
431     "pub", "application/x-mspublisher", 11, MimeTypeTag::Application;
432     "scd", "application/x-msschedule", 11, MimeTypeTag::Application;
433     "trm", "application/x-msterminal", 11, MimeTypeTag::Application;
434     "wri", "application/x-mswrite", 11, MimeTypeTag::Application;
435     "nc", "application/x-netcdf", 11, MimeTypeTag::Application;
436     "pma", "application/x-perfmon", 11, MimeTypeTag::Application;
437     "pmc", "application/x-perfmon", 11, MimeTypeTag::Application;
438     "pml", "application/x-perfmon", 11, MimeTypeTag::Application;
439     "pmr", "application/x-perfmon", 11, MimeTypeTag::Application;
440     "pmw", "application/x-perfmon", 11, MimeTypeTag::Application;
441     "p12", "application/x-pkcs12", 11, MimeTypeTag::Application;
442     "pfx", "application/x-pkcs12", 11, MimeTypeTag::Application;
443     "p7b", "application/x-pkcs7-certificates", 11, MimeTypeTag::Application;
444     "spc", "application/x-pkcs7-certificates", 11, MimeTypeTag::Application;
445     "p7r", "application/x-pkcs7-certificates", 11, MimeTypeTag::Application;
446     "p7c", "application/x-pkcs7-mime", 11, MimeTypeTag::Application;
447     "p7m", "application/x-pkcs7-mime", 11, MimeTypeTag::Application;
448     "p7s", "application/x-pkcs7-signature", 11, MimeTypeTag::Application;
449     "sh", "application/x-sh", 11, MimeTypeTag::Application;
450     "shar", "application/x-shar", 11, MimeTypeTag::Application;
451     "swf", "application/x-shockwave-flash", 11, MimeTypeTag::Application;
452     "sit", "application/x-stuffit", 11, MimeTypeTag::Application;
453     "sv4cpio", "application/x-sv4cpio", 11, MimeTypeTag::Application;
454     "sv4crc", "application/x-sv4crc", 11, MimeTypeTag::Application;
455     "tar", "application/x-tar", 11, MimeTypeTag::Application;
456     "tcl", "application/x-tcl", 11, MimeTypeTag::Application;
457     "tex", "application/x-tex", 11, MimeTypeTag::Application;
458     "texi", "application/x-texinfo", 11, MimeTypeTag::Application;
459     "texinfo", "application/x-texinfo", 11, MimeTypeTag::Application;
460     "roff", "application/x-troff", 11, MimeTypeTag::Application;
461     "t", "application/x-troff", 11, MimeTypeTag::Application;
462     "tr", "application/x-troff", 11, MimeTypeTag::Application;
463     "man", "application/x-troff-man", 11, MimeTypeTag::Application;
464     "me", "application/x-troff-me", 11, MimeTypeTag::Application;
465     "ms", "application/x-troff-ms", 11, MimeTypeTag::Application;
466     "ustar", "application/x-ustar", 11, MimeTypeTag::Application;
467     "src", "application/x-wais-source", 11, MimeTypeTag::Application;
468     "cer", "application/x-x509-ca-cert", 11, MimeTypeTag::Application;
469     "crt", "application/x-x509-ca-cert", 11, MimeTypeTag::Application;
470     "der", "application/x-x509-ca-cert", 11, MimeTypeTag::Application;
471     "pko", "application/ynd.ms-pkipko", 11, MimeTypeTag::Application;
472     "zip", "application/zip", 11, MimeTypeTag::Application;
473     "au", "audio/basic", 5, MimeTypeTag::Audio;
474     "snd", "audio/basic", 5, MimeTypeTag::Audio;
475     "mid", "audio/mid", 5, MimeTypeTag::Audio;
476     "rmi", "audio/mid", 5, MimeTypeTag::Audio;
477     "mp3", "audio/mpeg", 5, MimeTypeTag::Audio;
478     "aif", "audio/x-aiff", 5, MimeTypeTag::Audio;
479     "aifc", "audio/x-aiff", 5, MimeTypeTag::Audio;
480     "aiff", "audio/x-aiff", 5, MimeTypeTag::Audio;
481     "m3u", "audio/x-mpegurl", 5, MimeTypeTag::Audio;
482     "ra", "audio/x-pn-realaudio", 5, MimeTypeTag::Audio;
483     "ram", "audio/x-pn-realaudio", 5, MimeTypeTag::Audio;
484     "wav", "audio/x-wav", 5, MimeTypeTag::Audio;
485     "bmp", "image/bmp", 5, MimeTypeTag::Image;
486     "cod", "image/cis-cod", 5, MimeTypeTag::Image;
487     "gif", "image/gif", 5, MimeTypeTag::Image;
488     "ief", "image/ief", 5, MimeTypeTag::Image;
489     "jpe", "image/jpeg", 5, MimeTypeTag::Image;
490     "jpeg", "image/jpeg", 5, MimeTypeTag::Image;
491     "jpg", "image/jpeg", 5, MimeTypeTag::Image;
492     "jfif", "image/pipeg", 5, MimeTypeTag::Image;
493     "svg", "image/svg+xml", 5, MimeTypeTag::Image;
494     "tif", "image/tiff", 5, MimeTypeTag::Image;
495     "tiff", "image/tiff", 5, MimeTypeTag::Image;
496     "ras", "image/x-cmu-raster", 5, MimeTypeTag::Image;
497     "cmx", "image/x-cmx", 5, MimeTypeTag::Image;
498     "ico", "image/x-icon", 5, MimeTypeTag::Image;
499     "pnm", "image/x-portable-anymap", 5, MimeTypeTag::Image;
500     "pbm", "image/x-portable-bitmap", 5, MimeTypeTag::Image;
501     "pgm", "image/x-portable-graymap", 5, MimeTypeTag::Image;
502     "ppm", "image/x-portable-pixmap", 5, MimeTypeTag::Image;
503     "rgb", "image/x-rgb", 5, MimeTypeTag::Image;
504     "xbm", "image/x-xbitmap", 5, MimeTypeTag::Image;
505     "xpm", "image/x-xpixmap", 5, MimeTypeTag::Image;
506     "xwd", "image/x-xwindowdump", 5, MimeTypeTag::Image;
507     "mht", "message/rfc822", 7, MimeTypeTag::Message;
508     "mhtml", "message/rfc822", 7, MimeTypeTag::Message;
509     "nws", "message/rfc822", 7, MimeTypeTag::Message;
510     "css", "text/css", 4, MimeTypeTag::Text;
511     "323", "text/h323", 4, MimeTypeTag::Text;
512     "htm", "text/html", 4, MimeTypeTag::Text;
513     "html", "text/html", 4, MimeTypeTag::Text;
514     "stm", "text/html", 4, MimeTypeTag::Text;
515     "uls", "text/iuls", 4, MimeTypeTag::Text;
516     "bas", "text/plain", 4, MimeTypeTag::Text;
517     "c", "text/plain", 4, MimeTypeTag::Text;
518     "h", "text/plain", 4, MimeTypeTag::Text;
519     "txt", "text/plain", 4, MimeTypeTag::Text;
520     "rtx", "text/richtext", 4, MimeTypeTag::Text;
521     "sct", "text/scriptlet", 4, MimeTypeTag::Text;
522     "tsv", "text/tab-separated-values", 4, MimeTypeTag::Text;
523     "htt", "text/webviewhtml", 4, MimeTypeTag::Text;
524     "htc", "text/x-component", 4, MimeTypeTag::Text;
525     "etx", "text/x-setext", 4, MimeTypeTag::Text;
526     "vcf", "text/x-vcard", 4, MimeTypeTag::Text;
527     "mp2", "video/mpeg", 5, MimeTypeTag::Video;
528     "mpa", "video/mpeg", 5, MimeTypeTag::Video;
529     "mpe", "video/mpeg", 5, MimeTypeTag::Video;
530     "mpeg", "video/mpeg", 5, MimeTypeTag::Video;
531     "mpg", "video/mpeg", 5, MimeTypeTag::Video;
532     "mpv2", "video/mpeg", 5, MimeTypeTag::Video;
533     "mov", "video/quicktime", 5, MimeTypeTag::Video;
534     "qt", "video/quicktime", 5, MimeTypeTag::Video;
535     "lsf", "video/x-la-asf", 5, MimeTypeTag::Video;
536     "lsx", "video/x-la-asf", 5, MimeTypeTag::Video;
537     "asf", "video/x-ms-asf", 5, MimeTypeTag::Video;
538     "asr", "video/x-ms-asf", 5, MimeTypeTag::Video;
539     "asx", "video/x-ms-asf", 5, MimeTypeTag::Video;
540     "avi", "video/x-msvideo", 5, MimeTypeTag::Video;
541     "movie", "video/x-sgi-movie", 5, MimeTypeTag::Video;
542     "flr", "x-world/x-vrml", 7, MimeTypeTag::Xnew;
543     "vrml", "x-world/x-vrml", 7, MimeTypeTag::Xnew;
544     "wrl", "x-world/x-vrml", 7, MimeTypeTag::Xnew;
545     "wrz", "x-world/x-vrml", 7, MimeTypeTag::Xnew;
546     "xaf", "x-world/x-vrml", 7, MimeTypeTag::Xnew;
547     "xof", "x-world/x-vrml", 7, MimeTypeTag::Xnew;
548 );
549 
550 /// `MIME` main type.
551 #[derive(Debug, PartialEq, Eq)]
552 enum MimeTypeTag {
553     Application,
554     Audio,
555     Font,
556     Image,
557     Message,
558     Model,
559     Multipart,
560     Text,
561     Video,
562     // A type not included in the standard, beginning with `x-`
563     Xnew,
564 }
565 
566 impl MimeTypeTag {
from_bytes(b: &[u8]) -> Result<Self, HttpError>567     fn from_bytes(b: &[u8]) -> Result<Self, HttpError> {
568         // From [RFC6838](http://tools.ietf.org/html/rfc6838#section-4.2)
569         // <type-name> and <subtype-name> SHOULD be limited to 64 characters.
570         // Both top-level type and subtype names are case-insensitive.
571 
572         if b.len() > 64 || b.len() < 2 {
573             return Err(ErrorKind::InvalidInput.into());
574         }
575 
576         match b[0].to_ascii_lowercase() {
577             // beginning with "x-"
578             b'x' => {
579                 if b[1] == b'-' && is_valid(&b[2..]) {
580                     return Ok(Self::Xnew);
581                 }
582             }
583             b'a' => {
584                 return Self::mime_byte_a(b);
585             }
586             b'f' => {
587                 // font
588                 if b[1..].eq_ignore_ascii_case(b"ont") {
589                     return Ok(Self::Font);
590                 }
591             }
592             b'i' => {
593                 // image
594                 if b[1..].eq_ignore_ascii_case(b"mage") {
595                     return Ok(Self::Image);
596                 }
597             }
598             b'm' => {
599                 return Self::mime_byte_m(b);
600             }
601             b't' => {
602                 // text
603                 if b[1..].eq_ignore_ascii_case(b"ext") {
604                     return Ok(Self::Text);
605                 }
606             }
607             b'v' => {
608                 // video
609                 if b[1..].eq_ignore_ascii_case(b"ideo") {
610                     return Ok(Self::Video);
611                 }
612             }
613             _ => return Err(ErrorKind::InvalidInput.into()),
614         };
615 
616         Err(ErrorKind::InvalidInput.into())
617     }
618 
mime_byte_a(b: &[u8]) -> Result<MimeTypeTag, HttpError>619     fn mime_byte_a(b: &[u8]) -> Result<MimeTypeTag, HttpError> {
620         // application
621         if b[1..].eq_ignore_ascii_case(b"pplication") {
622             Ok(Self::Application)
623             // audio
624         } else if b[1..].eq_ignore_ascii_case(b"udio") {
625             Ok(Self::Audio)
626         } else {
627             Err(ErrorKind::InvalidInput.into())
628         }
629     }
mime_byte_m(b: &[u8]) -> Result<MimeTypeTag, HttpError>630     fn mime_byte_m(b: &[u8]) -> Result<MimeTypeTag, HttpError> {
631         // message
632         if b[1..].eq_ignore_ascii_case(b"essage") {
633             Ok(Self::Message)
634             // model
635         } else if b[1..].eq_ignore_ascii_case(b"odel") {
636             Ok(Self::Model)
637             // multipart
638         } else if b[1..].eq_ignore_ascii_case(b"ultipart") {
639             Ok(Self::Multipart)
640         } else {
641             Err(ErrorKind::InvalidInput.into())
642         }
643     }
644 }
645 
646 // From [RFC6838](http://tools.ietf.org/html/rfc6838#section-4.2):
647 //
648 // All registered media types MUST be assigned top-level type and
649 // subtype names.  The combination of these names serves to uniquely
650 // identify the media type, and the subtype name facet (or the absence
651 // of one) identifies the registration tree.  Both top-level type and
652 // subtype names are case-insensitive.
653 //
654 // Type and subtype names MUST conform to the following ABNF:
655 //
656 //     type-name = restricted-name
657 //     subtype-name = restricted-name
658 //
659 //     restricted-name = restricted-name-first *126restricted-name-chars
660 //     restricted-name-first  = ALPHA / DIGIT
661 //     restricted-name-chars  = ALPHA / DIGIT / "!" / "#" /
662 //                              "$" / "&" / "-" / "^" / "_"
663 //     restricted-name-chars =/ "." ; Characters before first dot always
664 //                                  ; specify a facet name
665 //     restricted-name-chars =/ "+" ; Characters after last plus always
666 //                                  ; specify a structured syntax suffix
667 #[rustfmt::skip]
668 static MEDIA_TYPE_VALID_BYTES: [bool; 256] = {
669     const __: bool = false;
670     const TT: bool = true;
671     [
672 //      \0                                  HT  LF          CR
673         __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,
674         __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,
675 //      \w  !   "   #   $   %   &   '   (   )   *   +   ,   -   .   /
676         __, TT, __, TT, TT, __, TT, __, __, __, __, TT, __, TT, TT, __,
677 //       0   1   2   3   4   5   6   7   8   9   :   ;   <   =   >   ?
678         TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, __, __, __, __, __, __,
679 //       @   A   B   C   D   E   F   G   H   I   J   K   L   M   N   O
680         __, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT,
681 //       P   Q   R   S   T   U   V   W   X   Y   Z   [   \   ]   ^   _
682         TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, __, __, __, TT, TT,
683 //       `   a   b   c   d   e   f   g   h   i   j   k   l   m   n   o
684         __, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT,
685 //       p   q   r   s   t   u   v   w   x   y   z   {   |   }   ~   del
686         TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, __, __, __, __, __,
687 // Expand ascii
688         __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,
689         __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,
690         __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,
691         __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,
692         __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,
693         __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,
694         __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,
695         __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,
696     ]
697 };
698 
is_valid(v: &[u8]) -> bool699 fn is_valid(v: &[u8]) -> bool {
700     v.iter().all(|b| MEDIA_TYPE_VALID_BYTES[*b as usize])
701 }
702 
703 #[cfg(test)]
704 mod ut_mime {
705     use super::{MimeType, MimeTypeTag};
706     use crate::error::{ErrorKind, HttpError};
707 
708     /// UT test cases for `MimeTypeTag::from_bytes`.
709     ///
710     /// # Brief
711     /// 1. Creates a `MimeTypeTag` from `&[u8]`.
712     /// 2. Checks if the test results are correct.
713     #[test]
ut_mime_type_tag_from_bytes()714     fn ut_mime_type_tag_from_bytes() {
715         assert_eq!(
716             MimeTypeTag::from_bytes(b"application"),
717             Ok(MimeTypeTag::Application)
718         );
719         assert_eq!(MimeTypeTag::from_bytes(b"audio"), Ok(MimeTypeTag::Audio));
720         assert_eq!(MimeTypeTag::from_bytes(b"font"), Ok(MimeTypeTag::Font));
721         assert_eq!(MimeTypeTag::from_bytes(b"image"), Ok(MimeTypeTag::Image));
722         assert_eq!(
723             MimeTypeTag::from_bytes(b"message"),
724             Ok(MimeTypeTag::Message)
725         );
726         assert_eq!(MimeTypeTag::from_bytes(b"model"), Ok(MimeTypeTag::Model));
727         assert_eq!(
728             MimeTypeTag::from_bytes(b"multipart"),
729             Ok(MimeTypeTag::Multipart)
730         );
731         assert_eq!(MimeTypeTag::from_bytes(b"text"), Ok(MimeTypeTag::Text));
732         assert_eq!(MimeTypeTag::from_bytes(b"video"), Ok(MimeTypeTag::Video));
733         assert_eq!(MimeTypeTag::from_bytes(b"x-world"), Ok(MimeTypeTag::Xnew));
734         assert_eq!(
735             MimeTypeTag::from_bytes(b"APPLICATION"),
736             Ok(MimeTypeTag::Application)
737         );
738         assert_eq!(
739             MimeTypeTag::from_bytes(b"x-ab/cd"),
740             Err(HttpError::from(ErrorKind::InvalidInput))
741         );
742         assert_eq!(
743             MimeTypeTag::from_bytes(b"notype"),
744             Err(HttpError::from(ErrorKind::InvalidInput))
745         );
746     }
747 
748     /// UT test cases for `MimeType::from_bytes`.
749     ///
750     /// # Brief
751     /// 1. Creates a `MimeType` from `&[u8]`.
752     /// 2. Checks if the test results are correct.
753     #[test]
ut_mime_type_from_bytes()754     fn ut_mime_type_from_bytes() {
755         assert_eq!(
756             MimeType::from_bytes(b"application/octet-stream"),
757             Ok(MimeType {
758                 tag: MimeTypeTag::Application,
759                 bytes: b"application/octet-stream",
760                 slash: 11,
761             })
762         );
763 
764         assert_eq!(
765             MimeType::from_bytes(b"TEXT/PLAIN"),
766             Ok(MimeType {
767                 tag: MimeTypeTag::Text,
768                 bytes: b"TEXT/PLAIN",
769                 slash: 4,
770             })
771         );
772 
773         assert_eq!(
774             MimeType::from_bytes(b"TEXT/~PLAIN"),
775             Err(HttpError::from(ErrorKind::InvalidInput))
776         );
777 
778         assert_eq!(
779             MimeType::from_bytes(b"application/octet/stream"),
780             Err(HttpError::from(ErrorKind::InvalidInput))
781         );
782     }
783 
784     /// UT test cases for `MimeType::from_path`.
785     ///
786     /// # Brief
787     /// 1. Creates a `MimeType` from path.
788     /// 2. Checks if the test results are correct.
789     #[test]
ut_mime_type_from_path()790     fn ut_mime_type_from_path() {
791         use std::path::Path;
792 
793         use crate::error::HttpError;
794 
795         assert_eq!(
796             MimeType::from_path(Path::new("./foo/bar.evy")),
797             MimeType::from_bytes(b"application/envoy")
798         );
799         assert_eq!(
800             MimeType::from_path(Path::new("foo.*")),
801             MimeType::from_bytes(b"application/octet-stream")
802         );
803         assert_eq!(
804             MimeType::from_path(Path::new("")),
805             Err(HttpError::from(ErrorKind::InvalidInput))
806         );
807         assert_eq!(
808             MimeType::from_path(Path::new(".txt")),
809             Err(HttpError::from(ErrorKind::InvalidInput))
810         );
811         assert_eq!(
812             MimeType::from_path(Path::new("./foo/bar")),
813             Err(HttpError::from(ErrorKind::InvalidInput))
814         );
815     }
816 
817     /// UT test cases for `MimeType::main_type`.
818     ///
819     /// # Brief
820     /// 1. Gets main type string by `main_type`.
821     /// 2. Checks if the test results are correct.
822     #[test]
ut_mime_type_main_type()823     fn ut_mime_type_main_type() {
824         let mime_type = MimeType::from_bytes(b"application/octet-stream").unwrap();
825         assert_eq!(mime_type.main_type(), "application");
826 
827         let mime_type = MimeType::from_bytes(b"TeXT/PLAIN").unwrap();
828         assert_eq!(mime_type.main_type(), "TeXT");
829     }
830 
831     /// UT test cases for `MimeType::sub_type`.
832     ///
833     /// # Brief
834     /// 1. Gets subtype type string by `sub_type`.
835     /// 2. Checks if the test results are correct.
836     #[test]
ut_mimetype_sub_type()837     fn ut_mimetype_sub_type() {
838         let mime_type = MimeType::from_bytes(b"application/octet-stream").unwrap();
839         assert_eq!(mime_type.sub_type(), "octet-stream");
840     }
841 
842     /// UT test cases for `MimeType::as_str`.
843     ///
844     /// # Brief
845     /// 1. Gets string from `MimeType` by `as_str`.
846     /// 2. Checks if the test results are correct.
847     #[test]
ut_mimetype_as_str()848     fn ut_mimetype_as_str() {
849         let mime_type = MimeType::from_bytes(b"application/pdf").unwrap();
850         assert_eq!(mime_type.as_str(), "application/pdf");
851 
852         let mime_type = MimeType::from_bytes(b"application/octet-stream").unwrap();
853         assert_eq!(mime_type.as_str(), "application/octet-stream");
854     }
855 
856     /// UT test cases for `Mimetype::eq`.
857     ///
858     /// # Brief
859     /// 1. Creates some `MimeType`, and check if they are equal.
860     /// 2. Checks if the test results are correct.
861     #[test]
ut_mime_type_eq()862     fn ut_mime_type_eq() {
863         assert_eq!(
864             MimeType {
865                 tag: MimeTypeTag::Application,
866                 bytes: b"application/octet-stream",
867                 slash: 11,
868             },
869             MimeType {
870                 tag: MimeTypeTag::Application,
871                 bytes: b"application/octet-stream",
872                 slash: 11,
873             }
874         );
875 
876         assert_ne!(
877             MimeType::from_bytes(b"application/octet-stream"),
878             MimeType::from_bytes(b"application/pdf")
879         );
880 
881         assert_eq!(
882             MimeType::from_extension("pdf"),
883             MimeType::from_bytes(b"application/pdf")
884         );
885     }
886 
887     /// UT test cases for `MimeType::is_application`.
888     ///
889     /// # Brief
890     /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`.
891     /// 2. Checks whether the main types are correct.
892     #[test]
ut_mimetype_is_application()893     fn ut_mimetype_is_application() {
894         assert!(MimeType::from_bytes(b"application/pdf")
895             .unwrap()
896             .is_application());
897         assert!(!MimeType::from_bytes(b"audio/basic")
898             .unwrap()
899             .is_application());
900     }
901 
902     /// UT test cases for `MimeType::is_audio`.
903     ///
904     /// # Brief
905     /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`.
906     /// 2. Checks whether the main types are correct.
907     #[test]
ut_mimetype_is_audio()908     fn ut_mimetype_is_audio() {
909         assert!(MimeType::from_bytes(b"audio/basic").unwrap().is_audio());
910         assert!(!MimeType::from_bytes(b"application/pdf").unwrap().is_audio());
911     }
912 
913     /// UT test cases for `MimeType::is_font`.
914     ///
915     /// # Brief
916     /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`.
917     /// 2. Checks whether the main types are correct.
918     #[test]
ut_mime_type_is_font()919     fn ut_mime_type_is_font() {
920         assert!(MimeType::from_bytes(b"font/collection").unwrap().is_font());
921         assert!(!MimeType::from_bytes(b"application/pdf").unwrap().is_font());
922     }
923 
924     /// UT test cases for `MimeType::is_image`.
925     ///
926     /// # Brief
927     /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`.
928     /// 2. Checks whether the main types are correct.
929     #[test]
ut_mimetype_is_image()930     fn ut_mimetype_is_image() {
931         assert!(MimeType::from_bytes(b"image/bmp").unwrap().is_image());
932         assert!(!MimeType::from_bytes(b"application/pdf").unwrap().is_image());
933     }
934 
935     /// UT test cases for `MimeType::is_message`.
936     ///
937     /// # Brief
938     /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`.
939     /// 2. Checks whether the main types are correct.
940     #[test]
ut_mimetype_is_message()941     fn ut_mimetype_is_message() {
942         assert!(MimeType::from_bytes(b"message/example")
943             .unwrap()
944             .is_message());
945         assert!(!MimeType::from_bytes(b"application/pdf")
946             .unwrap()
947             .is_message());
948     }
949 
950     /// UT test cases for `MimeType::is_model`.
951     ///
952     /// # Brief
953     /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`.
954     /// 2. Checks whether the main types are correct.
955     #[test]
ut_mimetype_is_model()956     fn ut_mimetype_is_model() {
957         assert!(MimeType::from_bytes(b"model/e57").unwrap().is_model());
958         assert!(!MimeType::from_bytes(b"application/pdf").unwrap().is_model());
959     }
960 
961     /// UT test cases for `MimeType::is_multipart`.
962     ///
963     /// # Brief
964     /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`.
965     /// 2. Checks whether the main types are correct.
966     #[test]
ut_mime_type_is_multipart()967     fn ut_mime_type_is_multipart() {
968         assert!(MimeType::from_bytes(b"multipart/form-data")
969             .unwrap()
970             .is_multipart());
971         assert!(!MimeType::from_bytes(b"application/pdf")
972             .unwrap()
973             .is_multipart());
974     }
975 
976     /// UT test cases for `MimeType::is_text`.
977     ///
978     /// # Brief
979     /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`.
980     /// 2. Checks whether the main types are correct.
981     #[test]
ut_mime_type_is_text()982     fn ut_mime_type_is_text() {
983         assert!(MimeType::from_bytes(b"text/csv").unwrap().is_text());
984         assert!(!MimeType::from_bytes(b"application/pdf").unwrap().is_text());
985     }
986 
987     /// UT test cases for `MimeType::is_video`.
988     ///
989     /// # Brief
990     /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`.
991     /// 2. Checks whether the main types are correct.
992     #[test]
ut_mimetype_is_video()993     fn ut_mimetype_is_video() {
994         assert!(MimeType::from_bytes(b"video/mpeg").unwrap().is_video());
995         assert!(!MimeType::from_bytes(b"application/pdf").unwrap().is_video());
996     }
997 
998     /// UT test cases for `MimeType::is_xnew`.
999     ///
1000     /// # Brief
1001     /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`.
1002     /// 2. Checks whether the main types are correct.
1003     #[test]
ut_mime_type_is_xnew()1004     fn ut_mime_type_is_xnew() {
1005         assert!(MimeType::from_bytes(b"x-world/x-vrml").unwrap().is_xnew());
1006         assert!(!MimeType::from_bytes(b"application/pdf").unwrap().is_xnew());
1007     }
1008 }
1009