├── src ├── consts │ ├── mod.rs │ ├── values.txt │ ├── values.rs │ └── names.txt ├── error.rs ├── serde.rs ├── params.rs ├── lib.rs ├── name.rs ├── media_type_list.rs ├── value.rs ├── parse.rs ├── media_type.rs └── media_type_buf.rs ├── rustfmt.toml ├── .gitignore ├── Cargo.toml ├── .github └── workflows │ └── rust.yml ├── LICENSE ├── tests └── codegen.rs └── README.md /src/consts/mod.rs: -------------------------------------------------------------------------------- 1 | #[rustfmt::skip] 2 | pub mod names; 3 | #[rustfmt::skip] 4 | pub mod values; 5 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | imports_granularity = "Crate" 2 | format_code_in_doc_comments = true 3 | normalize_doc_attributes = true 4 | normalize_comments = true 5 | format_strings = true 6 | edition = "2021" -------------------------------------------------------------------------------- /src/consts/values.txt: -------------------------------------------------------------------------------- 1 | Big5 2 | EUC-JP 3 | EUC-KR 4 | fixed 5 | flowed 6 | GB2312 7 | ISO-2022-JP 8 | ISO-2022-JP-2 9 | ISO-2022-KR 10 | ISO-8859-1 11 | ISO-8859-10 12 | ISO-8859-2 13 | ISO-8859-3 14 | ISO-8859-4 15 | ISO-8859-5 16 | ISO-8859-6 17 | ISO-8859-6-E 18 | ISO-8859-6-I 19 | ISO-8859-7 20 | ISO-8859-8 21 | ISO-8859-8-E 22 | ISO-8859-8-I 23 | ISO-8859-9 24 | KOI8-R 25 | no 26 | Shift_JIS 27 | US-ASCII 28 | UTF-8 29 | UTF-16 30 | yes -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | # Added by cargo 14 | # 15 | # already existing elements were commented out 16 | 17 | /target 18 | #Cargo.lock 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mediatype" 3 | version = "0.21.0" 4 | edition = "2021" 5 | authors = ["picoHz "] 6 | description = "MIME Media-type parsing" 7 | keywords = ["media-type", "media-types", "mime", "media-extensions"] 8 | categories = [ 9 | "parser-implementations", 10 | "network-programming", 11 | "web-programming", 12 | "email", 13 | ] 14 | repository = "https://github.com/picoHz/mediatype" 15 | homepage = "https://github.com/picoHz/mediatype" 16 | documentation = "https://docs.rs/mediatype" 17 | license = "MIT" 18 | readme = "README.md" 19 | 20 | [dependencies] 21 | serde = { version = "1.0.144", optional = true, default-features = false, features = ["alloc"] } 22 | 23 | [dev-dependencies] 24 | serde_json = "1.0.85" 25 | 26 | [package.metadata.docs.rs] 27 | all-features = true 28 | rustdoc-args = ["--cfg", "docsrs"] 29 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/cache@v4 20 | with: 21 | path: | 22 | ~/.rustup/toolchains/stable-* 23 | ~/.cargo/registry 24 | ~/.cargo/git 25 | target 26 | key: ${{ runner.os }}-stable-cargo-${{ hashFiles('**/Cargo.lock') }} 27 | - name: Run clippy 28 | run: cargo clippy --all-features --verbose 29 | - name: Build 30 | run: cargo build --all-features --verbose 31 | - name: Run tests 32 | run: cargo test --all-features --verbose 33 | - name: Test no_std support 34 | run: | 35 | rustup target add thumbv6m-none-eabi 36 | cargo build --verbose --release --no-default-features --target thumbv6m-none-eabi 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 picoHz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use core::{error, fmt}; 2 | 3 | /// Media-type format error. 4 | #[non_exhaustive] 5 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] 6 | pub enum MediaTypeError { 7 | /// The top-level type name is not valid. 8 | InvalidTypeName, 9 | /// The subtype name is not valid. 10 | InvalidSubtypeName, 11 | /// The suffix name is not valid. 12 | InvalidSuffix, 13 | /// The parameter syntax is not valid. 14 | InvalidParams, 15 | /// An invalid parameter name is detected. 16 | InvalidParamName, 17 | /// An invalid parameter value is detected. 18 | InvalidParamValue, 19 | } 20 | 21 | impl fmt::Display for MediaTypeError { 22 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 | let msg = match self { 24 | Self::InvalidTypeName => "Invalid type name", 25 | Self::InvalidSubtypeName => "Invalid subtype name", 26 | Self::InvalidSuffix => "Invalid suffix", 27 | Self::InvalidParams => "Invalid params", 28 | Self::InvalidParamName => "Invalid param name", 29 | Self::InvalidParamValue => "Invalid param value", 30 | }; 31 | f.write_str(msg) 32 | } 33 | } 34 | 35 | impl error::Error for MediaTypeError {} 36 | -------------------------------------------------------------------------------- /src/serde.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "serde")] 2 | 3 | use super::{media_type::*, media_type_buf::*}; 4 | use alloc::{borrow::Cow, string::ToString}; 5 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 6 | 7 | impl Serialize for MediaType<'_> { 8 | fn serialize(&self, serializer: S) -> Result 9 | where 10 | S: Serializer, 11 | { 12 | self.to_string().serialize(serializer) 13 | } 14 | } 15 | 16 | impl<'de: 'a, 'a> Deserialize<'de> for MediaType<'a> { 17 | fn deserialize(deserializer: D) -> Result 18 | where 19 | D: Deserializer<'de>, 20 | { 21 | use serde::de::Error; 22 | let s: &str = Deserialize::deserialize(deserializer)?; 23 | MediaType::parse(s).map_err(Error::custom) 24 | } 25 | } 26 | 27 | impl Serialize for MediaTypeBuf { 28 | fn serialize(&self, serializer: S) -> Result 29 | where 30 | S: Serializer, 31 | { 32 | self.to_string().serialize(serializer) 33 | } 34 | } 35 | 36 | impl<'de> Deserialize<'de> for MediaTypeBuf { 37 | fn deserialize(deserializer: D) -> Result 38 | where 39 | D: Deserializer<'de>, 40 | { 41 | use serde::de::Error; 42 | let s: Cow = Deserialize::deserialize(deserializer)?; 43 | Self::from_string(s.into_owned()).map_err(Error::custom) 44 | } 45 | } 46 | 47 | #[cfg(test)] 48 | mod tests { 49 | use super::*; 50 | use alloc::str::FromStr; 51 | use serde_json::Value; 52 | 53 | #[test] 54 | fn serde() { 55 | let original = MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8;").unwrap(); 56 | let json = serde_json::to_string(&original).unwrap(); 57 | let decoded: MediaType = serde_json::from_str(&json).unwrap(); 58 | assert_eq!(original, decoded); 59 | } 60 | 61 | #[test] 62 | fn serde_from_value() { 63 | let original = MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8;").unwrap(); 64 | let value = Value::String(original.as_str().into()); 65 | let decoded: MediaTypeBuf = serde_json::from_value(value).unwrap(); 66 | assert_eq!(original, decoded); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/consts/values.rs: -------------------------------------------------------------------------------- 1 | //! Predefined parameter values, @generated in tests/codegen.rs 2 | //! 3 | //! # Sources 4 | //! - 5 | //! - 6 | 7 | /// `Big5` 8 | pub const BIG5: crate::Value = crate::Value::new_unchecked("Big5"); 9 | /// `EUC-JP` 10 | pub const EUC_JP: crate::Value = crate::Value::new_unchecked("EUC-JP"); 11 | /// `EUC-KR` 12 | pub const EUC_KR: crate::Value = crate::Value::new_unchecked("EUC-KR"); 13 | /// `fixed` 14 | pub const FIXED: crate::Value = crate::Value::new_unchecked("fixed"); 15 | /// `flowed` 16 | pub const FLOWED: crate::Value = crate::Value::new_unchecked("flowed"); 17 | /// `GB2312` 18 | pub const GB2312: crate::Value = crate::Value::new_unchecked("GB2312"); 19 | /// `ISO-2022-JP` 20 | pub const ISO_2022_JP: crate::Value = crate::Value::new_unchecked("ISO-2022-JP"); 21 | /// `ISO-2022-JP-2` 22 | pub const ISO_2022_JP_2: crate::Value = crate::Value::new_unchecked("ISO-2022-JP-2"); 23 | /// `ISO-2022-KR` 24 | pub const ISO_2022_KR: crate::Value = crate::Value::new_unchecked("ISO-2022-KR"); 25 | /// `ISO-8859-1` 26 | pub const ISO_8859_1: crate::Value = crate::Value::new_unchecked("ISO-8859-1"); 27 | /// `ISO-8859-10` 28 | pub const ISO_8859_10: crate::Value = crate::Value::new_unchecked("ISO-8859-10"); 29 | /// `ISO-8859-2` 30 | pub const ISO_8859_2: crate::Value = crate::Value::new_unchecked("ISO-8859-2"); 31 | /// `ISO-8859-3` 32 | pub const ISO_8859_3: crate::Value = crate::Value::new_unchecked("ISO-8859-3"); 33 | /// `ISO-8859-4` 34 | pub const ISO_8859_4: crate::Value = crate::Value::new_unchecked("ISO-8859-4"); 35 | /// `ISO-8859-5` 36 | pub const ISO_8859_5: crate::Value = crate::Value::new_unchecked("ISO-8859-5"); 37 | /// `ISO-8859-6` 38 | pub const ISO_8859_6: crate::Value = crate::Value::new_unchecked("ISO-8859-6"); 39 | /// `ISO-8859-6-E` 40 | pub const ISO_8859_6_E: crate::Value = crate::Value::new_unchecked("ISO-8859-6-E"); 41 | /// `ISO-8859-6-I` 42 | pub const ISO_8859_6_I: crate::Value = crate::Value::new_unchecked("ISO-8859-6-I"); 43 | /// `ISO-8859-7` 44 | pub const ISO_8859_7: crate::Value = crate::Value::new_unchecked("ISO-8859-7"); 45 | /// `ISO-8859-8` 46 | pub const ISO_8859_8: crate::Value = crate::Value::new_unchecked("ISO-8859-8"); 47 | /// `ISO-8859-8-E` 48 | pub const ISO_8859_8_E: crate::Value = crate::Value::new_unchecked("ISO-8859-8-E"); 49 | /// `ISO-8859-8-I` 50 | pub const ISO_8859_8_I: crate::Value = crate::Value::new_unchecked("ISO-8859-8-I"); 51 | /// `ISO-8859-9` 52 | pub const ISO_8859_9: crate::Value = crate::Value::new_unchecked("ISO-8859-9"); 53 | /// `KOI8-R` 54 | pub const KOI8_R: crate::Value = crate::Value::new_unchecked("KOI8-R"); 55 | /// `no` 56 | pub const NO: crate::Value = crate::Value::new_unchecked("no"); 57 | /// `Shift_JIS` 58 | pub const SHIFT_JIS: crate::Value = crate::Value::new_unchecked("Shift_JIS"); 59 | /// `US-ASCII` 60 | pub const US_ASCII: crate::Value = crate::Value::new_unchecked("US-ASCII"); 61 | /// `UTF-8` 62 | pub const UTF_8: crate::Value = crate::Value::new_unchecked("UTF-8"); 63 | /// `UTF-16` 64 | pub const UTF_16: crate::Value = crate::Value::new_unchecked("UTF-16"); 65 | /// `yes` 66 | pub const YES: crate::Value = crate::Value::new_unchecked("yes"); 67 | -------------------------------------------------------------------------------- /src/params.rs: -------------------------------------------------------------------------------- 1 | use super::{name::*, parse::*, value::*}; 2 | 3 | /// An iterator over the parameters. 4 | #[derive(Debug)] 5 | pub struct Params<'a> { 6 | source: ParamsSource<'a>, 7 | index: usize, 8 | } 9 | 10 | impl<'a> Params<'a> { 11 | pub(crate) const fn from_slice(s: &'a [(Name<'a>, Value<'a>)]) -> Self { 12 | Self { 13 | source: ParamsSource::Slice(s), 14 | index: 0, 15 | } 16 | } 17 | 18 | pub(crate) const fn from_indices(s: &'a str, i: &'a Indices) -> Self { 19 | Self { 20 | source: ParamsSource::Indices(s, i), 21 | index: 0, 22 | } 23 | } 24 | } 25 | 26 | #[derive(Debug)] 27 | enum ParamsSource<'a> { 28 | Slice(&'a [(Name<'a>, Value<'a>)]), 29 | Indices(&'a str, &'a Indices), 30 | } 31 | 32 | impl<'a> Iterator for Params<'a> { 33 | type Item = (Name<'a>, Value<'a>); 34 | 35 | fn next(&mut self) -> Option { 36 | let index = self.index; 37 | match self.source { 38 | ParamsSource::Slice(s) => { 39 | if index >= s.len() { 40 | None 41 | } else { 42 | self.index += 1; 43 | Some((s[index].0, s[index].1)) 44 | } 45 | } 46 | ParamsSource::Indices(s, i) => { 47 | if index >= i.params().len() { 48 | None 49 | } else { 50 | self.index += 1; 51 | let param = i.params()[index]; 52 | Some(( 53 | Name::new_unchecked(&s[param[0]..param[1]]), 54 | Value::new_unchecked(&s[param[2]..param[3]]), 55 | )) 56 | } 57 | } 58 | } 59 | } 60 | 61 | fn size_hint(&self) -> (usize, Option) { 62 | let len = match self.source { 63 | ParamsSource::Slice(s) => s.len(), 64 | ParamsSource::Indices(_, i) => i.params().len(), 65 | }; 66 | (len, Some(len)) 67 | } 68 | } 69 | 70 | /// A trait for getting parameter values. 71 | pub trait ReadParams { 72 | /// Returns the parameters. 73 | fn params(&self) -> Params; 74 | 75 | /// Gets the parameter value by its name. 76 | /// 77 | /// If the same name appears more than once, returns the last value. 78 | fn get_param(&self, name: Name) -> Option; 79 | } 80 | 81 | /// A trait for mutating parameter values. 82 | pub trait WriteParams<'a>: ReadParams { 83 | /// Sets a parameter value. 84 | /// 85 | /// If the parameters with the name already exist, they will be removed. 86 | /// 87 | /// ``` 88 | /// # use mediatype::{names::*, values::*, MediaType, WriteParams}; 89 | /// let madia_type = "text/plain; charset=UTF-8; charset=US-ASCII; format=fixed"; 90 | /// 91 | /// let mut text_plain = MediaType::parse(madia_type).unwrap(); 92 | /// text_plain.set_param(CHARSET, UTF_8); 93 | /// 94 | /// assert_eq!( 95 | /// text_plain.to_string(), 96 | /// "text/plain; format=fixed; charset=UTF-8" 97 | /// ); 98 | /// ``` 99 | fn set_param<'n: 'a, 'v: 'a>(&mut self, name: Name<'n>, value: Value<'v>); 100 | 101 | /// Removes all parameters with the name. 102 | fn remove_params(&mut self, name: Name); 103 | 104 | /// Removes all parameters. 105 | fn clear_params(&mut self); 106 | } 107 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides two media type structs: [`MediaType`] and [`MediaTypeBuf`]. 2 | //! 3 | //! - [`MediaType`] does not copy data during parsing 4 | //! and borrows the original string. It is also const-constructible. 5 | //! - [`MediaTypeBuf`] is an owned and immutable version of [`MediaType`]. 6 | //! 7 | //! [`MadiaType`]: ./struct.MediaType.html 8 | //! [`MediaTypeBuf`]: ./struct.MediaTypeBuf.html 9 | //! 10 | //! ``` 11 | //! use mediatype::{names::*, MediaType, MediaTypeBuf}; 12 | //! 13 | //! const TEXT_PLAIN: MediaType = MediaType::new(TEXT, PLAIN); 14 | //! let text_plain: MediaTypeBuf = "text/plain".parse().unwrap(); 15 | //! 16 | //! assert_eq!(text_plain, TEXT_PLAIN); 17 | //! ``` 18 | //! 19 | //! # Letter case handling 20 | //! 21 | //! [`MediaType`] and [`MediaTypeBuf`] preserve the original string's letter case. 22 | //! Comparisons for [`MediaType`], [`MediaTypeBuf`] and [`Name`] are case-insensitive 23 | //! except parameter values. 24 | //! 25 | //! [`MadiaType`]: ./struct.MediaType.html 26 | //! [`MediaTypeBuf`]: ./struct.MediaTypeBuf.html 27 | //! [`Name`]: ./struct.Name.html 28 | //! 29 | //! ``` 30 | //! # use mediatype::{names::*, MediaType, MediaTypeBuf}; 31 | //! let lower: MediaTypeBuf = "text/plain; charset=UTF-8".parse().unwrap(); 32 | //! let upper: MediaTypeBuf = "TEXT/PLAIN; CHARSET=UTF-8".parse().unwrap(); 33 | //! 34 | //! assert_eq!(lower.as_str(), "text/plain; charset=UTF-8"); 35 | //! assert_eq!(upper.as_str(), "TEXT/PLAIN; CHARSET=UTF-8"); 36 | //! assert_eq!(lower, upper); 37 | //! assert_eq!(lower.ty(), "Text"); 38 | //! assert_eq!(upper.subty(), "Plain"); 39 | //! ``` 40 | 41 | #![no_std] 42 | #![forbid(unsafe_code)] 43 | #![forbid(clippy::all)] 44 | #![cfg_attr(docsrs, feature(doc_cfg))] 45 | 46 | extern crate alloc; 47 | 48 | #[cfg(test)] 49 | extern crate std; 50 | 51 | mod consts; 52 | mod error; 53 | mod media_type; 54 | mod media_type_buf; 55 | mod media_type_list; 56 | mod name; 57 | mod params; 58 | mod parse; 59 | mod serde; 60 | mod value; 61 | 62 | pub use consts::*; 63 | pub use error::*; 64 | pub use media_type::*; 65 | pub use media_type_buf::*; 66 | pub use media_type_list::*; 67 | pub use name::*; 68 | pub use params::*; 69 | pub use value::*; 70 | 71 | /// Convenient macro to construct a [`MediaType`]. 72 | /// 73 | /// [`MadiaType`]: ./struct.MediaType.html 74 | /// 75 | /// 76 | /// ``` 77 | /// # use mediatype::media_type; 78 | /// assert_eq!(media_type!(TEXT/PLAIN).to_string(), "text/plain"); 79 | /// assert_eq!(media_type!(IMAGE/x_::ICON).to_string(), "image/x-icon"); 80 | /// 81 | /// assert_eq!( 82 | /// media_type!(IMAGE/SVG+XML; CHARSET=UTF_8).to_string(), 83 | /// "image/svg+xml; charset=UTF-8" 84 | /// ); 85 | /// assert_eq!( 86 | /// media_type!(APPLICATION/vnd::OPENSTREETMAP_DATA+XML).to_string(), 87 | /// "application/vnd.openstreetmap.data+xml" 88 | /// ); 89 | /// ``` 90 | #[macro_export] 91 | macro_rules! media_type { 92 | ($ty:ident / $prefix:ident $(:: $subty:ident)* $(;$name:ident = $value:ident)*) => { 93 | const { 94 | $crate::MediaType::from_parts( 95 | $crate::names::$ty, 96 | $crate::names::$prefix $(::$subty)*, 97 | None, 98 | &[$(($crate::names::$name, $crate::values::$value)),*], 99 | ) 100 | } 101 | }; 102 | ($ty:ident / $prefix:ident $(:: $subty:ident)* + $suffix:ident $(;$name:ident = $value:ident)*) => { 103 | const { 104 | $crate::MediaType::from_parts( 105 | $crate::names::$ty, 106 | $crate::names::$prefix $(::$subty)*, 107 | Some($crate::names::$suffix), 108 | &[$(($crate::names::$name, $crate::values::$value)),*], 109 | ) 110 | } 111 | }; 112 | } 113 | -------------------------------------------------------------------------------- /tests/codegen.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Write, fs}; 2 | 3 | #[test] 4 | fn generated_code_is_fresh() { 5 | let mut fresh = generate_consts( 6 | "Name", 7 | "src/consts/names.txt", 8 | "src/consts/names.rs", 9 | &[ 10 | ("vnd.", "vnd", "Vendor subtypes starting with `vnd.`."), 11 | ("x-", "x_", "Unregistered subtypes starting with `x-`."), 12 | ("", "", ""), 13 | ], 14 | NAMES_HEADER, 15 | ); 16 | 17 | fresh = fresh 18 | && generate_consts( 19 | "Value", 20 | "src/consts/values.txt", 21 | "src/consts/values.rs", 22 | &[("", "", "")], 23 | VALUES_HEADER, 24 | ); 25 | 26 | if !fresh { 27 | panic!("generated code is not fresh, please commit updates"); 28 | } 29 | } 30 | 31 | fn generate_consts( 32 | ty: &str, 33 | input: &str, 34 | dst: &str, 35 | prefixes: &[(&str, &str, &str)], 36 | header: &str, 37 | ) -> bool { 38 | let mut prefixes = prefixes 39 | .iter() 40 | .map(|&(pf, name, comment)| (pf, name, comment, String::with_capacity(1024))) 41 | .collect::>(); 42 | 43 | let input = fs::read_to_string(input).expect("failed to read input file"); 44 | for line in input.lines() { 45 | let (ident, name) = if let Some(pair) = line.split_once('=') { 46 | pair 47 | } else { 48 | (line, line) 49 | }; 50 | 51 | if let Some((pf, _, _, out)) = prefixes.iter_mut().find(|(pf, ..)| ident.starts_with(pf)) { 52 | let ident = upper_snake_case(ident.trim_start_matches(*pf)); 53 | let indent = if pf.is_empty() { "" } else { " " }; 54 | writeln!(out, "{}/// `{}`", indent, name).unwrap(); 55 | writeln!( 56 | out, 57 | "{}pub const {}: crate::{} = crate::{}::new_unchecked(\"{}\");", 58 | indent, ident, ty, ty, name 59 | ) 60 | .unwrap(); 61 | } 62 | } 63 | 64 | let mut new = String::with_capacity(1024); 65 | new.push_str(header); 66 | new.push_str("\n\n"); 67 | for (pf, name, comment, out) in prefixes { 68 | if !pf.is_empty() { 69 | writeln!(new, "/// {}", comment).unwrap(); 70 | writeln!(new, "pub mod {} {{", name).unwrap(); 71 | } 72 | 73 | new.push_str(&out); 74 | 75 | if !pf.is_empty() { 76 | new.push_str("}\n\n"); 77 | } 78 | } 79 | 80 | let old = fs::read_to_string(dst).expect("failed to read destination file"); 81 | if old != new { 82 | fs::write(dst, &new).expect("failed to write destination file"); 83 | } 84 | 85 | old == new 86 | } 87 | 88 | fn upper_snake_case(s: &str) -> String { 89 | let s = s 90 | .split_inclusive(char::is_uppercase) 91 | .map(|chunk| { 92 | if chunk.ends_with(char::is_uppercase) { 93 | let prefix = chunk.trim_end_matches(char::is_uppercase); 94 | if prefix.ends_with(char::is_lowercase) { 95 | return format!("{}_{}", prefix, chunk.split_at(chunk.len() - 1).1); 96 | } 97 | } 98 | chunk.to_string() 99 | }) 100 | .collect::() 101 | .replace('+', "_plus") 102 | .replace(|c| !char::is_ascii_alphanumeric(&c), "_") 103 | .to_ascii_uppercase(); 104 | 105 | if s.starts_with(char::is_numeric) { 106 | format!("_{}", s) 107 | } else { 108 | s 109 | } 110 | } 111 | 112 | const NAMES_HEADER: &str = r#"//! Predefined names, @generated in tests/codegen.rs 113 | //! 114 | //! # Sources 115 | //! - 116 | //! - 117 | //! - 118 | //! - 119 | //! - 120 | //! - "#; 121 | 122 | const VALUES_HEADER: &str = r#"//! Predefined parameter values, @generated in tests/codegen.rs 123 | //! 124 | //! # Sources 125 | //! - 126 | //! - "#; 127 | -------------------------------------------------------------------------------- /src/name.rs: -------------------------------------------------------------------------------- 1 | use super::parse::*; 2 | use alloc::{borrow::Cow, fmt, string::String}; 3 | use core::{ 4 | cmp::Ordering, 5 | hash::{Hash, Hasher}, 6 | }; 7 | 8 | /// A media-type name. 9 | /// 10 | /// A valid name has the following requirements: 11 | /// 12 | /// - The first character must be an alphabet or a number. 13 | /// - The subsequent characters must be alphabets, numbers or `!#$&-^_.+%*'`. 14 | /// - Length of the name can not exceed 127 bytes. 15 | #[derive(Debug, Copy, Clone)] 16 | pub struct Name<'a>(&'a str); 17 | 18 | impl<'a> Name<'a> { 19 | /// Constructs a `Name`. 20 | /// 21 | /// If the string is not valid as a name, returns `None`. 22 | #[must_use] 23 | pub fn new(s: &'a str) -> Option { 24 | if is_restricted_name(s) { 25 | Some(Self(s)) 26 | } else { 27 | None 28 | } 29 | } 30 | 31 | /// Returns the underlying string. 32 | #[must_use] 33 | pub const fn as_str(&self) -> &'a str { 34 | self.0 35 | } 36 | 37 | /// The maximum byte length of a name. 38 | pub const MAX_LENGTH: usize = 127; 39 | 40 | /// Constructs a `Name` without validation. 41 | pub const fn new_unchecked(s: &'a str) -> Self { 42 | Self(s) 43 | } 44 | } 45 | 46 | impl fmt::Display for Name<'_> { 47 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 48 | f.write_str(self.0) 49 | } 50 | } 51 | 52 | impl AsRef for Name<'_> { 53 | fn as_ref(&self) -> &str { 54 | self.0 55 | } 56 | } 57 | 58 | impl PartialEq for Name<'_> { 59 | fn eq(&self, other: &Self) -> bool { 60 | self.0.eq_ignore_ascii_case(other.as_ref()) 61 | } 62 | } 63 | 64 | impl Eq for Name<'_> {} 65 | 66 | impl PartialOrd for Name<'_> { 67 | fn partial_cmp(&self, other: &Self) -> Option { 68 | Some(self.cmp(other)) 69 | } 70 | } 71 | 72 | impl Ord for Name<'_> { 73 | fn cmp(&self, other: &Self) -> Ordering { 74 | self.0 75 | .chars() 76 | .map(|c| c.to_ascii_lowercase()) 77 | .cmp(other.0.chars().map(|c| c.to_ascii_lowercase())) 78 | } 79 | } 80 | 81 | impl Hash for Name<'_> { 82 | fn hash(&self, state: &mut H) { 83 | self.0.to_ascii_lowercase().hash(state); 84 | } 85 | } 86 | 87 | impl PartialEq for Name<'_> { 88 | fn eq(&self, other: &String) -> bool { 89 | self.eq(other.as_str()) 90 | } 91 | } 92 | 93 | impl PartialOrd for Name<'_> { 94 | fn partial_cmp(&self, other: &String) -> Option { 95 | self.partial_cmp(other.as_str()) 96 | } 97 | } 98 | 99 | impl PartialEq<&String> for Name<'_> { 100 | fn eq(&self, other: &&String) -> bool { 101 | self.eq(other.as_str()) 102 | } 103 | } 104 | 105 | impl PartialOrd<&String> for Name<'_> { 106 | fn partial_cmp(&self, other: &&String) -> Option { 107 | self.partial_cmp(other.as_str()) 108 | } 109 | } 110 | 111 | impl PartialEq for Name<'_> { 112 | fn eq(&self, other: &str) -> bool { 113 | self.0.eq_ignore_ascii_case(other) 114 | } 115 | } 116 | 117 | impl PartialOrd for Name<'_> { 118 | fn partial_cmp(&self, other: &str) -> Option { 119 | Some( 120 | self.0 121 | .chars() 122 | .map(|c| c.to_ascii_lowercase()) 123 | .cmp(other.chars().map(|c| c.to_ascii_lowercase())), 124 | ) 125 | } 126 | } 127 | 128 | impl PartialEq<&str> for Name<'_> { 129 | fn eq(&self, other: &&str) -> bool { 130 | self.eq(*other) 131 | } 132 | } 133 | 134 | impl PartialOrd<&str> for Name<'_> { 135 | fn partial_cmp(&self, other: &&str) -> Option { 136 | self.partial_cmp(*other) 137 | } 138 | } 139 | 140 | impl PartialEq> for Name<'_> { 141 | fn eq(&self, other: &Cow<'_, str>) -> bool { 142 | self.eq(other.as_ref()) 143 | } 144 | } 145 | 146 | impl PartialOrd> for Name<'_> { 147 | fn partial_cmp(&self, other: &Cow<'_, str>) -> Option { 148 | self.partial_cmp(other.as_ref()) 149 | } 150 | } 151 | 152 | impl PartialEq<&Cow<'_, str>> for Name<'_> { 153 | fn eq(&self, other: &&Cow<'_, str>) -> bool { 154 | self.eq(other.as_ref()) 155 | } 156 | } 157 | 158 | impl PartialOrd<&Cow<'_, str>> for Name<'_> { 159 | fn partial_cmp(&self, other: &&Cow<'_, str>) -> Option { 160 | self.partial_cmp(other.as_ref()) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # MediaType 4 | 5 | MIME Media-type parsing for Rust 6 | 7 | [![Crates.io](https://img.shields.io/crates/v/mediatype.svg)](https://crates.io/crates/mediatype) 8 | [![dependency status](https://deps.rs/crate/mediatype/0.21.0/status.svg)](https://deps.rs/crate/mediatype/0.21.0) 9 | [![GitHub license](https://img.shields.io/github/license/picoHz/mediatype.svg)](https://github.com/picoHz/mediatype/blob/main/LICENSE) 10 | [![Rustdoc](https://img.shields.io/badge/doc-rustdoc-green.svg)](https://docs.rs/mediatype) 11 | ![Rust](https://github.com/picoHz/mediatype/workflows/Rust/badge.svg) 12 | 13 |
14 | 15 | - [Parsing](#parsing) 16 | - [Construction](#construction) 17 | - [Parameters](#parameters) 18 | - [Case Sensitivity](#case-sensitivity) 19 | - [Duplicate Parameter Names](#duplicate-parameter-names) 20 | - [Owned Type](#owned-type) 21 | - [MediaTypeList](#mediatypelist) 22 | - [Serialize and Deserialize](serialize-and-deserialize) 23 | 24 | ## Parsing 25 | 26 | `MediaType::parse` runs a zero-copy parsing: A `MediaType` borrows the input 27 | string instead of copying it. 28 | 29 | If you need an owned type, use [`MediaTypeBuf`](#owned-type). 30 | 31 | ```rust 32 | use mediatype::{names::*, values::*, MediaType}; 33 | 34 | let madia_type = "image/svg+xml; charset=UTF-8"; 35 | let svg = MediaType::parse(madia_type).unwrap(); 36 | 37 | assert_eq!(svg.ty, IMAGE); 38 | assert_eq!(svg.subty, SVG); 39 | assert_eq!(svg.suffix, Some(XML)); 40 | assert_eq!(svg.get_param(CHARSET), Some(UTF_8)); 41 | ``` 42 | 43 | ## Construction 44 | 45 | `MediaType` is const-constructible. It can be defined as a constant. 46 | 47 | Predefind names and values are defined in 48 | [`names`](https://docs.rs/mediatype/latest/mediatype/names/index.html) and 49 | [`values`](https://docs.rs/mediatype/latest/mediatype/values/index.html) 50 | modules. 51 | 52 | ```rust 53 | use mediatype::{names::*, values::*, media_type, MediaType}; 54 | 55 | const TEXT_PLAIN: MediaType = MediaType::new(TEXT, PLAIN); 56 | 57 | const IMAGE_SVG: MediaType = 58 | MediaType::from_parts(TEXT, PLAIN, Some(XML), &[(CHARSET, UTF_8)]); 59 | 60 | const TEXT_MARKDOWN: MediaType = 61 | media_type!(TEXT/MARKDOWN; CHARSET=UTF_8); 62 | ``` 63 | 64 | ## Parameters 65 | 66 | ### Case Sensitivity 67 | 68 | Comparisons are case-insensitive except parameter values. 69 | 70 | ```rust 71 | let text_plain_lower = MediaType::parse("text/plain; charset=UTF-8").unwrap(); 72 | let text_plain_upper = MediaType::parse("TEXT/PLAIN; CHARSET=UTF-8").unwrap(); 73 | 74 | assert_eq!(text_plain_lower, text_plain_upper); 75 | assert_eq!(text_plain_lower.ty(), "Text"); 76 | assert_eq!(text_plain_upper.subty(), "Plain"); 77 | assert!(text_plain_lower != 78 | MediaType::parse("text/plain; charset=utf-8").unwrap()); 79 | ``` 80 | 81 | ### Duplicate Parameter Names 82 | 83 | The parser does not report duplicate parameter names as an error, but 84 | `MediaType` recognizes only the last value. 85 | 86 | ```rust 87 | let text_plain = MediaType::parse( 88 | "text/plain; charset=US-ASCII; charset=UTF-8").unwrap(); 89 | 90 | assert_eq!( 91 | text_plain.to_string(), 92 | "text/plain; charset=US-ASCII; charset=UTF-8" 93 | ); 94 | 95 | // Return the last charset value. 96 | assert_eq!(text_plain.get_param(CHARSET), Some(UTF_8)); 97 | 98 | // Compare the last charset value. 99 | assert_eq!( 100 | text_plain, 101 | MediaType::parse("text/plain; charset=UTF-8").unwrap() 102 | ); 103 | ``` 104 | 105 | ## Owned Type 106 | 107 | [`MediaTypeBuf`](https://docs.rs/mediatype/latest/mediatype/struct.MediaTypeBuf.html) 108 | is an owned version of `MediaType`. It is immutable but optimized for minimal 109 | stack and heap usage. 110 | 111 | ```rust 112 | use mediatype::{names::*, values::*, MediaType, MediaTypeBuf}; 113 | 114 | let text_plain: MediaTypeBuf = "text/plain; charset=UTF-8".parse().unwrap(); 115 | assert_eq!(text_plain.get_param(CHARSET).unwrap(), UTF_8); 116 | 117 | // Convert to MediaType 118 | let mut text_markdown: MediaType = text_plain.to_ref(); 119 | text_markdown.subty = MARKDOWN; 120 | assert_eq!(text_markdown.to_string(), "text/markdown; charset=UTF-8"); 121 | ``` 122 | 123 | ## MediaTypeList 124 | 125 | [`MediaTypeList`](https://docs.rs/mediatype/latest/mediatype/struct.MediaTypeList.html) 126 | parses a comma-separated list of `MediaType`s used in the HTTP `Accept` header. 127 | ([RFC 7231](https://www.rfc-editor.org/rfc/rfc7231#section-5.3.2)) 128 | 129 | ```rust 130 | use mediatype::{MediaType, MediaTypeList}; 131 | 132 | let mut list = MediaTypeList::new( 133 | "text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8", 134 | ); 135 | assert_eq!(list.next(), Some(MediaType::parse("text/html"))); 136 | assert_eq!(list.next(), Some(MediaType::parse("application/xhtml+xml"))); 137 | assert_eq!(list.next(), Some(MediaType::parse("application/xml;q=0.9"))); 138 | assert_eq!(list.next(), Some(MediaType::parse("*/*;q=0.8"))); 139 | assert_eq!(list.next(), None); 140 | ``` 141 | 142 | ## Serialize and Deserialize 143 | 144 | To enable serialization and deserialization, specify `serde` feature in 145 | `Cargo.toml`. 146 | 147 | ```toml 148 | mediatype = { version = "...", features = ["serde"] } 149 | ``` 150 | 151 | ```rust 152 | let json = r#" 153 | [ 154 | "text/plain", 155 | "image/svg+xml; charset=UTF-8" 156 | ] 157 | "#; 158 | 159 | let decoded: Vec = serde_json::from_str(json).unwrap(); 160 | ``` 161 | -------------------------------------------------------------------------------- /src/media_type_list.rs: -------------------------------------------------------------------------------- 1 | use super::{error::*, media_type::*, parse::*}; 2 | 3 | /// A comma-separated list of `MediaType`s used in the HTTP `Accept` header. ([RFC 7231](https://www.rfc-editor.org/rfc/rfc7231#section-5.3.2)) 4 | /// 5 | /// ``` 6 | /// use mediatype::{MediaType, MediaTypeList}; 7 | /// 8 | /// let mut list = MediaTypeList::new( 9 | /// "text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8", 10 | /// ); 11 | /// assert_eq!(list.next(), Some(MediaType::parse("text/html"))); 12 | /// assert_eq!(list.next(), Some(MediaType::parse("application/xhtml+xml"))); 13 | /// assert_eq!(list.next(), Some(MediaType::parse("application/xml;q=0.9"))); 14 | /// assert_eq!(list.next(), Some(MediaType::parse("*/*;q=0.8"))); 15 | /// assert_eq!(list.next(), None); 16 | /// 17 | /// // Commas can be used in a quoted string. 18 | /// let mut list = MediaTypeList::new("text/html; message=\"Hello, world!\""); 19 | /// assert_eq!(list.next(), Some(MediaType::parse("text/html; message=\"Hello, world!\""))); 20 | /// assert_eq!(list.next(), None); 21 | /// ``` 22 | pub struct MediaTypeList<'a>(&'a str); 23 | 24 | impl<'a> MediaTypeList<'a> { 25 | /// Constructs a `MediaTypeList`. 26 | pub fn new(s: &'a str) -> Self { 27 | Self(s) 28 | } 29 | } 30 | 31 | impl<'a> Iterator for MediaTypeList<'a> { 32 | type Item = Result, MediaTypeError>; 33 | 34 | fn next(&mut self) -> Option { 35 | if let Some(index) = self.0.find(|c| !is_ows(c)) { 36 | self.0 = &self.0[index..]; 37 | } else { 38 | return None; 39 | } 40 | if self.0.is_empty() { 41 | return None; 42 | } 43 | let mut end = 0; 44 | let mut quoted = false; 45 | let mut escaped = false; 46 | while let Some(c) = self.0.as_bytes().get(end) { 47 | if escaped { 48 | escaped = false; 49 | } else { 50 | match c { 51 | b'"' => quoted = !quoted, 52 | b'\\' if quoted => escaped = true, 53 | b',' if !quoted => break, 54 | _ => (), 55 | } 56 | } 57 | end += 1; 58 | } 59 | let madia_type = MediaType::parse(&self.0[..end]); 60 | let end = self.0.len().min(end + 1); 61 | self.0 = &self.0[end..]; 62 | Some(madia_type) 63 | } 64 | 65 | fn size_hint(&self) -> (usize, Option) { 66 | (0, Some(self.0.matches(',').count() + 1)) 67 | } 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use super::*; 73 | use crate::params::ReadParams; 74 | 75 | #[test] 76 | fn empty() { 77 | let mut list = MediaTypeList::new(""); 78 | assert_eq!(list.next(), None); 79 | let mut list = MediaTypeList::new(" \t "); 80 | assert_eq!(list.next(), None); 81 | } 82 | 83 | #[test] 84 | fn invalid() { 85 | let mut list = MediaTypeList::new(",,,"); 86 | assert_eq!(list.next(), Some(MediaType::parse(""))); 87 | assert_eq!(list.next(), Some(MediaType::parse(""))); 88 | assert_eq!(list.next(), Some(MediaType::parse(""))); 89 | assert_eq!(list.next(), None); 90 | } 91 | 92 | #[test] 93 | fn simple() { 94 | let mut list = MediaTypeList::new("text/html"); 95 | assert_eq!(list.next(), Some(MediaType::parse("text/html"))); 96 | assert_eq!(list.next(), None); 97 | let mut list = MediaTypeList::new("image/*"); 98 | assert_eq!(list.next(), Some(MediaType::parse("image/*"))); 99 | assert_eq!(list.next(), None); 100 | let mut list = MediaTypeList::new("*/*"); 101 | assert_eq!(list.next(), Some(MediaType::parse("*/*"))); 102 | assert_eq!(list.next(), None); 103 | } 104 | 105 | #[test] 106 | fn list() { 107 | let mut list = MediaTypeList::new("text/html, image/*, */*"); 108 | assert_eq!(list.next(), Some(MediaType::parse("text/html"))); 109 | assert_eq!(list.next(), Some(MediaType::parse("image/*"))); 110 | assert_eq!(list.next(), Some(MediaType::parse("*/*"))); 111 | assert_eq!(list.next(), None); 112 | } 113 | 114 | #[test] 115 | fn params() { 116 | let mut list = MediaTypeList::new( 117 | "text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8", 118 | ); 119 | assert_eq!(list.next(), Some(MediaType::parse("text/html"))); 120 | assert_eq!(list.next(), Some(MediaType::parse("application/xhtml+xml"))); 121 | assert_eq!(list.next(), Some(MediaType::parse("application/xml;q=0.9"))); 122 | assert_eq!(list.next(), Some(MediaType::parse("*/*;q=0.8"))); 123 | assert_eq!(list.next(), None); 124 | } 125 | 126 | #[test] 127 | fn quoted_params() { 128 | let mut list = MediaTypeList::new("text/html; message=\"Hello, world!\", application/xhtml+xml; message=\"Hello, world?\""); 129 | 130 | let media_type = list.next(); 131 | assert_eq!( 132 | media_type, 133 | Some(MediaType::parse("text/html; message=\"Hello, world!\"")) 134 | ); 135 | assert_eq!( 136 | media_type 137 | .unwrap() 138 | .unwrap() 139 | .params() 140 | .next() 141 | .unwrap() 142 | .1 143 | .unquoted_str(), 144 | "Hello, world!" 145 | ); 146 | 147 | let media_type = list.next(); 148 | assert_eq!( 149 | media_type, 150 | Some(MediaType::parse( 151 | "application/xhtml+xml; message=\"Hello, world?\"" 152 | )) 153 | ); 154 | assert_eq!( 155 | media_type 156 | .unwrap() 157 | .unwrap() 158 | .params() 159 | .next() 160 | .unwrap() 161 | .1 162 | .unquoted_str(), 163 | "Hello, world?" 164 | ); 165 | 166 | assert_eq!(list.next(), None); 167 | } 168 | 169 | #[test] 170 | fn escaped_params() { 171 | let mut list = MediaTypeList::new("image/svg+xml; charset=\"UT\\\"F-8\""); 172 | assert_eq!( 173 | list.next(), 174 | Some(MediaType::parse("image/svg+xml; charset=\"UT\\\"F-8\"")) 175 | ); 176 | assert_eq!(list.next(), None); 177 | } 178 | 179 | #[test] 180 | fn size_hint() { 181 | let list = MediaTypeList::new(""); 182 | assert_eq!(list.size_hint(), (0, Some(1))); 183 | 184 | let list = MediaTypeList::new( 185 | "text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8", 186 | ); 187 | assert_eq!(list.size_hint(), (0, Some(4))); 188 | 189 | let list = MediaTypeList::new("text/html; message=\"Hello, world!\""); 190 | assert_eq!(list.size_hint(), (0, Some(2))); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/value.rs: -------------------------------------------------------------------------------- 1 | use super::parse::*; 2 | use alloc::{borrow::Cow, fmt, string::String, vec}; 3 | use core::{ 4 | cmp::Ordering, 5 | hash::{Hash, Hasher}, 6 | iter, 7 | }; 8 | 9 | /// A media-type parameter value. 10 | /// 11 | /// The constructor accepts both an unquoted string and a quoted string like `"Hello Wold!"`. 12 | /// If the string is not quoted, the allowed characters are 13 | /// alphabets, numbers and `!#$&-^_.+%*'`. 14 | /// 15 | /// ``` 16 | /// # use mediatype::Value; 17 | /// let utf8 = Value::new("UTF-8").unwrap(); 18 | /// let utf8_quoted = Value::new("\"UTF-8\"").unwrap(); 19 | /// assert_eq!(utf8, utf8_quoted); 20 | /// assert_eq!(utf8_quoted.as_str(), "\"UTF-8\""); 21 | /// assert_eq!(utf8_quoted.unquoted_str(), "UTF-8"); 22 | /// 23 | /// let double_quoted = Value::new("\" \\\" \"").unwrap(); 24 | /// assert_eq!(double_quoted.as_str(), "\" \\\" \""); 25 | /// assert_eq!(double_quoted.unquoted_str(), " \" "); 26 | /// ``` 27 | #[derive(Debug, Copy, Clone)] 28 | pub struct Value<'a>(&'a str); 29 | 30 | impl<'a> Value<'a> { 31 | /// Constructs a `Value`. 32 | /// 33 | /// If the string is not valid as a value, returns `None`. 34 | #[must_use] 35 | pub fn new(s: &'a str) -> Option { 36 | if let Some(quoted) = s.strip_prefix('\"') { 37 | if quoted.len() > 1 && parse_quoted_value(quoted) == Ok(quoted.len()) { 38 | return Some(Self(s)); 39 | } 40 | } else if is_restricted_str(s) { 41 | return Some(Self(s)); 42 | } 43 | None 44 | } 45 | 46 | /// Returns the underlying string. 47 | #[must_use] 48 | pub const fn as_str(&self) -> &'a str { 49 | self.0 50 | } 51 | 52 | /// Returns the unquoted string. 53 | #[must_use] 54 | pub fn unquoted_str(&self) -> Cow<'_, str> { 55 | if self.0.starts_with('"') { 56 | let inner = &self.0[1..self.0.len() - 1]; 57 | if inner.contains('\\') { 58 | let mut s = String::with_capacity(inner.len()); 59 | let mut quoted = false; 60 | for c in inner.chars() { 61 | match c { 62 | _ if quoted => { 63 | quoted = false; 64 | s.push(c); 65 | } 66 | '\\' => { 67 | quoted = true; 68 | } 69 | _ => { 70 | s.push(c); 71 | } 72 | } 73 | } 74 | Cow::Owned(s) 75 | } else { 76 | Cow::Borrowed(inner) 77 | } 78 | } else { 79 | Cow::Borrowed(self.0) 80 | } 81 | } 82 | 83 | /// Generates a quoted string if necessary. 84 | /// 85 | /// ``` 86 | /// # use mediatype::Value; 87 | /// assert_eq!(Value::quote("UTF-8"), "UTF-8"); 88 | /// assert_eq!(Value::quote("Hello world!"), "\"Hello world!\""); 89 | /// assert_eq!( 90 | /// Value::quote(r#" "What's wrong?" "#), 91 | /// "\" \\\"What's wrong\\?\\\" \"" 92 | /// ); 93 | /// ``` 94 | #[must_use] 95 | pub fn quote(s: &str) -> Cow<'_, str> { 96 | if is_restricted_str(s) { 97 | Cow::Borrowed(s) 98 | } else { 99 | let inner = s.chars().flat_map(|c| { 100 | if is_restricted_char(c) || c == ' ' { 101 | vec![c] 102 | } else { 103 | vec!['\\', c] 104 | } 105 | }); 106 | let quoted = iter::once('"').chain(inner).chain(iter::once('"')); 107 | Cow::Owned(quoted.collect()) 108 | } 109 | } 110 | 111 | pub(crate) const fn new_unchecked(s: &'a str) -> Self { 112 | Self(s) 113 | } 114 | } 115 | 116 | impl fmt::Display for Value<'_> { 117 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 118 | f.write_str(self.0) 119 | } 120 | } 121 | 122 | impl PartialEq for Value<'_> { 123 | fn eq(&self, other: &Self) -> bool { 124 | self.unquoted_str() == other.unquoted_str() 125 | } 126 | } 127 | 128 | impl Eq for Value<'_> {} 129 | 130 | impl PartialOrd for Value<'_> { 131 | fn partial_cmp(&self, other: &Self) -> Option { 132 | Some(self.cmp(other)) 133 | } 134 | } 135 | 136 | impl Ord for Value<'_> { 137 | fn cmp(&self, other: &Self) -> Ordering { 138 | self.unquoted_str().cmp(&other.unquoted_str()) 139 | } 140 | } 141 | 142 | impl Hash for Value<'_> { 143 | fn hash(&self, state: &mut H) { 144 | self.unquoted_str().hash(state); 145 | } 146 | } 147 | 148 | impl PartialEq for Value<'_> { 149 | fn eq(&self, other: &String) -> bool { 150 | self.eq(other.as_str()) 151 | } 152 | } 153 | 154 | impl PartialOrd for Value<'_> { 155 | fn partial_cmp(&self, other: &String) -> Option { 156 | self.partial_cmp(other.as_str()) 157 | } 158 | } 159 | 160 | impl PartialEq<&String> for Value<'_> { 161 | fn eq(&self, other: &&String) -> bool { 162 | self.eq(other.as_str()) 163 | } 164 | } 165 | 166 | impl PartialOrd<&String> for Value<'_> { 167 | fn partial_cmp(&self, other: &&String) -> Option { 168 | self.partial_cmp(other.as_str()) 169 | } 170 | } 171 | 172 | impl PartialEq for Value<'_> { 173 | fn eq(&self, other: &str) -> bool { 174 | self.unquoted_str() == other 175 | } 176 | } 177 | 178 | impl PartialOrd for Value<'_> { 179 | fn partial_cmp(&self, other: &str) -> Option { 180 | Some(self.unquoted_str().as_ref().cmp(other)) 181 | } 182 | } 183 | 184 | impl PartialEq<&str> for Value<'_> { 185 | fn eq(&self, other: &&str) -> bool { 186 | self.eq(*other) 187 | } 188 | } 189 | 190 | impl PartialOrd<&str> for Value<'_> { 191 | fn partial_cmp(&self, other: &&str) -> Option { 192 | self.partial_cmp(*other) 193 | } 194 | } 195 | 196 | impl PartialEq> for Value<'_> { 197 | fn eq(&self, other: &Cow<'_, str>) -> bool { 198 | self.eq(other.as_ref()) 199 | } 200 | } 201 | 202 | impl PartialOrd> for Value<'_> { 203 | fn partial_cmp(&self, other: &Cow<'_, str>) -> Option { 204 | self.partial_cmp(other.as_ref()) 205 | } 206 | } 207 | 208 | impl PartialEq<&Cow<'_, str>> for Value<'_> { 209 | fn eq(&self, other: &&Cow<'_, str>) -> bool { 210 | self.eq(other.as_ref()) 211 | } 212 | } 213 | 214 | impl PartialOrd<&Cow<'_, str>> for Value<'_> { 215 | fn partial_cmp(&self, other: &&Cow<'_, str>) -> Option { 216 | self.partial_cmp(other.as_ref()) 217 | } 218 | } 219 | 220 | #[cfg(test)] 221 | mod tests { 222 | use super::*; 223 | 224 | #[test] 225 | fn unquoted_str() { 226 | assert_eq!(Value::new("\"\\a\\\\\"").unwrap().unquoted_str(), "a\\"); 227 | assert_eq!(Value::new("\"\\\"\"").unwrap().unquoted_str(), "\""); 228 | assert_eq!(Value::new("\"\\a\\b\\c\"").unwrap().unquoted_str(), "abc"); 229 | assert_eq!( 230 | Value::new("\" \\\"What's wrong\\?\\\" \"") 231 | .unwrap() 232 | .unquoted_str(), 233 | r#" "What's wrong?" "# 234 | ); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use super::{error::*, name::*}; 2 | use alloc::{boxed::Box, vec::Vec}; 3 | use core::{num::NonZeroU8, ops::Range}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct Indices { 7 | ty: NonZeroU8, 8 | subty: NonZeroU8, 9 | suffix: u8, 10 | params: Box<[[usize; 4]]>, 11 | } 12 | 13 | impl Indices { 14 | pub const fn ty(&self) -> Range { 15 | 0..self.ty.get() as _ 16 | } 17 | 18 | pub const fn subty(&self) -> Range { 19 | let start = self.ty.get() as usize + 1; 20 | let end = start + self.subty.get() as usize; 21 | start..end 22 | } 23 | 24 | pub const fn suffix(&self) -> Option> { 25 | let start = self.ty.get() as usize + 1 + self.subty.get() as usize + 1; 26 | let end = start + self.suffix as usize; 27 | if start < end { 28 | Some(start..end) 29 | } else { 30 | None 31 | } 32 | } 33 | 34 | pub const fn params(&self) -> &[[usize; 4]] { 35 | &self.params 36 | } 37 | 38 | pub fn parse(s: &str) -> Result<(Self, usize), MediaTypeError> { 39 | let (ty, right) = match s.bytes().take(Name::MAX_LENGTH + 1).position(|b| b == b'/') { 40 | Some(slash) => (&s[..slash], &s[slash + 1..]), 41 | None => { 42 | return Err(MediaTypeError::InvalidTypeName); 43 | } 44 | }; 45 | 46 | if !is_restricted_name(ty) { 47 | return Err(MediaTypeError::InvalidTypeName); 48 | } 49 | 50 | let suffix_end = right 51 | .find(|c: char| !is_restricted_char(c)) 52 | .unwrap_or(right.len()); 53 | let suffix_start = right[..suffix_end].rfind('+'); 54 | 55 | let (subty, suffix) = suffix_start.map_or_else( 56 | || (&right[..suffix_end], ""), 57 | |suffix_start| (&right[..suffix_start], &right[suffix_start + 1..suffix_end]), 58 | ); 59 | 60 | if !is_restricted_name(subty) { 61 | return Err(MediaTypeError::InvalidSubtypeName); 62 | } 63 | 64 | if !suffix.is_empty() && !is_restricted_name(&suffix[1..]) { 65 | return Err(MediaTypeError::InvalidSuffix); 66 | } 67 | 68 | let sub_end = ty.len() + 1 + subty.len(); 69 | let params_start = sub_end 70 | + if suffix.is_empty() { 71 | 0 72 | } else { 73 | suffix.len() + 1 74 | }; 75 | 76 | let (mut params, params_len) = parse_params(&s[params_start..])?; 77 | for elem in &mut params { 78 | for v in elem.iter_mut() { 79 | *v += params_start; 80 | } 81 | } 82 | 83 | Ok(( 84 | Self { 85 | ty: NonZeroU8::new(ty.len().try_into().unwrap()).unwrap(), 86 | subty: NonZeroU8::new(subty.len().try_into().unwrap()).unwrap(), 87 | suffix: suffix.len().try_into().unwrap(), 88 | params: params.into_boxed_slice(), 89 | }, 90 | params_start + params_len, 91 | )) 92 | } 93 | } 94 | 95 | #[cfg(test)] 96 | fn parse_to_string(s: &str) -> Result { 97 | use core::fmt::Write; 98 | 99 | let mut out = alloc::string::String::new(); 100 | let (indices, _) = Indices::parse(s)?; 101 | 102 | write!(out, "{}/{}", &s[indices.ty()], &s[indices.subty()]).unwrap(); 103 | if let Some(suffix) = indices.suffix() { 104 | write!(out, "+{}", &s[suffix]).unwrap(); 105 | } 106 | for param in indices.params() { 107 | write!( 108 | out, 109 | "; {}={}", 110 | &s[param[0]..param[1]], 111 | &s[param[2]..param[3]] 112 | ) 113 | .unwrap(); 114 | } 115 | 116 | Ok(out) 117 | } 118 | 119 | pub fn is_restricted_name(s: &str) -> bool { 120 | s.len() <= Name::MAX_LENGTH 121 | && s.starts_with(|c: char| c.is_ascii_alphanumeric() || c == '*') 122 | && is_restricted_str(s) 123 | } 124 | 125 | pub fn is_restricted_str(s: &str) -> bool { 126 | s.chars().all(is_restricted_char) 127 | } 128 | 129 | pub fn is_restricted_char(c: char) -> bool { 130 | c.is_ascii_alphanumeric() 131 | || matches!( 132 | c, 133 | '!' | '#' | '$' | '&' | '-' | '^' | '_' | '.' | '+' | '%' | '*' | '\'' 134 | ) 135 | } 136 | 137 | pub const fn is_ows(c: char) -> bool { 138 | c == ' ' || c == '\t' 139 | } 140 | 141 | fn parse_params(s: &str) -> Result<(Vec<[usize; 4]>, usize), MediaTypeError> { 142 | let mut vec = Vec::new(); 143 | let mut offset = 0; 144 | let mut len = 0; 145 | 146 | while let Some((name, value)) = parse_param(&s[offset..])? { 147 | vec.push([ 148 | offset + name.start, 149 | offset + name.end, 150 | offset + value.start, 151 | offset + value.end, 152 | ]); 153 | len = offset + value.end; 154 | offset += value.end; 155 | } 156 | 157 | Ok((vec, len)) 158 | } 159 | 160 | type ParamRange = (Range, Range); 161 | 162 | fn parse_param(s: &str) -> Result, MediaTypeError> { 163 | let (ows, right) = match s.split_once(';') { 164 | Some((ows, right)) if ows.chars().all(is_ows) && right.chars().all(is_ows) => { 165 | return Ok(None) 166 | } 167 | Some((ows, right)) if ows.chars().all(is_ows) => (ows, right), 168 | _ if s.chars().all(is_ows) => return Ok(None), 169 | _ => return Err(MediaTypeError::InvalidParams), 170 | }; 171 | 172 | let (name, value) = match right.split_once('=') { 173 | Some(pair) => pair, 174 | _ => return Err(MediaTypeError::InvalidParams), 175 | }; 176 | 177 | let key_trimmed = name.trim_start_matches(is_ows).len(); 178 | let key_start = ows.len() + 1 + name.len() - key_trimmed; 179 | let key_range = key_start..key_start + key_trimmed; 180 | if !is_restricted_name(&s[key_range.clone()]) { 181 | return Err(MediaTypeError::InvalidParamName); 182 | } 183 | 184 | let value_start = key_range.end + 1; 185 | if let Some(value) = value.strip_prefix('\"') { 186 | let value_end = value_start + parse_quoted_value(value)? + 1; 187 | let value_range = value_start..value_end; 188 | Ok(Some((key_range, value_range))) 189 | } else { 190 | let value_end = value_start 191 | + value 192 | .chars() 193 | .take_while(|&c| is_restricted_char(c)) 194 | .map(char::len_utf8) 195 | .sum::(); 196 | let value_range = value_start..value_end; 197 | Ok(Some((key_range, value_range))) 198 | } 199 | } 200 | 201 | pub fn parse_quoted_value(s: &str) -> Result { 202 | let mut len = 0; 203 | let mut escaped = false; 204 | for c in s.chars() { 205 | len += c.len_utf8(); 206 | match c { 207 | _ if escaped => { 208 | escaped = false; 209 | } 210 | '\\' => { 211 | escaped = true; 212 | } 213 | '"' => return Ok(len), 214 | '\n' => return Err(MediaTypeError::InvalidParamValue), 215 | _ => (), 216 | } 217 | } 218 | Err(MediaTypeError::InvalidParamValue) 219 | } 220 | 221 | #[cfg(test)] 222 | mod tests { 223 | use super::*; 224 | 225 | #[test] 226 | fn parse() { 227 | assert_eq!(parse_to_string("*/*"), Ok("*/*".into())); 228 | assert_eq!(parse_to_string("text/plain"), Ok("text/plain".into())); 229 | assert_eq!(parse_to_string("text/plain;"), Ok("text/plain".into())); 230 | assert_eq!(parse_to_string("image/svg+xml"), Ok("image/svg+xml".into())); 231 | assert_eq!( 232 | parse_to_string("image/svg+xml;"), 233 | Ok("image/svg+xml".into()) 234 | ); 235 | assert_eq!( 236 | parse_to_string("image/svg+xml; charset=UTF-8"), 237 | Ok("image/svg+xml; charset=UTF-8".into()) 238 | ); 239 | assert_eq!( 240 | parse_to_string("image/svg+xml; charset=UTF-8;"), 241 | Ok("image/svg+xml; charset=UTF-8".into()) 242 | ); 243 | assert_eq!( 244 | parse_to_string("image/svg+xml; charset=US-ASCII; charset=UTF-8;"), 245 | Ok("image/svg+xml; charset=US-ASCII; charset=UTF-8".into()) 246 | ); 247 | assert_eq!( 248 | parse_to_string("image/svg+xml; charset=US-ASCII; hello=WORLD; charset=UTF-8;"), 249 | Ok("image/svg+xml; charset=US-ASCII; hello=WORLD; charset=UTF-8".into()) 250 | ); 251 | assert_eq!( 252 | parse_to_string("image/svg+xml ; charset=UTF-8 "), 253 | Ok("image/svg+xml; charset=UTF-8".into()) 254 | ); 255 | assert_eq!( 256 | parse_to_string("image/svg+xml; charset=\"UTF-8\""), 257 | Ok("image/svg+xml; charset=\"UTF-8\"".into()) 258 | ); 259 | assert_eq!( 260 | parse_to_string("image/svg+xml; charset=\"UT\\\"F-8\""), 261 | Ok("image/svg+xml; charset=\"UT\\\"F-8\"".into()) 262 | ); 263 | assert_eq!( 264 | parse_to_string("multipart/form-data ; boundary=--boundary13234"), 265 | Ok("multipart/form-data; boundary=--boundary13234".into()) 266 | ); 267 | 268 | let s = "text/plain"; 269 | let long_str = [s, ";", &" ".repeat(u16::MAX as usize - 2 - s.len())].concat(); 270 | assert_eq!(parse_to_string(&long_str), Ok("text/plain".into())); 271 | 272 | let long_name = "a".repeat(Name::MAX_LENGTH); 273 | let long_str = [&long_name, "/", &long_name, "+", &long_name].concat(); 274 | assert_eq!(parse_to_string(&long_str), Ok(long_str)); 275 | } 276 | 277 | #[test] 278 | fn parse_error() { 279 | assert_eq!(parse_to_string(""), Err(MediaTypeError::InvalidTypeName)); 280 | assert_eq!( 281 | parse_to_string("textplain"), 282 | Err(MediaTypeError::InvalidTypeName) 283 | ); 284 | assert_eq!( 285 | parse_to_string("text//plain"), 286 | Err(MediaTypeError::InvalidSubtypeName) 287 | ); 288 | assert_eq!( 289 | parse_to_string(" text/plain"), 290 | Err(MediaTypeError::InvalidTypeName) 291 | ); 292 | assert_eq!( 293 | parse_to_string("text/plain; charsetUTF-8"), 294 | Err(MediaTypeError::InvalidParams) 295 | ); 296 | assert_eq!( 297 | parse_to_string("text/plain;;"), 298 | Err(MediaTypeError::InvalidParams) 299 | ); 300 | assert_eq!( 301 | parse_to_string("text/plain;;;"), 302 | Err(MediaTypeError::InvalidParams) 303 | ); 304 | assert_eq!( 305 | parse_to_string("text/plain; charset=\"UTF-8"), 306 | Err(MediaTypeError::InvalidParamValue) 307 | ); 308 | assert_eq!( 309 | parse_to_string("text/plain; charset==UTF-8"), 310 | Err(MediaTypeError::InvalidParams) 311 | ); 312 | assert_eq!( 313 | parse_to_string("text/plain; \r\n charset=UTF-8;"), 314 | Err(MediaTypeError::InvalidParamName) 315 | ); 316 | 317 | let long_str = [&"t".repeat(u16::MAX as usize), "/plain"].concat(); 318 | assert_eq!( 319 | parse_to_string(&long_str), 320 | Err(MediaTypeError::InvalidTypeName) 321 | ); 322 | let multibyte_str = "a\u{FFFF}".repeat(Name::MAX_LENGTH); 323 | assert_eq!( 324 | parse_to_string(&multibyte_str), 325 | Err(MediaTypeError::InvalidTypeName) 326 | ); 327 | 328 | assert_eq!( 329 | parse_to_string("текст/plain"), 330 | Err(MediaTypeError::InvalidTypeName) 331 | ); 332 | assert_eq!( 333 | parse_to_string("text/plain; кодування=UTF-8"), 334 | Err(MediaTypeError::InvalidParamName) 335 | ); 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /src/media_type.rs: -------------------------------------------------------------------------------- 1 | use super::{error::*, media_type_buf::*, name::*, params::*, parse::*, value::*}; 2 | use alloc::{borrow::Cow, collections::BTreeMap, vec::Vec}; 3 | use core::{ 4 | fmt, 5 | hash::{Hash, Hasher}, 6 | }; 7 | 8 | /// A borrowed media type. 9 | /// 10 | /// ``` 11 | /// use mediatype::{names::*, MediaType, Value, WriteParams}; 12 | /// 13 | /// let mut multipart = MediaType::new(MULTIPART, FORM_DATA); 14 | /// 15 | /// let boundary = Value::new("dyEV84n7XNJ").unwrap(); 16 | /// multipart.set_param(BOUNDARY, boundary); 17 | /// assert_eq!( 18 | /// multipart.to_string(), 19 | /// "multipart/form-data; boundary=dyEV84n7XNJ" 20 | /// ); 21 | /// 22 | /// multipart.subty = RELATED; 23 | /// assert_eq!( 24 | /// multipart.to_string(), 25 | /// "multipart/related; boundary=dyEV84n7XNJ" 26 | /// ); 27 | /// 28 | /// const IMAGE_SVG: MediaType = MediaType::from_parts(IMAGE, SVG, Some(XML), &[]); 29 | /// let svg = MediaType::parse("IMAGE/SVG+XML").unwrap(); 30 | /// assert_eq!(svg, IMAGE_SVG); 31 | /// ``` 32 | #[derive(Debug, Clone)] 33 | pub struct MediaType<'a> { 34 | /// Top-level type. 35 | pub ty: Name<'a>, 36 | 37 | /// Subtype. 38 | pub subty: Name<'a>, 39 | 40 | /// Optional suffix. 41 | pub suffix: Option>, 42 | 43 | /// Parameters. 44 | pub params: Cow<'a, [(Name<'a>, Value<'a>)]>, 45 | } 46 | 47 | impl<'a> MediaType<'a> { 48 | /// Constructs a `MediaType` from a top-level type and a subtype. 49 | /// ``` 50 | /// # use mediatype::{names::*, MediaType}; 51 | /// const IMAGE_PNG: MediaType = MediaType::new(IMAGE, PNG); 52 | /// assert_eq!(IMAGE_PNG, MediaType::parse("image/png").unwrap()); 53 | /// ``` 54 | #[must_use] 55 | pub const fn new(ty: Name<'a>, subty: Name<'a>) -> Self { 56 | Self { 57 | ty, 58 | subty, 59 | suffix: None, 60 | params: Cow::Borrowed(&[]), 61 | } 62 | } 63 | 64 | /// Constructs a `MediaType` with an optional suffix and parameters. 65 | /// 66 | /// ``` 67 | /// # use mediatype::{names::*, values::*, MediaType}; 68 | /// const IMAGE_SVG: MediaType = MediaType::from_parts(IMAGE, SVG, Some(XML), &[(CHARSET, UTF_8)]); 69 | /// assert_eq!( 70 | /// IMAGE_SVG, 71 | /// MediaType::parse("image/svg+xml; charset=UTF-8").unwrap() 72 | /// ); 73 | /// ``` 74 | #[must_use] 75 | pub const fn from_parts( 76 | ty: Name<'a>, 77 | subty: Name<'a>, 78 | suffix: Option>, 79 | params: &'a [(Name<'a>, Value<'a>)], 80 | ) -> Self { 81 | Self { 82 | ty, 83 | subty, 84 | suffix, 85 | params: Cow::Borrowed(params), 86 | } 87 | } 88 | 89 | pub(crate) const fn from_parts_unchecked( 90 | ty: Name<'a>, 91 | subty: Name<'a>, 92 | suffix: Option>, 93 | params: Cow<'a, [(Name<'a>, Value<'a>)]>, 94 | ) -> Self { 95 | Self { 96 | ty, 97 | subty, 98 | suffix, 99 | params, 100 | } 101 | } 102 | 103 | /// Constructs a `MediaType` from `str` without copying the string. 104 | /// 105 | /// # Errors 106 | /// 107 | /// Returns an error if the string fails to be parsed. 108 | pub fn parse<'s: 'a>(s: &'s str) -> Result { 109 | let (indices, _) = Indices::parse(s)?; 110 | let params = indices 111 | .params() 112 | .iter() 113 | .map(|param| { 114 | ( 115 | Name::new_unchecked(&s[param[0]..param[1]]), 116 | Value::new_unchecked(&s[param[2]..param[3]]), 117 | ) 118 | }) 119 | .collect(); 120 | Ok(Self { 121 | ty: Name::new_unchecked(&s[indices.ty()]), 122 | subty: Name::new_unchecked(&s[indices.subty()]), 123 | suffix: indices.suffix().map(|range| Name::new_unchecked(&s[range])), 124 | params: Cow::Owned(params), 125 | }) 126 | } 127 | 128 | /// Returns a [`MediaType`] without parameters. 129 | /// 130 | /// ``` 131 | /// # use mediatype::{names::*, values::*, MediaType}; 132 | /// const IMAGE_SVG: MediaType = MediaType::from_parts(IMAGE, SVG, Some(XML), &[(CHARSET, UTF_8)]); 133 | /// assert_eq!( 134 | /// IMAGE_SVG.essence(), 135 | /// MediaType::parse("image/svg+xml").unwrap() 136 | /// ); 137 | /// ``` 138 | /// 139 | /// [`MadiaType`]: ./struct.MediaType.html 140 | #[must_use] 141 | pub const fn essence(&self) -> MediaType<'_> { 142 | MediaType::from_parts(self.ty, self.subty, self.suffix, &[]) 143 | } 144 | } 145 | 146 | impl ReadParams for MediaType<'_> { 147 | fn params(&self) -> Params { 148 | Params::from_slice(&self.params) 149 | } 150 | 151 | fn get_param(&self, name: Name) -> Option { 152 | self.params 153 | .iter() 154 | .rev() 155 | .find(|&¶m| name == param.0) 156 | .map(|&(_, value)| value) 157 | } 158 | } 159 | 160 | impl<'a> WriteParams<'a> for MediaType<'a> { 161 | fn set_param<'n: 'a, 'v: 'a>(&mut self, name: Name<'n>, value: Value<'v>) { 162 | self.remove_params(name); 163 | let params = self.params.to_mut(); 164 | params.push((name, value)); 165 | } 166 | 167 | fn remove_params(&mut self, name: Name) { 168 | let key_exists = self.params.iter().any(|¶m| name == param.0); 169 | if key_exists { 170 | self.params.to_mut().retain(|¶m| name != param.0); 171 | } 172 | } 173 | 174 | fn clear_params(&mut self) { 175 | if !self.params.is_empty() { 176 | self.params.to_mut().clear(); 177 | } 178 | } 179 | } 180 | 181 | impl fmt::Display for MediaType<'_> { 182 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 183 | write!(f, "{}/{}", self.ty, self.subty)?; 184 | if let Some(suffix) = self.suffix { 185 | write!(f, "+{}", suffix)?; 186 | } 187 | for (name, value) in &*self.params { 188 | write!(f, "; {}={}", name, value)?; 189 | } 190 | Ok(()) 191 | } 192 | } 193 | 194 | impl<'a> From<&'a MediaTypeBuf> for MediaType<'a> { 195 | fn from(t: &'a MediaTypeBuf) -> Self { 196 | t.to_ref() 197 | } 198 | } 199 | 200 | impl<'b> PartialEq> for MediaType<'_> { 201 | fn eq(&self, other: &MediaType<'b>) -> bool { 202 | self.ty == other.ty 203 | && self.subty == other.subty 204 | && self.suffix == other.suffix 205 | && self.params().collect::>() 206 | == other.params().collect::>() 207 | } 208 | } 209 | 210 | impl Eq for MediaType<'_> {} 211 | 212 | impl PartialEq for MediaType<'_> { 213 | fn eq(&self, other: &MediaTypeBuf) -> bool { 214 | self.ty == other.ty() 215 | && self.subty == other.subty() 216 | && self.suffix == other.suffix() 217 | && self.params().collect::>() 218 | == other.params().collect::>() 219 | } 220 | } 221 | 222 | impl PartialEq<&MediaTypeBuf> for MediaType<'_> { 223 | fn eq(&self, other: &&MediaTypeBuf) -> bool { 224 | self == *other 225 | } 226 | } 227 | 228 | impl Hash for MediaType<'_> { 229 | fn hash(&self, state: &mut H) { 230 | self.ty.hash(state); 231 | self.subty.hash(state); 232 | self.suffix.hash(state); 233 | self.params() 234 | .collect::>() 235 | .into_iter() 236 | .collect::>() 237 | .hash(state); 238 | } 239 | } 240 | 241 | #[cfg(test)] 242 | mod tests { 243 | use super::*; 244 | use crate::{names::*, values::*}; 245 | use alloc::string::ToString; 246 | use core::str::FromStr; 247 | use std::collections::hash_map::DefaultHasher; 248 | 249 | fn calculate_hash(t: &T) -> u64 { 250 | let mut s = DefaultHasher::new(); 251 | t.hash(&mut s); 252 | s.finish() 253 | } 254 | 255 | #[test] 256 | fn to_string() { 257 | assert_eq!(MediaType::new(_STAR, _STAR).to_string(), "*/*"); 258 | assert_eq!(MediaType::new(TEXT, PLAIN).to_string(), "text/plain"); 259 | assert_eq!( 260 | MediaType::from_parts(IMAGE, SVG, Some(XML), &[]).to_string(), 261 | "image/svg+xml" 262 | ); 263 | assert_eq!( 264 | MediaType::from_parts(TEXT, PLAIN, None, &[(CHARSET, UTF_8)]).to_string(), 265 | "text/plain; charset=UTF-8" 266 | ); 267 | assert_eq!( 268 | MediaType::from_parts(IMAGE, SVG, Some(XML), &[(CHARSET, UTF_8)]).to_string(), 269 | "image/svg+xml; charset=UTF-8" 270 | ); 271 | } 272 | 273 | #[test] 274 | fn get_param() { 275 | assert_eq!(MediaType::new(TEXT, PLAIN).get_param(CHARSET), None); 276 | assert_eq!( 277 | MediaType::from_parts(TEXT, PLAIN, None, &[(CHARSET, UTF_8)]).get_param(CHARSET), 278 | Some(UTF_8) 279 | ); 280 | assert_eq!( 281 | MediaType::parse("image/svg+xml; charset=UTF-8; HELLO=WORLD; HELLO=world") 282 | .unwrap() 283 | .get_param(Name::new("hello").unwrap()), 284 | Some(Value::new("world").unwrap()) 285 | ); 286 | } 287 | 288 | #[test] 289 | fn set_param() { 290 | let mut media_type = MediaType::from_parts(TEXT, PLAIN, None, &[(CHARSET, UTF_8)]); 291 | let lower_utf8 = Value::new("utf-8").unwrap(); 292 | media_type.set_param(CHARSET, lower_utf8); 293 | assert_eq!(media_type.to_string(), "text/plain; charset=utf-8"); 294 | 295 | let alice = Name::new("ALICE").unwrap(); 296 | let bob = Value::new("bob").unwrap(); 297 | media_type.set_param(alice, bob); 298 | media_type.set_param(alice, bob); 299 | 300 | assert_eq!( 301 | media_type.to_string(), 302 | "text/plain; charset=utf-8; ALICE=bob" 303 | ); 304 | } 305 | 306 | #[test] 307 | fn remove_params() { 308 | let mut media_type = MediaType::from_parts(TEXT, PLAIN, None, &[(CHARSET, UTF_8)]); 309 | media_type.remove_params(CHARSET); 310 | assert_eq!(media_type.to_string(), "text/plain"); 311 | 312 | let mut media_type = 313 | MediaType::parse("image/svg+xml; hello=WORLD; charset=UTF-8; HELLO=WORLD").unwrap(); 314 | media_type.remove_params(Name::new("hello").unwrap()); 315 | assert_eq!(media_type.to_string(), "image/svg+xml; charset=UTF-8"); 316 | } 317 | 318 | #[test] 319 | fn clear_params() { 320 | let mut media_type = MediaType::parse("image/svg+xml; charset=UTF-8; HELLO=WORLD").unwrap(); 321 | media_type.clear_params(); 322 | assert_eq!(media_type.to_string(), "image/svg+xml"); 323 | } 324 | 325 | #[test] 326 | fn cmp() { 327 | assert_eq!( 328 | MediaType::parse("text/plain").unwrap(), 329 | MediaType::parse("TEXT/PLAIN").unwrap() 330 | ); 331 | assert_eq!( 332 | MediaType::parse("image/svg+xml; charset=UTF-8").unwrap(), 333 | MediaType::parse("IMAGE/SVG+XML; CHARSET=UTF-8").unwrap() 334 | ); 335 | assert_eq!( 336 | MediaType::parse("image/svg+xml; hello=WORLD; charset=UTF-8").unwrap(), 337 | MediaType::parse("IMAGE/SVG+XML; HELLO=WORLD; CHARSET=UTF-8").unwrap() 338 | ); 339 | assert_eq!( 340 | MediaType::from_parts( 341 | IMAGE, 342 | SVG, 343 | Some(XML), 344 | &[(CHARSET, US_ASCII), (CHARSET, UTF_8)] 345 | ), 346 | MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8").unwrap(), 347 | ); 348 | 349 | const TEXT_PLAIN: MediaType = MediaType::from_parts(TEXT, PLAIN, None, &[]); 350 | let text_plain = MediaType::parse("text/plain").unwrap(); 351 | assert_eq!(text_plain.essence(), TEXT_PLAIN); 352 | } 353 | 354 | #[test] 355 | fn hash() { 356 | assert_eq!( 357 | calculate_hash(&MediaType::parse("text/plain").unwrap()), 358 | calculate_hash(&MediaType::parse("TEXT/PLAIN").unwrap()) 359 | ); 360 | assert_eq!( 361 | calculate_hash(&MediaType::parse("image/svg+xml; charset=UTF-8").unwrap()), 362 | calculate_hash(&MediaType::parse("IMAGE/SVG+XML; CHARSET=UTF-8").unwrap()) 363 | ); 364 | assert_eq!( 365 | calculate_hash(&MediaType::parse("image/svg+xml; hello=WORLD; charset=UTF-8").unwrap()), 366 | calculate_hash(&MediaType::parse("IMAGE/SVG+XML; HELLO=WORLD; CHARSET=UTF-8").unwrap()) 367 | ); 368 | assert_eq!( 369 | calculate_hash(&MediaType::from_parts( 370 | IMAGE, 371 | SVG, 372 | Some(XML), 373 | &[(CHARSET, UTF_8)] 374 | )), 375 | calculate_hash(&MediaType::from_parts( 376 | IMAGE, 377 | SVG, 378 | Some(XML), 379 | &[(CHARSET, US_ASCII), (CHARSET, UTF_8)] 380 | )), 381 | ); 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /src/media_type_buf.rs: -------------------------------------------------------------------------------- 1 | use super::{error::*, media_type::*, name::*, params::*, parse::*, value::*}; 2 | use alloc::{ 3 | borrow::Cow, 4 | boxed::Box, 5 | collections::BTreeMap, 6 | string::{String, ToString}, 7 | vec::Vec, 8 | }; 9 | use core::{ 10 | fmt, 11 | hash::{Hash, Hasher}, 12 | str::FromStr, 13 | }; 14 | 15 | /// An owned and immutable media type. 16 | /// 17 | /// ``` 18 | /// use mediatype::{names::*, values::*, MediaType, MediaTypeBuf, ReadParams}; 19 | /// 20 | /// let text_plain: MediaTypeBuf = "text/plain; charset=UTF-8".parse().unwrap(); 21 | /// assert_eq!(text_plain.get_param(CHARSET).unwrap(), UTF_8); 22 | /// 23 | /// let mut text_markdown: MediaType = text_plain.to_ref(); 24 | /// text_markdown.subty = MARKDOWN; 25 | /// assert_eq!(text_markdown.to_string(), "text/markdown; charset=UTF-8"); 26 | /// ``` 27 | #[derive(Debug, Clone)] 28 | pub struct MediaTypeBuf { 29 | data: Box, 30 | indices: Indices, 31 | } 32 | 33 | impl MediaTypeBuf { 34 | /// Constructs a `MediaTypeBuf` from a top-level type and a subtype. 35 | #[must_use] 36 | pub fn new(ty: Name, subty: Name) -> Self { 37 | Self::from_string([ty.as_str(), "/", subty.as_str()].concat()) 38 | .expect("`ty` and `subty` should be valid") 39 | } 40 | 41 | /// Constructs a `MediaTypeBuf` with an optional suffix and parameters. 42 | #[must_use] 43 | pub fn from_parts( 44 | ty: Name, 45 | subty: Name, 46 | suffix: Option, 47 | params: &[(Name, Value)], 48 | ) -> Self { 49 | use core::fmt::Write; 50 | let mut s = String::new(); 51 | write!(s, "{}/{}", ty, subty).expect("`ty` and `subty` should be valid"); 52 | if let Some(suffix) = suffix { 53 | write!(s, "+{}", suffix).unwrap(); 54 | } 55 | for (name, value) in params { 56 | write!(s, "; {}={}", name, value).unwrap(); 57 | } 58 | Self::from_string(s).expect("all values should be valid") 59 | } 60 | 61 | /// Constructs a `MediaTypeBuf` from [`String`]. 62 | /// 63 | /// Unlike [`FromStr::from_str`], this function takes the ownership of [`String`] 64 | /// instead of making a new copy. 65 | /// 66 | /// # Errors 67 | /// 68 | /// Returns an error if the string fails to be parsed. 69 | /// 70 | /// [`String`]: https://doc.rust-lang.org/std/string/struct.String.html 71 | /// [`FromStr::from_str`]: https://doc.rust-lang.org/std/str/trait.FromStr.html 72 | pub fn from_string(mut s: String) -> Result { 73 | let (indices, len) = Indices::parse(&s)?; 74 | s.truncate(len); 75 | Ok(Self { 76 | data: s.into(), 77 | indices, 78 | }) 79 | } 80 | 81 | /// Returns the top-level type. 82 | #[must_use] 83 | pub fn ty(&self) -> Name { 84 | Name::new_unchecked(&self.data[self.indices.ty()]) 85 | } 86 | 87 | /// Returns the subtype. 88 | #[must_use] 89 | pub fn subty(&self) -> Name { 90 | Name::new_unchecked(&self.data[self.indices.subty()]) 91 | } 92 | 93 | /// Returns the suffix. 94 | #[must_use] 95 | pub fn suffix(&self) -> Option { 96 | self.indices 97 | .suffix() 98 | .map(|range| Name::new_unchecked(&self.data[range])) 99 | } 100 | 101 | /// Returns a [`MediaType`] without parameters. 102 | /// 103 | /// ``` 104 | /// # use mediatype::MediaTypeBuf; 105 | /// # use std::str::FromStr; 106 | /// let media_type: MediaTypeBuf = "image/svg+xml; charset=UTF-8".parse().unwrap(); 107 | /// assert_eq!(media_type.essence().to_string(), "image/svg+xml"); 108 | /// ``` 109 | /// 110 | /// [`MadiaType`]: ./struct.MediaType.html 111 | #[must_use] 112 | pub fn essence(&self) -> MediaType<'_> { 113 | MediaType::from_parts(self.ty(), self.subty(), self.suffix(), &[]) 114 | } 115 | 116 | /// Returns the underlying string. 117 | #[must_use] 118 | pub const fn as_str(&self) -> &str { 119 | &self.data 120 | } 121 | 122 | /// Returns the canonicalized `MediaTypeBuf`. 123 | /// 124 | /// All strings except parameter values will be converted to lowercase. 125 | /// 126 | /// ``` 127 | /// # use mediatype::MediaTypeBuf; 128 | /// let media_type: MediaTypeBuf = "IMAGE/SVG+XML; CHARSET=UTF-8; ".parse().unwrap(); 129 | /// assert_eq!( 130 | /// media_type.canonicalize().to_string(), 131 | /// "image/svg+xml; charset=UTF-8" 132 | /// ); 133 | /// ``` 134 | #[must_use] 135 | pub fn canonicalize(&self) -> Self { 136 | use core::fmt::Write; 137 | let mut s = String::with_capacity(self.data.len()); 138 | write!( 139 | s, 140 | "{}/{}", 141 | self.ty().as_str().to_ascii_lowercase(), 142 | self.subty().as_str().to_ascii_lowercase() 143 | ) 144 | .unwrap(); 145 | if let Some(suffix) = self.suffix() { 146 | write!(s, "+{}", suffix.as_str().to_ascii_lowercase()) 147 | .expect("`write` should not fail on a `String`"); 148 | } 149 | for (name, value) in self.params() { 150 | write!(s, "; {}={}", name.as_str().to_ascii_lowercase(), value) 151 | .expect("`write` should not fail on a `String`"); 152 | } 153 | s.shrink_to_fit(); 154 | Self::from_string(s).expect("all values should be valid") 155 | } 156 | 157 | /// Constructs a `MediaType` from `self`. 158 | #[must_use] 159 | pub fn to_ref(&self) -> MediaType { 160 | let params = self.params().collect::>(); 161 | let params = if params.is_empty() { 162 | Cow::Borrowed([].as_slice()) 163 | } else { 164 | Cow::Owned(params) 165 | }; 166 | MediaType::from_parts_unchecked(self.ty(), self.subty(), self.suffix(), params) 167 | } 168 | } 169 | 170 | impl ReadParams for MediaTypeBuf { 171 | fn params(&self) -> Params { 172 | Params::from_indices(&self.data, &self.indices) 173 | } 174 | 175 | fn get_param(&self, name: Name) -> Option { 176 | self.indices 177 | .params() 178 | .iter() 179 | .rev() 180 | .find(|&&[start, end, _, _]| { 181 | name == Name::new_unchecked(&self.data[start..end]) 182 | }) 183 | .map(|&[_, _, start, end]| { 184 | Value::new_unchecked(&self.data[start..end]) 185 | }) 186 | } 187 | } 188 | 189 | impl FromStr for MediaTypeBuf { 190 | type Err = MediaTypeError; 191 | 192 | fn from_str(s: &str) -> Result { 193 | let (indices, len) = Indices::parse(s)?; 194 | Ok(Self { 195 | data: s[..len].into(), 196 | indices, 197 | }) 198 | } 199 | } 200 | 201 | impl From> for MediaTypeBuf { 202 | fn from(t: MediaType) -> Self { 203 | Self::from_string(t.to_string()).expect("`t` should be valid") 204 | } 205 | } 206 | 207 | impl From<&MediaType<'_>> for MediaTypeBuf { 208 | fn from(t: &MediaType) -> Self { 209 | Self::from_string(t.to_string()).expect("`t` should be valid") 210 | } 211 | } 212 | 213 | impl AsRef for MediaTypeBuf { 214 | fn as_ref(&self) -> &str { 215 | &self.data 216 | } 217 | } 218 | 219 | impl PartialEq for MediaTypeBuf { 220 | fn eq(&self, other: &Self) -> bool { 221 | self.ty() == other.ty() 222 | && self.subty() == other.subty() 223 | && self.suffix() == other.suffix() 224 | && self.params().collect::>() 225 | == other.params().collect::>() 226 | } 227 | } 228 | 229 | impl Eq for MediaTypeBuf {} 230 | 231 | impl PartialEq> for MediaTypeBuf { 232 | fn eq(&self, other: &MediaType) -> bool { 233 | self.ty() == other.ty 234 | && self.subty() == other.subty 235 | && self.suffix() == other.suffix 236 | && self.params().collect::>() 237 | == other.params().collect::>() 238 | } 239 | } 240 | 241 | impl PartialEq<&MediaType<'_>> for MediaTypeBuf { 242 | fn eq(&self, other: &&MediaType) -> bool { 243 | self == *other 244 | } 245 | } 246 | 247 | impl PartialEq> for &MediaTypeBuf { 248 | fn eq(&self, other: &MediaType) -> bool { 249 | *self == other 250 | } 251 | } 252 | 253 | impl fmt::Display for MediaTypeBuf { 254 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 255 | write!(f, "{}/{}", self.ty(), self.subty())?; 256 | if let Some(suffix) = self.suffix() { 257 | write!(f, "+{}", suffix)?; 258 | } 259 | for (name, value) in self.params() { 260 | write!(f, "; {}={}", name, value)?; 261 | } 262 | Ok(()) 263 | } 264 | } 265 | 266 | impl Hash for MediaTypeBuf { 267 | fn hash(&self, state: &mut H) { 268 | self.ty().hash(state); 269 | self.subty().hash(state); 270 | self.suffix().hash(state); 271 | self.params() 272 | .collect::>() 273 | .into_iter() 274 | .collect::>() 275 | .hash(state); 276 | } 277 | } 278 | 279 | #[cfg(test)] 280 | mod tests { 281 | use super::*; 282 | use crate::{media_type, names::*, values::*}; 283 | use alloc::string::ToString; 284 | use std::collections::hash_map::DefaultHasher; 285 | 286 | fn calculate_hash(t: &T) -> u64 { 287 | let mut s = DefaultHasher::new(); 288 | t.hash(&mut s); 289 | s.finish() 290 | } 291 | 292 | #[test] 293 | fn from_parts() { 294 | assert_eq!( 295 | MediaTypeBuf::from_parts(IMAGE, SVG, Some(XML), &[(CHARSET, UTF_8)]).to_string(), 296 | "image/svg+xml; charset=UTF-8" 297 | ); 298 | } 299 | 300 | #[test] 301 | fn get_param() { 302 | assert_eq!( 303 | MediaTypeBuf::from_str("image/svg+xml") 304 | .unwrap() 305 | .get_param(CHARSET), 306 | None 307 | ); 308 | assert_eq!( 309 | MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8") 310 | .unwrap() 311 | .get_param(CHARSET), 312 | Some(UTF_8) 313 | ); 314 | assert_eq!( 315 | MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8; HELLO=WORLD; HELLO=world") 316 | .unwrap() 317 | .get_param(Name::new("hello").unwrap()), 318 | Some(Value::new("world").unwrap()) 319 | ); 320 | } 321 | 322 | #[test] 323 | fn essence() { 324 | assert_eq!( 325 | MediaTypeBuf::from_str("image/svg+xml") 326 | .unwrap() 327 | .essence() 328 | .to_string(), 329 | "image/svg+xml" 330 | ); 331 | assert_eq!( 332 | MediaTypeBuf::from_str("image/svg+xml; ") 333 | .unwrap() 334 | .essence() 335 | .to_string(), 336 | "image/svg+xml" 337 | ); 338 | assert_eq!( 339 | MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8") 340 | .unwrap() 341 | .essence() 342 | .to_string(), 343 | "image/svg+xml" 344 | ); 345 | assert_eq!( 346 | MediaTypeBuf::from_str("image/svg+xml ; charset=UTF-8") 347 | .unwrap() 348 | .essence() 349 | .to_string(), 350 | "image/svg+xml" 351 | ); 352 | } 353 | 354 | #[test] 355 | fn canonicalize() { 356 | assert_eq!( 357 | MediaTypeBuf::from_str("IMAGE/SVG+XML; CHARSET=UTF-8; ") 358 | .unwrap() 359 | .canonicalize() 360 | .to_string(), 361 | "image/svg+xml; charset=UTF-8" 362 | ); 363 | } 364 | 365 | #[test] 366 | fn cmp() { 367 | assert_eq!( 368 | MediaTypeBuf::from_str("text/plain").unwrap(), 369 | MediaTypeBuf::from_str("TEXT/PLAIN").unwrap() 370 | ); 371 | assert_eq!( 372 | MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8").unwrap(), 373 | MediaTypeBuf::from_str("IMAGE/SVG+XML; CHARSET=UTF-8").unwrap() 374 | ); 375 | assert_eq!( 376 | MediaTypeBuf::from_str("image/svg+xml; hello=WORLD; charset=UTF-8").unwrap(), 377 | MediaTypeBuf::from_str("IMAGE/SVG+XML; HELLO=WORLD; CHARSET=UTF-8").unwrap() 378 | ); 379 | assert_eq!( 380 | MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8").unwrap(), 381 | MediaType::from_parts( 382 | IMAGE, 383 | SVG, 384 | Some(XML), 385 | &[(CHARSET, US_ASCII), (CHARSET, UTF_8)] 386 | ), 387 | ); 388 | assert_eq!( 389 | &MediaTypeBuf::from_str("image/svg+xml").unwrap(), 390 | media_type!(IMAGE / SVG + XML) 391 | ); 392 | } 393 | 394 | #[test] 395 | fn hash() { 396 | assert_eq!( 397 | calculate_hash(&MediaTypeBuf::from_str("text/plain").unwrap()), 398 | calculate_hash(&MediaTypeBuf::from_str("TEXT/PLAIN").unwrap()) 399 | ); 400 | assert_eq!( 401 | calculate_hash(&MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8").unwrap()), 402 | calculate_hash(&MediaTypeBuf::from_str("IMAGE/SVG+XML; CHARSET=UTF-8").unwrap()) 403 | ); 404 | assert_eq!( 405 | calculate_hash( 406 | &MediaTypeBuf::from_str("image/svg+xml; hello=WORLD; charset=UTF-8").unwrap() 407 | ), 408 | calculate_hash( 409 | &MediaTypeBuf::from_str("IMAGE/SVG+XML; HELLO=WORLD; CHARSET=UTF-8").unwrap() 410 | ) 411 | ); 412 | assert_eq!( 413 | calculate_hash(&MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8").unwrap()), 414 | calculate_hash( 415 | &MediaTypeBuf::from_str("image/svg+xml; charset=US-ASCII; charset=UTF-8").unwrap() 416 | ), 417 | ); 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /src/consts/names.txt: -------------------------------------------------------------------------------- 1 | _star=* 2 | 1d-interleaved-parityfec 3 | 32kadpcm 4 | 3gpdash-qoe-report 5 | 3gpp 6 | 3gpp-ims 7 | 3gpp-tt 8 | 3gpp2 9 | 3gppHal 10 | 3gppHalForms 11 | 3mf 12 | A2L 13 | aac 14 | ac3 15 | ace 16 | aces 17 | activemessage 18 | activity 19 | alternative 20 | alto-costmap 21 | alto-costmapfilter 22 | alto-directory 23 | alto-endpointcost 24 | alto-endpointcostparams 25 | alto-endpointprop 26 | alto-endpointpropparams 27 | alto-error 28 | alto-networkmap 29 | alto-networkmapfilter 30 | alto-updatestreamcontrol 31 | alto-updatestreamparams 32 | AML 33 | AMR 34 | AMR-WB 35 | amr-wb+ 36 | andrew-inset 37 | appledouble 38 | applefile 39 | application 40 | aptx 41 | asc 42 | at 43 | ATF 44 | ATFX 45 | atom 46 | atomcat 47 | atomdeleted 48 | atomicmail 49 | atomsvc 50 | ATRAC-ADVANCED-LOSSLESS 51 | ATRAC-X 52 | ATRAC3 53 | atsc-dwd 54 | atsc-dynamic-event-message 55 | atsc-held 56 | atsc-rdt 57 | atsc-rsat 58 | ATXML 59 | audio 60 | auth-policy 61 | AV1 62 | avci 63 | avcs 64 | avif 65 | bacnet-xdd 66 | basic 67 | batch-SMTP 68 | beep 69 | ber 70 | bmp 71 | BMPEG 72 | boundary 73 | BT656 74 | BV16 75 | BV32 76 | byteranges 77 | cache-manifest 78 | calendar 79 | call-completion 80 | CALS-1840 81 | captive 82 | cbor 83 | cbor-seq 84 | cccex 85 | ccmp 86 | ccxml 87 | CDFX 88 | cdmi-capability 89 | cdmi-container 90 | cdmi-domain 91 | cdmi-object 92 | cdmi-queue 93 | cdni 94 | CEA 95 | cea-2018 96 | CelB 97 | cellml 98 | cfw 99 | cgm 100 | charset 101 | chemical 102 | city 103 | clearmode 104 | clr 105 | clue 106 | clue_info 107 | cms 108 | CN 109 | cnrp 110 | coap-group 111 | coap-payload 112 | collection 113 | commonground 114 | conference-info 115 | cose 116 | cose-key 117 | cose-key-set 118 | cpl 119 | cql 120 | cql-expression 121 | cql-identifier 122 | csrattrs 123 | css 124 | csta 125 | CSTAdata 126 | csv 127 | csv-schema 128 | csvm 129 | cwt 130 | cybercash 131 | dash 132 | dash-patch 133 | dashdelta 134 | DAT12 135 | davmount 136 | dca-rft 137 | DCD 138 | dec-dx 139 | delsp 140 | der 141 | dialog-info 142 | dicom 143 | dicom-rle 144 | digest 145 | DII 146 | directory 147 | DIT 148 | dls 149 | dns 150 | dns-message 151 | dots 152 | dskpp 153 | dsr-es201108 154 | dsr-es202050 155 | dsr-es202211 156 | dsr-es202212 157 | dssc 158 | DV 159 | dvcs 160 | DVI4 161 | e57 162 | eac3 163 | ecmascript 164 | EDI-consent 165 | EDI-X12 166 | EDIFACT 167 | efi 168 | elm 169 | EmergencyCallData.cap 170 | EmergencyCallData.Comment 171 | EmergencyCallData.Control 172 | EmergencyCallData.DeviceInfo 173 | EmergencyCallData.eCall.MSD 174 | EmergencyCallData.ProviderInfo 175 | EmergencyCallData.ServiceInfo 176 | EmergencyCallData.SubscriberInfo 177 | EmergencyCallData.VEDS 178 | emf 179 | emma 180 | emotionml 181 | encaprtp 182 | encrypted 183 | enriched 184 | epp 185 | epub 186 | eshop 187 | event-stream 188 | EVRC 189 | EVRC-QCP 190 | EVRC0 191 | EVRC1 192 | EVRCB 193 | EVRCB0 194 | EVRCB1 195 | EVRCNW 196 | EVRCNW0 197 | EVRCNW1 198 | EVRCWB 199 | EVRCWB0 200 | EVRCWB1 201 | EVS 202 | example 203 | exi 204 | expect-ct-report 205 | express 206 | fastinfoset 207 | fastsoap 208 | fdt 209 | feed 210 | FFV1 211 | fhir 212 | fhirpath 213 | fits 214 | flexfec 215 | font 216 | font-sfnt 217 | font-tdpfr 218 | font-woff 219 | form-data 220 | format 221 | framework-attributes 222 | fwdred 223 | g3fax 224 | G711-0 225 | G719 226 | G722 227 | G7221 228 | G723 229 | G726-16 230 | G726-24 231 | G726-32 232 | G726-40 233 | G728 234 | G729 235 | G7291 236 | G729D 237 | G729E 238 | geo 239 | geopackage 240 | geoxacml 241 | gff3 242 | gif 243 | gltf 244 | gltf-binary 245 | gltf-buffer 246 | gml 247 | grammar-ref-list 248 | GSM 249 | GSM-EFR 250 | GSM-HR-08 251 | gzip 252 | H224 253 | H261 254 | H263 255 | H263-1998 256 | H263-2000 257 | H264 258 | H264-RCDO 259 | H264-SVC 260 | H265 261 | header-set 262 | heic 263 | heic-sequence 264 | heif 265 | heif-sequence 266 | hej2k 267 | held 268 | hsj2 269 | html 270 | http 271 | hyperstudio 272 | ibe-key-request 273 | ibe-pkg-reply 274 | ibe-pp-data 275 | ief 276 | iges 277 | iLBC 278 | im-iscomposing 279 | image 280 | index 281 | index.cmd 282 | index.obj 283 | index.response 284 | index.vnd 285 | inkml 286 | IOTP 287 | ip-mr_v2.5 288 | ipfix 289 | ipp 290 | iso.segment 291 | ISUP 292 | its 293 | java-archive 294 | javascript 295 | javascript1.0 296 | javascript1.1 297 | javascript1.2 298 | javascript1.3 299 | javascript1.4 300 | javascript1.5 301 | jcr-cnd 302 | jf2feed 303 | jls 304 | jose 305 | jp2 306 | jpeg 307 | jpeg2000 308 | jph 309 | jphc 310 | jpm 311 | jpx 312 | jrd 313 | jscalendar 314 | jscript 315 | json 316 | json-patch 317 | json-seq 318 | jwk 319 | jwk-set 320 | jwt 321 | jxr 322 | jxrA 323 | jxrS 324 | jxs 325 | jxsc 326 | jxsi 327 | jxss 328 | jxsv 329 | kpml-request 330 | kpml-response 331 | ktx 332 | ktx2 333 | L16 334 | L20 335 | L24 336 | L8 337 | ld 338 | lgr 339 | link-format 340 | livescript 341 | load-control 342 | lost 343 | lostsync 344 | LPC 345 | lpf 346 | LXF 347 | mac-binhex40 348 | macwriteii 349 | mads 350 | manifest 351 | marc 352 | marcxml 353 | markdown 354 | mathematica 355 | mathml 356 | mathml-content 357 | mathml-presentation 358 | mbms-associated-procedure-description 359 | mbms-deregister 360 | mbms-envelope 361 | mbms-msk 362 | mbms-msk-response 363 | mbms-protection-description 364 | mbms-reception-report 365 | mbms-register 366 | mbms-register-response 367 | mbms-schedule 368 | mbms-user-service-description 369 | mbox 370 | media_control 371 | media-policy-dataset 372 | mediaservercontrol 373 | MELP 374 | MELP1200 375 | MELP2400 376 | MELP600 377 | merge-patch 378 | mesh 379 | message 380 | metalink4 381 | mets 382 | MF4 383 | mhas 384 | mikey 385 | mipc 386 | missing-blocks 387 | mixed 388 | mizar 389 | mj2 390 | mmt-aei 391 | mmt-usd 392 | mobile-xmf 393 | model 394 | mods 395 | moss-keys 396 | moss-signature 397 | mosskey-data 398 | mosskey-request 399 | MP1S 400 | mp21 401 | MP2P 402 | mp2t 403 | mp4 404 | MP4A-LATM 405 | MP4V-ES 406 | MPA 407 | mpa-robust 408 | mpeg 409 | mpeg4-generic 410 | mpeg4-iod 411 | mpeg4-iod-xmt 412 | MPV 413 | mrb-consumer 414 | mrb-publish 415 | msc-ivr 416 | msc-mixer 417 | msgpack 418 | msword 419 | mtl 420 | mud 421 | multilingual 422 | multipart 423 | multipart-core 424 | mxf 425 | n-quads 426 | n-triples 427 | n3 428 | naplps 429 | nasdata 430 | news-checkgroups 431 | news-groupinfo 432 | news-transmission 433 | nlsml 434 | node 435 | nss 436 | nv 437 | oauth-authz-req 438 | obj 439 | ocsp-request 440 | ocsp-response 441 | octet-stream 442 | ODA 443 | odm 444 | ODX 445 | oebps-package 446 | ogg 447 | opc-nodeset 448 | opus 449 | oscore 450 | otf 451 | oxps 452 | p21 453 | p2p-overlay 454 | parallel 455 | parameters 456 | parityfec 457 | passport 458 | patch-ops-error 459 | PCMA 460 | PCMA-WB 461 | PCMU 462 | PCMU-WB 463 | pdf 464 | PDX 465 | pem-certificate-chain 466 | pgp-encrypted 467 | pgp-keys 468 | pgp-signature 469 | pidf 470 | pidf-diff 471 | pkcs10 472 | pkcs12 473 | pkcs7-mime 474 | pkcs7-signature 475 | pkcs8 476 | pkcs8-encrypted 477 | pkix-attr-cert 478 | pkix-cert 479 | pkix-crl 480 | pkix-pkipath 481 | pkixcmp 482 | plain 483 | pls 484 | png 485 | poc-settings 486 | pointer 487 | postscript 488 | ppsp-tracker 489 | problem 490 | provenance 491 | provenance-notation 492 | prs.alvestrand.titrax-sheet 493 | prs.btif 494 | prs.cww 495 | prs.cyn 496 | prs.fallenstein.rst 497 | prs.hpub 498 | prs.lines.tag 499 | prs.nprend 500 | prs.plucker 501 | prs.prop.logic 502 | prs.pti 503 | prs.rdf-xml-crypt 504 | prs.sid 505 | prs.xsf 506 | pskc 507 | pvd 508 | pwg-raster 509 | q 510 | QCELP 511 | QSIG 512 | quicktime 513 | raptorfec 514 | raw 515 | rdap 516 | rdf 517 | RED 518 | reginfo 519 | related 520 | relax-ng-compact-syntax 521 | remote-printing 522 | report 523 | reputon 524 | resource-lists 525 | resource-lists-diff 526 | rfc 527 | rfc822-headers 528 | richtext 529 | riscos 530 | rlmi 531 | rls-services 532 | route-apd 533 | route-s-tsid 534 | route-usd 535 | rpki-ghostbusters 536 | rpki-manifest 537 | rpki-publication 538 | rpki-roa 539 | rpki-updown 540 | rss 541 | rtf 542 | rtp-enc-aescm128 543 | rtp-midi 544 | rtploopback 545 | rtx 546 | samlassertion 547 | samlmetadata 548 | sarif 549 | sarif-external-properties 550 | sbe 551 | sbml 552 | scaip 553 | scim 554 | scip 555 | scvp-cv-request 556 | scvp-cv-response 557 | scvp-vp-request 558 | scvp-vp-response 559 | sdp 560 | secevent 561 | senml 562 | senml-etch 563 | senml-exi 564 | sensml 565 | sensml-exi 566 | sep 567 | sep-exi 568 | session-info 569 | set-payment 570 | set-payment-initiation 571 | set-registration 572 | set-registration-initiation 573 | sfnt 574 | SGML 575 | sgml-open-catalog 576 | shaclc 577 | shex 578 | shf 579 | sieve 580 | signed 581 | simple-filter 582 | simple-message-summary 583 | simpleSymbolContainer 584 | sipc 585 | slate 586 | smil 587 | smpte291 588 | SMPTE292M 589 | smpte336m 590 | SMV 591 | SMV-QCP 592 | SMV0 593 | soap 594 | sofa 595 | sp-midi 596 | sparql-query 597 | sparql-update 598 | sparql-results 599 | spdx 600 | speex 601 | spirits-event 602 | sql 603 | srgs 604 | sru 605 | ssml 606 | step 607 | step-xml 608 | stix 609 | stl 610 | strings 611 | svg 612 | swid 613 | t140 614 | t140c 615 | t38 616 | tab-separated-values 617 | tamp-apex-update 618 | tamp-apex-update-confirm 619 | tamp-community-update 620 | tamp-community-update-confirm 621 | tamp-error 622 | tamp-sequence-adjust 623 | tamp-sequence-adjust-confirm 624 | tamp-status-query 625 | tamp-status-response 626 | tamp-update 627 | tamp-update-confirm 628 | taxii 629 | td 630 | tei 631 | telephone-event 632 | TETRA_ACELP 633 | TETRA_ACELP_BB 634 | TETRA_ISI 635 | text 636 | thraud 637 | tiff 638 | tiff-fx 639 | timestamp-query 640 | timestamp-reply 641 | timestamped-data 642 | tlsrpt 643 | tnauthlist 644 | token-introspection 645 | tone 646 | trickle-ice-sdpfrag 647 | trig 648 | troff 649 | TSVCIS 650 | ttf 651 | ttml 652 | turtle 653 | tve-trigger 654 | tzif 655 | tzif-leap 656 | UEMCLIP 657 | ulpfec 658 | urc-grpsheet 659 | urc-ressheet 660 | urc-targetdesc 661 | urc-uisocketdesc 662 | uri-list 663 | usac 664 | vc1 665 | vc2 666 | vcard 667 | VDVI 668 | vemmi 669 | video 670 | VMR-WB 671 | vnd.1000minds.decision-model 672 | vnd.3gpp-prose 673 | vnd.3gpp-prose-pc3ch 674 | vnd.3gpp-v2x-local-service-information 675 | vnd.3gpp.5gnas 676 | vnd.3gpp.access-transfer-events 677 | vnd.3gpp.bsf 678 | vnd.3gpp.GMOP 679 | vnd.3gpp.gtpc 680 | vnd.3gpp.interworking-data 681 | vnd.3gpp.iufp 682 | vnd.3gpp.lpp 683 | vnd.3gpp.mc-signalling-ear 684 | vnd.3gpp.mcdata-affiliation-command 685 | vnd.3gpp.mcdata-info 686 | vnd.3gpp.mcdata-payload 687 | vnd.3gpp.mcdata-service-config 688 | vnd.3gpp.mcdata-signalling 689 | vnd.3gpp.mcdata-ue-config 690 | vnd.3gpp.mcdata-user-profile 691 | vnd.3gpp.mcptt-affiliation-command 692 | vnd.3gpp.mcptt-floor-request 693 | vnd.3gpp.mcptt-info 694 | vnd.3gpp.mcptt-location-info 695 | vnd.3gpp.mcptt-mbms-usage-info 696 | vnd.3gpp.mcptt-service-config 697 | vnd.3gpp.mcptt-signed 698 | vnd.3gpp.mcptt-ue-config 699 | vnd.3gpp.mcptt-ue-init-config 700 | vnd.3gpp.mcptt-user-profile 701 | vnd.3gpp.mcvideo-affiliation-command 702 | vnd.3gpp.mcvideo-affiliation-info 703 | vnd.3gpp.mcvideo-info 704 | vnd.3gpp.mcvideo-location-info 705 | vnd.3gpp.mcvideo-mbms-usage-info 706 | vnd.3gpp.mcvideo-service-config 707 | vnd.3gpp.mcvideo-transmission-request 708 | vnd.3gpp.mcvideo-ue-config 709 | vnd.3gpp.mcvideo-user-profile 710 | vnd.3gpp.mid-call 711 | vnd.3gpp.ngap 712 | vnd.3gpp.pfcp 713 | vnd.3gpp.pic-bw-large 714 | vnd.3gpp.pic-bw-small 715 | vnd.3gpp.pic-bw-var 716 | vnd.3gpp.s1ap 717 | vnd.3gpp.sms 718 | vnd.3gpp.srvcc-ext 719 | vnd.3gpp.SRVCC-info 720 | vnd.3gpp.state-and-event-info 721 | vnd.3gpp.ussd 722 | vnd.3gpp2.bcmcsinfo 723 | vnd.3gpp2.sms 724 | vnd.3gpp2.tcap 725 | vnd.3lightssoftware.imagescal 726 | vnd.3M.Post-it-Notes 727 | vnd.4SB 728 | vnd.a 729 | vnd.abc 730 | vnd.accpac.simply.aso 731 | vnd.accpac.simply.imp 732 | vnd.acucobol 733 | vnd.acucorp 734 | vnd.adobe.flash.movie 735 | vnd.adobe.formscentral.fcdt 736 | vnd.adobe.fxp 737 | vnd.adobe.partial-upload 738 | vnd.adobe.photoshop 739 | vnd.adobe.xdp 740 | vnd.adobe.xfdf 741 | vnd.aether.imp 742 | vnd.afpc.afplinedata 743 | vnd.afpc.afplinedata-pagedef 744 | vnd.afpc.cmoca-cmresource 745 | vnd.afpc.foca-charset 746 | vnd.afpc.foca-codedfont 747 | vnd.afpc.foca-codepage 748 | vnd.afpc.modca 749 | vnd.afpc.modca-cmtable 750 | vnd.afpc.modca-formdef 751 | vnd.afpc.modca-mediummap 752 | vnd.afpc.modca-objectcontainer 753 | vnd.afpc.modca-overlay 754 | vnd.afpc.modca-pagesegment 755 | vnd.age 756 | vnd.ah-barcode 757 | vnd.ahead.space 758 | vnd.airzip.accelerator.azv 759 | vnd.airzip.filesecure.azf 760 | vnd.airzip.filesecure.azs 761 | vnd.amadeus 762 | vnd.amazon.ebook 763 | vnd.amazon.mobi8-ebook 764 | vnd.americandynamics.acc 765 | vnd.amiga.ami 766 | vnd.amundsen.maze 767 | vnd.android.ota 768 | vnd.anki 769 | vnd.anser-web-certificate-issue-initiation 770 | vnd.antix.game-component 771 | vnd.apache.arrow.file 772 | vnd.apache.arrow.stream 773 | vnd.apache.thrift.binary 774 | vnd.apache.thrift.compact 775 | vnd.apache.thrift.json 776 | vnd.api 777 | vnd.aplextor.warrp 778 | vnd.apothekende.reservation 779 | vnd.apple.installer 780 | vnd.apple.keynote 781 | vnd.apple.mpegurl 782 | vnd.apple.numbers 783 | vnd.apple.pages 784 | vnd.arastra.swi 785 | vnd.aristanetworks.swi 786 | vnd.artisan 787 | vnd.artsquare 788 | vnd.ascii-art 789 | vnd.astraea-software.iota 790 | vnd.audiograph 791 | vnd.audiokoz 792 | vnd.autopackage 793 | vnd.avalon 794 | vnd.avistar 795 | vnd.balsamiq.bmml 796 | vnd.balsamiq.bmpr 797 | vnd.banana-accounting 798 | vnd.bbf.usp.error 799 | vnd.bbf.usp.msg 800 | vnd.bekitzur-stech 801 | vnd.bint.med-content 802 | vnd.bint.med-plus 803 | vnd.biopax.rdf 804 | vnd.blink-idb-value-wrapper 805 | vnd.blueice.multipass 806 | vnd.bluetooth.ep.oob 807 | vnd.bluetooth.le.oob 808 | vnd.bmi 809 | vnd.bpf 810 | vnd.bpf3 811 | vnd.businessobjects 812 | vnd.byu.uapi 813 | vnd.cab-jscript 814 | vnd.canon-cpdl 815 | vnd.canon-lips 816 | vnd.capasystems-pg 817 | vnd.CCTV 818 | vnd.CELP 819 | vnd.cendio.thinlinc.clientconf 820 | vnd.century-systems.tcp_stream 821 | vnd.chemdraw 822 | vnd.chess-pgn 823 | vnd.chipnuts.karaoke-mmd 824 | vnd.ciedi 825 | vnd.cinderella 826 | vnd.cirpack.isdn-ext 827 | vnd.cisco.nse 828 | vnd.citationstyles.style 829 | vnd.claymore 830 | vnd.cloanto.rp9 831 | vnd.clonk.c4group 832 | vnd.cluetrust.cartomobile-config 833 | vnd.cluetrust.cartomobile-config-pkg 834 | vnd.cmles.radio-events 835 | vnd.cns.anp1 836 | vnd.cns.inf1 837 | vnd.cns.inf2 838 | vnd.coffeescript 839 | vnd.collabio.xodocuments.document 840 | vnd.collabio.xodocuments.document-template 841 | vnd.collabio.xodocuments.presentation 842 | vnd.collabio.xodocuments.presentation-template 843 | vnd.collabio.xodocuments.spreadsheet 844 | vnd.collabio.xodocuments.spreadsheet-template 845 | vnd.collada 846 | vnd.collection 847 | vnd.collection.doc 848 | vnd.collection.next 849 | vnd.comicbook 850 | vnd.comicbook-rar 851 | vnd.commerce-battelle 852 | vnd.commonspace 853 | vnd.contact.cmsg 854 | vnd.coreos.ignition 855 | vnd.cosmocaller 856 | vnd.crick.clicker 857 | vnd.crick.clicker.keyboard 858 | vnd.crick.clicker.palette 859 | vnd.crick.clicker.template 860 | vnd.crick.clicker.wordbank 861 | vnd.criticaltools.wbs 862 | vnd.cryptii.pipe 863 | vnd.crypto-shade-file 864 | vnd.cryptomator.encrypted 865 | vnd.cryptomator.vault 866 | vnd.ctc-posml 867 | vnd.ctct.ws 868 | vnd.cups-pdf 869 | vnd.cups-postscript 870 | vnd.cups-ppd 871 | vnd.cups-raster 872 | vnd.cups-raw 873 | vnd.curl 874 | vnd.cyan.dean.root 875 | vnd.cybank 876 | vnd.cyclonedx 877 | vnd.d2l.coursepackage1p0 878 | vnd.d3m-dataset 879 | vnd.d3m-problem 880 | vnd.dart 881 | vnd.data-vision.rdz 882 | vnd.datapackage 883 | vnd.dataresource 884 | vnd.dbf 885 | vnd.debian.binary-package 886 | vnd.debian.copyright 887 | vnd.dece.audio 888 | vnd.dece.data 889 | vnd.dece.graphic 890 | vnd.dece.hd 891 | vnd.dece.mobile 892 | vnd.dece.mp4 893 | vnd.dece.pd 894 | vnd.dece.sd 895 | vnd.dece.ttml 896 | vnd.dece.unspecified 897 | vnd.dece.video 898 | vnd.dece.zip 899 | vnd.denovo.fcselayout-link 900 | vnd.desmume.movie 901 | vnd.digital-winds 902 | vnd.dir-bi.plate-dl-nosuffix 903 | vnd.directv.mpeg 904 | vnd.directv.mpeg-tts 905 | vnd.djvu 906 | vnd.dlna.adts 907 | vnd.dlna.mpeg-tts 908 | vnd.dm.delegation 909 | vnd.DMClientScript 910 | vnd.dna 911 | vnd.document 912 | vnd.dolby.heaac.1 913 | vnd.dolby.heaac.2 914 | vnd.dolby.mlp 915 | vnd.dolby.mobile.1 916 | vnd.dolby.mobile.2 917 | vnd.dolby.mps 918 | vnd.dolby.pl2 919 | vnd.dolby.pl2x 920 | vnd.dolby.pl2z 921 | vnd.dolby.pulse.1 922 | vnd.doremir.scorecloud-binary-document 923 | vnd.dpgraph 924 | vnd.dra 925 | vnd.dreamfactory 926 | vnd.drive 927 | vnd.dtg.local 928 | vnd.dtg.local.flash 929 | vnd.dtg.local.html 930 | vnd.dts 931 | vnd.dts.hd 932 | vnd.dts.uhd 933 | vnd.dvb.ait 934 | vnd.dvb.dvbisl 935 | vnd.dvb.dvbj 936 | vnd.dvb.esgcontainer 937 | vnd.dvb.file 938 | vnd.dvb.ipdcdftnotifaccess 939 | vnd.dvb.ipdcesgaccess 940 | vnd.dvb.ipdcesgaccess2 941 | vnd.dvb.ipdcesgpdd 942 | vnd.dvb.ipdcroaming 943 | vnd.dvb.iptv.alfec-base 944 | vnd.dvb.iptv.alfec-enhancement 945 | vnd.dvb.notif-aggregate-root 946 | vnd.dvb.notif-container 947 | vnd.dvb.notif-generic 948 | vnd.dvb.notif-ia-msglist 949 | vnd.dvb.notif-ia-registration-request 950 | vnd.dvb.notif-ia-registration-response 951 | vnd.dvb.notif-init 952 | vnd.dvb.pfr 953 | vnd.dvb.service 954 | vnd.dvb.subtitle 955 | vnd.dwf 956 | vnd.dwg 957 | vnd.dxf 958 | vnd.dxr 959 | vnd.dynageo 960 | vnd.dzr 961 | vnd.easykaraoke.cdgdownload 962 | vnd.ecdis-update 963 | vnd.ecip.rlp 964 | vnd.eclipse.ditto 965 | vnd.ecowin.chart 966 | vnd.ecowin.filerequest 967 | vnd.ecowin.fileupdate 968 | vnd.ecowin.series 969 | vnd.ecowin.seriesrequest 970 | vnd.ecowin.seriesupdate 971 | vnd.efi.img 972 | vnd.efi.iso 973 | vnd.emclient.accessrequest 974 | vnd.enliven 975 | vnd.enphase.envoy 976 | vnd.eprints.data 977 | vnd.epson.esf 978 | vnd.epson.msf 979 | vnd.epson.quickanime 980 | vnd.epson.salt 981 | vnd.epson.ssf 982 | vnd.ericsson.quickcall 983 | vnd.esmertec.theme-descriptor 984 | vnd.espass-espass 985 | vnd.eszigno3 986 | vnd.etsi.aoc 987 | vnd.etsi.asic-e 988 | vnd.etsi.asic-s 989 | vnd.etsi.cug 990 | vnd.etsi.iptvcommand 991 | vnd.etsi.iptvdiscovery 992 | vnd.etsi.iptvprofile 993 | vnd.etsi.iptvsad-bc 994 | vnd.etsi.iptvsad-cod 995 | vnd.etsi.iptvsad-npvr 996 | vnd.etsi.iptvservice 997 | vnd.etsi.iptvsync 998 | vnd.etsi.iptvueprofile 999 | vnd.etsi.mcid 1000 | vnd.etsi.mheg5 1001 | vnd.etsi.overload-control-policy-dataset 1002 | vnd.etsi.pstn 1003 | vnd.etsi.sci 1004 | vnd.etsi.simservs 1005 | vnd.etsi.timestamp-token 1006 | vnd.etsi.tsl 1007 | vnd.etsi.tsl.der 1008 | vnd.eu.kasparian.car 1009 | vnd.eudora.data 1010 | vnd.everad.plj 1011 | vnd.evolv.ecig.profile 1012 | vnd.evolv.ecig.settings 1013 | vnd.evolv.ecig.theme 1014 | vnd.exstream-empower 1015 | vnd.exstream-package 1016 | vnd.ezpix-album 1017 | vnd.ezpix-package 1018 | vnd.f-secure.mobile 1019 | vnd.familysearch.gedcom 1020 | vnd.fastbidsheet 1021 | vnd.fastcopy-disk-image 1022 | vnd.fdf 1023 | vnd.fdsn.mseed 1024 | vnd.fdsn.seed 1025 | vnd.ffsns 1026 | vnd.ficlab.flb 1027 | vnd.ficlab.flt 1028 | vnd.filmit.zfc 1029 | vnd.fints 1030 | vnd.firemonkeys.cloudcell 1031 | vnd.flatland.3dml 1032 | vnd.FloGraphIt 1033 | vnd.fluxtime.clip 1034 | vnd.fly 1035 | vnd.fmi.flexstor 1036 | vnd.font-fontforge-sfd 1037 | vnd.fpx 1038 | vnd.framemaker 1039 | vnd.frogans.fnc 1040 | vnd.frogans.ltf 1041 | vnd.fsc.weblaunch 1042 | vnd.fst 1043 | vnd.fujifilm.fb.docuworks 1044 | vnd.fujifilm.fb.docuworks.binder 1045 | vnd.fujifilm.fb.docuworks.container 1046 | vnd.fujifilm.fb.jfi 1047 | vnd.fujitsu.oasys 1048 | vnd.fujitsu.oasys2 1049 | vnd.fujitsu.oasys3 1050 | vnd.fujitsu.oasysgp 1051 | vnd.fujitsu.oasysprs 1052 | vnd.fujixerox.ART-EX 1053 | vnd.fujixerox.ART4 1054 | vnd.fujixerox.ddd 1055 | vnd.fujixerox.docuworks 1056 | vnd.fujixerox.docuworks.binder 1057 | vnd.fujixerox.docuworks.container 1058 | vnd.fujixerox.edmics-mmr 1059 | vnd.fujixerox.edmics-rlc 1060 | vnd.fujixerox.HBPL 1061 | vnd.fut-misnet 1062 | vnd.futoin 1063 | vnd.fuzzysheet 1064 | vnd.fvt 1065 | vnd.gdl 1066 | vnd.genomatix.tuxedo 1067 | vnd.gentics.grd 1068 | vnd.geo 1069 | vnd.geocube 1070 | vnd.geogebra.file 1071 | vnd.geogebra.slides 1072 | vnd.geogebra.tool 1073 | vnd.geometry-explorer 1074 | vnd.geonext 1075 | vnd.geoplan 1076 | vnd.geospace 1077 | vnd.gerber 1078 | vnd.globalgraphics.pgb 1079 | vnd.globalplatform.card-content-mgt 1080 | vnd.globalplatform.card-content-mgt-response 1081 | vnd.gml 1082 | vnd.gmx 1083 | vnd.google-earth.kml 1084 | vnd.google-earth.kmz 1085 | vnd.gov.sk.e-form 1086 | vnd.gov.sk.xmldatacontainer 1087 | vnd.grafeq 1088 | vnd.graphviz 1089 | vnd.gridmp 1090 | vnd.groove-account 1091 | vnd.groove-help 1092 | vnd.groove-identity-message 1093 | vnd.groove-injector 1094 | vnd.groove-tool-message 1095 | vnd.groove-tool-template 1096 | vnd.groove-vcard 1097 | vnd.gs-gdl 1098 | vnd.gtw 1099 | vnd.hal 1100 | vnd.HandHeld-Entertainment 1101 | vnd.hans 1102 | vnd.hbci 1103 | vnd.hc 1104 | vnd.hcl-bireports 1105 | vnd.hdt 1106 | vnd.heroku 1107 | vnd.hgl 1108 | vnd.hhe.lesson-player 1109 | vnd.hl7cda 1110 | vnd.hl7v2 1111 | vnd.hns.audio 1112 | vnd.hns.video 1113 | vnd.hp-HPGL 1114 | vnd.hp-hpid 1115 | vnd.hp-hps 1116 | vnd.hp-jlyt 1117 | vnd.hp-PCL 1118 | vnd.hp-PCLXL 1119 | vnd.httphone 1120 | vnd.hydrostatix.sof-data 1121 | vnd.hyper 1122 | vnd.hyper-item 1123 | vnd.hyperdrive 1124 | vnd.hzn-3d-crossword 1125 | vnd.ibm.afplinedata 1126 | vnd.ibm.electronic-media 1127 | vnd.ibm.MiniPay 1128 | vnd.ibm.modcap 1129 | vnd.ibm.rights-management 1130 | vnd.ibm.secure-container 1131 | vnd.iccprofile 1132 | vnd.ieee.1905 1133 | vnd.igloader 1134 | vnd.imagemeter.folder 1135 | vnd.imagemeter.image 1136 | vnd.immervision-ivp 1137 | vnd.immervision-ivu 1138 | vnd.ims.imsccv1p1 1139 | vnd.ims.imsccv1p2 1140 | vnd.ims.imsccv1p3 1141 | vnd.ims.lis.v2.result 1142 | vnd.ims.lti.v2.toolconsumerprofile 1143 | vnd.ims.lti.v2.toolproxy 1144 | vnd.ims.lti.v2.toolproxy.id 1145 | vnd.ims.lti.v2.toolsettings 1146 | vnd.ims.lti.v2.toolsettings.simple 1147 | vnd.in3d.3dml 1148 | vnd.in3d.spot 1149 | vnd.informedcontrol.rms 1150 | vnd.informix-visionary 1151 | vnd.infotech.project 1152 | vnd.innopath.wamp.notification 1153 | vnd.insors.igm 1154 | vnd.intercon.formnet 1155 | vnd.intergeo 1156 | vnd.intertrust.digibox 1157 | vnd.intertrust.nncp 1158 | vnd.intu.qbo 1159 | vnd.intu.qfx 1160 | vnd.iptc.g2.catalogitem 1161 | vnd.iptc.g2.conceptitem 1162 | vnd.iptc.g2.knowledgeitem 1163 | vnd.iptc.g2.newsitem 1164 | vnd.iptc.g2.newsmessage 1165 | vnd.iptc.g2.packageitem 1166 | vnd.iptc.g2.planningitem 1167 | vnd.IPTC.NewsML 1168 | vnd.IPTC.NITF 1169 | vnd.iptvforum.1dparityfec-1010 1170 | vnd.iptvforum.1dparityfec-2005 1171 | vnd.iptvforum.2dparityfec-1010 1172 | vnd.iptvforum.2dparityfec-2005 1173 | vnd.iptvforum.ttsavc 1174 | vnd.iptvforum.ttsmpeg2 1175 | vnd.ipunplugged.rcprofile 1176 | vnd.irepository.package 1177 | vnd.is-xpr 1178 | vnd.isac.fcs 1179 | vnd.iso11783-10 1180 | vnd.jam 1181 | vnd.japannet-directory-service 1182 | vnd.japannet-jpnstore-wakeup 1183 | vnd.japannet-payment-wakeup 1184 | vnd.japannet-registration 1185 | vnd.japannet-registration-wakeup 1186 | vnd.japannet-setstore-wakeup 1187 | vnd.japannet-verification 1188 | vnd.japannet-verification-wakeup 1189 | vnd.jcp.javame.midlet-rms 1190 | vnd.jisp 1191 | vnd.joost.joda-archive 1192 | vnd.jsk.isdn-ngn 1193 | vnd.kahootz 1194 | vnd.kde.karbon 1195 | vnd.kde.kchart 1196 | vnd.kde.kformula 1197 | vnd.kde.kivio 1198 | vnd.kde.kontour 1199 | vnd.kde.kpresenter 1200 | vnd.kde.kspread 1201 | vnd.kde.kword 1202 | vnd.kenameaapp 1203 | vnd.kidspiration 1204 | vnd.Kinar 1205 | vnd.koan 1206 | vnd.kodak-descriptor 1207 | vnd.las 1208 | vnd.las.las 1209 | vnd.laszip 1210 | vnd.latex-z 1211 | vnd.leap 1212 | vnd.liberty-request 1213 | vnd.llamagraphics.life-balance.desktop 1214 | vnd.llamagraphics.life-balance.exchange 1215 | vnd.logipipe.circuit 1216 | vnd.loom 1217 | vnd.lotus-1-2-3 1218 | vnd.lotus-approach 1219 | vnd.lotus-freelance 1220 | vnd.lotus-notes 1221 | vnd.lotus-organizer 1222 | vnd.lotus-screencam 1223 | vnd.lotus-wordpro 1224 | vnd.lucent.voice 1225 | vnd.macports.portpkg 1226 | vnd.mapbox-vector-tile 1227 | vnd.marlin.drm.actiontoken 1228 | vnd.marlin.drm.conftoken 1229 | vnd.marlin.drm.license 1230 | vnd.marlin.drm.mdcf 1231 | vnd.mason 1232 | vnd.maxar.archive.3tz 1233 | vnd.maxmind.maxmind-db 1234 | vnd.mcd 1235 | vnd.medcalcdata 1236 | vnd.mediastation.cdkey 1237 | vnd.meridian-slingshot 1238 | vnd.MFER 1239 | vnd.mfmp 1240 | vnd.micro 1241 | vnd.micrografx.flo 1242 | vnd.micrografx.igx 1243 | vnd.microsoft.icon 1244 | vnd.microsoft.portable-executable 1245 | vnd.microsoft.windows.thumbnail-cache 1246 | vnd.miele 1247 | vnd.mif 1248 | vnd.minisoft-hp3000-save 1249 | vnd.mitsubishi.misty-guard.trustweb 1250 | vnd.mix 1251 | vnd.Mobius.DAF 1252 | vnd.Mobius.DIS 1253 | vnd.Mobius.MBK 1254 | vnd.Mobius.MQY 1255 | vnd.Mobius.MSL 1256 | vnd.Mobius.PLC 1257 | vnd.Mobius.TXF 1258 | vnd.moml 1259 | vnd.mophun.application 1260 | vnd.mophun.certificate 1261 | vnd.motorola.flexsuite 1262 | vnd.motorola.flexsuite.adsi 1263 | vnd.motorola.flexsuite.fis 1264 | vnd.motorola.flexsuite.gotap 1265 | vnd.motorola.flexsuite.kmr 1266 | vnd.motorola.flexsuite.ttc 1267 | vnd.motorola.flexsuite.wem 1268 | vnd.motorola.iprm 1269 | vnd.motorola.reflex 1270 | vnd.motorola.video 1271 | vnd.motorola.videop 1272 | vnd.mozilla.apng 1273 | vnd.mozilla.xul 1274 | vnd.mpegurl 1275 | vnd.ms-3mfdocument 1276 | vnd.ms-artgalry 1277 | vnd.ms-asf 1278 | vnd.ms-cab-compressed 1279 | vnd.ms-excel 1280 | vnd.ms-excel.addin.macroEnabled.12 1281 | vnd.ms-excel.sheet.binary.macroEnabled.12 1282 | vnd.ms-excel.sheet.macroEnabled.12 1283 | vnd.ms-excel.template.macroEnabled.12 1284 | vnd.ms-fontobject 1285 | vnd.ms-htmlhelp 1286 | vnd.ms-ims 1287 | vnd.ms-lrm 1288 | vnd.ms-mediapackage 1289 | vnd.ms-modi 1290 | vnd.ms-office.activeX 1291 | vnd.ms-officetheme 1292 | vnd.ms-playready.initiator 1293 | vnd.ms-playready.media.pya 1294 | vnd.ms-playready.media.pyv 1295 | vnd.ms-powerpoint 1296 | vnd.ms-powerpoint.addin.macroEnabled.12 1297 | vnd.ms-powerpoint.presentation.macroEnabled.12 1298 | vnd.ms-powerpoint.slide.macroEnabled.12 1299 | vnd.ms-powerpoint.slideshow.macroEnabled.12 1300 | vnd.ms-powerpoint.template.macroEnabled.12 1301 | vnd.ms-PrintDeviceCapabilities 1302 | vnd.ms-PrintSchemaTicket 1303 | vnd.ms-project 1304 | vnd.ms-tnef 1305 | vnd.ms-windows.devicepairing 1306 | vnd.ms-windows.nwprinting.oob 1307 | vnd.ms-windows.printerpairing 1308 | vnd.ms-windows.wsd.oob 1309 | vnd.ms-wmdrm.lic-chlg-req 1310 | vnd.ms-wmdrm.lic-resp 1311 | vnd.ms-wmdrm.meter-chlg-req 1312 | vnd.ms-wmdrm.meter-resp 1313 | vnd.ms-word.document.macroEnabled.12 1314 | vnd.ms-word.template.macroEnabled.12 1315 | vnd.ms-works 1316 | vnd.ms-wpl 1317 | vnd.ms-xpsdocument 1318 | vnd.msa-disk-image 1319 | vnd.mseq 1320 | vnd.msign 1321 | vnd.msgpack 1322 | vnd.mts 1323 | vnd.multiad.creator 1324 | vnd.multiad.creator.cif 1325 | vnd.music-niff 1326 | vnd.musician 1327 | vnd.muvee.style 1328 | vnd.mynfc 1329 | vnd.nacamar.ybrid 1330 | vnd.ncd.control 1331 | vnd.ncd.reference 1332 | vnd.nearst.inv 1333 | vnd.nebumind.line 1334 | vnd.nervana 1335 | vnd.net-fpx 1336 | vnd.net2phone.commcenter.command 1337 | vnd.netfpx 1338 | vnd.neurolanguage.nlu 1339 | vnd.nimn 1340 | vnd.nintendo.nitro.rom 1341 | vnd.nintendo.snes.rom 1342 | vnd.nitf 1343 | vnd.noblenet-directory 1344 | vnd.noblenet-sealer 1345 | vnd.noblenet-web 1346 | vnd.nokia.catalogs 1347 | vnd.nokia.conml 1348 | vnd.nokia.interleaved-multimedia 1349 | vnd.nokia.iptv.config 1350 | vnd.nokia.iSDS-radio-presets 1351 | vnd.nokia.landmark 1352 | vnd.nokia.landmarkcollection 1353 | vnd.nokia.mobile-xmf 1354 | vnd.nokia.mp4vr 1355 | vnd.nokia.n-gage.ac 1356 | vnd.nokia.n-gage.data 1357 | vnd.nokia.n-gage.symbian.install 1358 | vnd.nokia.ncd 1359 | vnd.nokia.pcd 1360 | vnd.nokia.radio-preset 1361 | vnd.nokia.radio-presets 1362 | vnd.nokia.videovoip 1363 | vnd.nortel.vbk 1364 | vnd.novadigm.EDM 1365 | vnd.novadigm.EDX 1366 | vnd.novadigm.EXT 1367 | vnd.ntt-local.content-share 1368 | vnd.ntt-local.file-transfer 1369 | vnd.ntt-local.ogw_remote-access 1370 | vnd.ntt-local.sip-ta_remote 1371 | vnd.ntt-local.sip-ta_tcp_stream 1372 | vnd.nuera.ecelp4800 1373 | vnd.nuera.ecelp7470 1374 | vnd.nuera.ecelp9600 1375 | vnd.oasis.opendocument.chart 1376 | vnd.oasis.opendocument.chart-template 1377 | vnd.oasis.opendocument.database 1378 | vnd.oasis.opendocument.formula 1379 | vnd.oasis.opendocument.formula-template 1380 | vnd.oasis.opendocument.graphics 1381 | vnd.oasis.opendocument.graphics-template 1382 | vnd.oasis.opendocument.image 1383 | vnd.oasis.opendocument.image-template 1384 | vnd.oasis.opendocument.presentation 1385 | vnd.oasis.opendocument.presentation-template 1386 | vnd.oasis.opendocument.spreadsheet 1387 | vnd.oasis.opendocument.spreadsheet-template 1388 | vnd.oasis.opendocument.text 1389 | vnd.oasis.opendocument.text-master 1390 | vnd.oasis.opendocument.text-template 1391 | vnd.oasis.opendocument.text-web 1392 | vnd.objectvideo 1393 | vnd.obn 1394 | vnd.ocf 1395 | vnd.oci.image.manifest.v1 1396 | vnd.octel.sbc 1397 | vnd.oftn.l10n 1398 | vnd.oipf.contentaccessdownload 1399 | vnd.oipf.contentaccessstreaming 1400 | vnd.oipf.cspg-hexbinary 1401 | vnd.oipf.dae.svg 1402 | vnd.oipf.dae.xhtml 1403 | vnd.oipf.mippvcontrolmessage 1404 | vnd.oipf.pae.gem 1405 | vnd.oipf.spdiscovery 1406 | vnd.oipf.spdlist 1407 | vnd.oipf.ueprofile 1408 | vnd.oipf.userprofile 1409 | vnd.olpc-sugar 1410 | vnd.oma-scws-config 1411 | vnd.oma-scws-http-request 1412 | vnd.oma-scws-http-response 1413 | vnd.oma.bcast.associated-procedure-parameter 1414 | vnd.oma.bcast.drm-trigger 1415 | vnd.oma.bcast.imd 1416 | vnd.oma.bcast.ltkm 1417 | vnd.oma.bcast.notification 1418 | vnd.oma.bcast.provisioningtrigger 1419 | vnd.oma.bcast.sgboot 1420 | vnd.oma.bcast.sgdd 1421 | vnd.oma.bcast.sgdu 1422 | vnd.oma.bcast.simple-symbol-container 1423 | vnd.oma.bcast.smartcard-trigger 1424 | vnd.oma.bcast.sprov 1425 | vnd.oma.bcast.stkm 1426 | vnd.oma.cab-address-book 1427 | vnd.oma.cab-feature-handler 1428 | vnd.oma.cab-pcc 1429 | vnd.oma.cab-subs-invite 1430 | vnd.oma.cab-user-prefs 1431 | vnd.oma.dcd 1432 | vnd.oma.dcdc 1433 | vnd.oma.dd2 1434 | vnd.oma.drm.risd 1435 | vnd.oma.group-usage-list 1436 | vnd.oma.lwm2m 1437 | vnd.oma.pal 1438 | vnd.oma.poc.detailed-progress-report 1439 | vnd.oma.poc.final-report 1440 | vnd.oma.poc.groups 1441 | vnd.oma.poc.invocation-descriptor 1442 | vnd.oma.poc.optimized-progress-report 1443 | vnd.oma.push 1444 | vnd.oma.scidm.messages 1445 | vnd.oma.xcap-directory 1446 | vnd.omads-email 1447 | vnd.omads-file 1448 | vnd.omads-folder 1449 | vnd.omaloc-supl-init 1450 | vnd.onepager 1451 | vnd.onepagertamp 1452 | vnd.onepagertamx 1453 | vnd.onepagertat 1454 | vnd.onepagertatp 1455 | vnd.onepagertatx 1456 | vnd.openblox.game 1457 | vnd.openblox.game-binary 1458 | vnd.openeye.oeb 1459 | vnd.opengex 1460 | vnd.openstreetmap.data 1461 | vnd.opentimestamps.ots 1462 | vnd.openxmlformats-officedocument.custom-properties 1463 | vnd.openxmlformats-officedocument.customXmlProperties 1464 | vnd.openxmlformats-officedocument.drawing 1465 | vnd.openxmlformats-officedocument.drawingml.chart 1466 | vnd.openxmlformats-officedocument.drawingml.chartshapes 1467 | vnd.openxmlformats-officedocument.drawingml.diagramColors 1468 | vnd.openxmlformats-officedocument.drawingml.diagramData 1469 | vnd.openxmlformats-officedocument.drawingml.diagramLayout 1470 | vnd.openxmlformats-officedocument.drawingml.diagramStyle 1471 | vnd.openxmlformats-officedocument.extended-properties 1472 | vnd.openxmlformats-officedocument.presentationml.commentAuthors 1473 | vnd.openxmlformats-officedocument.presentationml.comments 1474 | vnd.openxmlformats-officedocument.presentationml.handoutMaster 1475 | vnd.openxmlformats-officedocument.presentationml.notesMaster 1476 | vnd.openxmlformats-officedocument.presentationml.notesSlide 1477 | vnd.openxmlformats-officedocument.presentationml.presentation 1478 | vnd.openxmlformats-officedocument.presentationml.presentation.main 1479 | vnd.openxmlformats-officedocument.presentationml.presProps 1480 | vnd.openxmlformats-officedocument.presentationml.slide 1481 | vnd.openxmlformats-officedocument.presentationml.slideLayout 1482 | vnd.openxmlformats-officedocument.presentationml.slideMaster 1483 | vnd.openxmlformats-officedocument.presentationml.slideshow 1484 | vnd.openxmlformats-officedocument.presentationml.slideshow.main 1485 | vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo 1486 | vnd.openxmlformats-officedocument.presentationml.tableStyles 1487 | vnd.openxmlformats-officedocument.presentationml.tags 1488 | vnd.openxmlformats-officedocument.presentationml.template 1489 | vnd.openxmlformats-officedocument.presentationml.template.main 1490 | vnd.openxmlformats-officedocument.presentationml.viewProps 1491 | vnd.openxmlformats-officedocument.spreadsheetml.calcChain 1492 | vnd.openxmlformats-officedocument.spreadsheetml.chartsheet 1493 | vnd.openxmlformats-officedocument.spreadsheetml.comments 1494 | vnd.openxmlformats-officedocument.spreadsheetml.connections 1495 | vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet 1496 | vnd.openxmlformats-officedocument.spreadsheetml.externalLink 1497 | vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition 1498 | vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords 1499 | vnd.openxmlformats-officedocument.spreadsheetml.pivotTable 1500 | vnd.openxmlformats-officedocument.spreadsheetml.queryTable 1501 | vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders 1502 | vnd.openxmlformats-officedocument.spreadsheetml.revisionLog 1503 | vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings 1504 | vnd.openxmlformats-officedocument.spreadsheetml.sheet 1505 | vnd.openxmlformats-officedocument.spreadsheetml.sheet.main 1506 | vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata 1507 | vnd.openxmlformats-officedocument.spreadsheetml.styles 1508 | vnd.openxmlformats-officedocument.spreadsheetml.table 1509 | vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells 1510 | vnd.openxmlformats-officedocument.spreadsheetml.template 1511 | vnd.openxmlformats-officedocument.spreadsheetml.template.main 1512 | vnd.openxmlformats-officedocument.spreadsheetml.userNames 1513 | vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies 1514 | vnd.openxmlformats-officedocument.spreadsheetml.worksheet 1515 | vnd.openxmlformats-officedocument.theme 1516 | vnd.openxmlformats-officedocument.themeOverride 1517 | vnd.openxmlformats-officedocument.vmlDrawing 1518 | vnd.openxmlformats-officedocument.wordprocessingml.comments 1519 | vnd.openxmlformats-officedocument.wordprocessingml.document 1520 | vnd.openxmlformats-officedocument.wordprocessingml.document.glossary 1521 | vnd.openxmlformats-officedocument.wordprocessingml.document.main 1522 | vnd.openxmlformats-officedocument.wordprocessingml.endnotes 1523 | vnd.openxmlformats-officedocument.wordprocessingml.fontTable 1524 | vnd.openxmlformats-officedocument.wordprocessingml.footer 1525 | vnd.openxmlformats-officedocument.wordprocessingml.footnotes 1526 | vnd.openxmlformats-officedocument.wordprocessingml.numbering 1527 | vnd.openxmlformats-officedocument.wordprocessingml.settings 1528 | vnd.openxmlformats-officedocument.wordprocessingml.styles 1529 | vnd.openxmlformats-officedocument.wordprocessingml.template 1530 | vnd.openxmlformats-officedocument.wordprocessingml.template.main 1531 | vnd.openxmlformats-officedocument.wordprocessingml.webSettings 1532 | vnd.openxmlformats-package.core-properties 1533 | vnd.openxmlformats-package.digital-signature-xmlsignature 1534 | vnd.openxmlformats-package.relationships 1535 | vnd.oracle.resource 1536 | vnd.orange.indata 1537 | vnd.osa.netdeploy 1538 | vnd.osgeo.mapguide.package 1539 | vnd.osgi.bundle 1540 | vnd.osgi.dp 1541 | vnd.osgi.subsystem 1542 | vnd.otps.ct-kip 1543 | vnd.oxli.countgraph 1544 | vnd.pagerduty 1545 | vnd.palm 1546 | vnd.panoply 1547 | vnd.paos.xml 1548 | vnd.parasolid.transmit.binary 1549 | vnd.parasolid.transmit.text 1550 | vnd.patentdive 1551 | vnd.patientecommsdoc 1552 | vnd.pawaafile 1553 | vnd.pco.b16 1554 | vnd.pcos 1555 | vnd.pg.format 1556 | vnd.pg.osasli 1557 | vnd.piaccess.application-licence 1558 | vnd.picsel 1559 | vnd.pmi.widget 1560 | vnd.poc.group-advertisement 1561 | vnd.pocketlearn 1562 | vnd.powerbuilder6 1563 | vnd.powerbuilder6-s 1564 | vnd.powerbuilder7 1565 | vnd.powerbuilder7-s 1566 | vnd.powerbuilder75 1567 | vnd.powerbuilder75-s 1568 | vnd.preminet 1569 | vnd.presonus.multitrack 1570 | vnd.previewsystems.box 1571 | vnd.proteus.magazine 1572 | vnd.psfs 1573 | vnd.publishare-delta-tree 1574 | vnd.pvi.ptid1 1575 | vnd.pwg-multiplexed 1576 | vnd.pwg-xhtml-print 1577 | vnd.pytha.pyox 1578 | vnd.qcelp 1579 | vnd.qualcomm.brew-app-res 1580 | vnd.quarantainenet 1581 | vnd.Quark.QuarkXPress 1582 | vnd.quobject-quoxdocument 1583 | vnd.radgamettools.bink 1584 | vnd.radgamettools.smacker 1585 | vnd.radiance 1586 | vnd.radisys.moml 1587 | vnd.radisys.msml 1588 | vnd.radisys.msml-audit 1589 | vnd.radisys.msml-audit-conf 1590 | vnd.radisys.msml-audit-conn 1591 | vnd.radisys.msml-audit-dialog 1592 | vnd.radisys.msml-audit-stream 1593 | vnd.radisys.msml-basic-layout 1594 | vnd.radisys.msml-conf 1595 | vnd.radisys.msml-dialog 1596 | vnd.radisys.msml-dialog-base 1597 | vnd.radisys.msml-dialog-fax-detect 1598 | vnd.radisys.msml-dialog-fax-sendrecv 1599 | vnd.radisys.msml-dialog-group 1600 | vnd.radisys.msml-dialog-speech 1601 | vnd.radisys.msml-dialog-transform 1602 | vnd.rainstor.data 1603 | vnd.rapid 1604 | vnd.rar 1605 | vnd.realvnc.bed 1606 | vnd.recordare.musicxml 1607 | vnd.RenLearn.rlprint 1608 | vnd.resilient.logic 1609 | vnd.restful 1610 | vnd.rhetorex.32kadpcm 1611 | vnd.rig.cryptonote 1612 | vnd.rip 1613 | vnd.rosette.annotated-data-model 1614 | vnd.route66.link66 1615 | vnd.rs-274x 1616 | vnd.ruckus.download 1617 | vnd.s3sms 1618 | vnd.sailingtracker.track 1619 | vnd.sap.vds 1620 | vnd.sar 1621 | vnd.sbm.cid 1622 | vnd.sbm.mid2 1623 | vnd.scribus 1624 | vnd.sealed.3df 1625 | vnd.sealed.csf 1626 | vnd.sealed.doc 1627 | vnd.sealed.eml 1628 | vnd.sealed.mht 1629 | vnd.sealed.mpeg1 1630 | vnd.sealed.mpeg4 1631 | vnd.sealed.net 1632 | vnd.sealed.png 1633 | vnd.sealed.ppt 1634 | vnd.sealed.swf 1635 | vnd.sealed.tiff 1636 | vnd.sealed.xls 1637 | vnd.sealedmedia.softseal.gif 1638 | vnd.sealedmedia.softseal.html 1639 | vnd.sealedmedia.softseal.jpg 1640 | vnd.sealedmedia.softseal.mov 1641 | vnd.sealedmedia.softseal.mpeg 1642 | vnd.sealedmedia.softseal.pdf 1643 | vnd.seemail 1644 | vnd.seis 1645 | vnd.sema 1646 | vnd.semd 1647 | vnd.semf 1648 | vnd.senx.warpscript 1649 | vnd.shade-save-file 1650 | vnd.shana.informed.formdata 1651 | vnd.shana.informed.formtemplate 1652 | vnd.shana.informed.interchange 1653 | vnd.shana.informed.package 1654 | vnd.shootproof 1655 | vnd.shopkick 1656 | vnd.shp 1657 | vnd.shx 1658 | vnd.si.uricatalogue 1659 | vnd.sigrok.session 1660 | vnd.SimTech-MindMapper 1661 | vnd.siren 1662 | vnd.smaf 1663 | vnd.smart.notebook 1664 | vnd.smart.teacher 1665 | vnd.snesdev-page-table 1666 | vnd.software602.filler.form 1667 | vnd.software602.filler.form-xml-zip 1668 | vnd.solent.sdkm 1669 | vnd.sosi 1670 | vnd.spotfire.dxp 1671 | vnd.spotfire.sfs 1672 | vnd.sqlite3 1673 | vnd.sss-cod 1674 | vnd.sss-dtf 1675 | vnd.sss-ntf 1676 | vnd.stepmania.package 1677 | vnd.stepmania.stepchart 1678 | vnd.street-stream 1679 | vnd.sun.j2me.app-descriptor 1680 | vnd.sun.wadl 1681 | vnd.sus-calendar 1682 | vnd.svd 1683 | vnd.svf 1684 | vnd.swiftview-ics 1685 | vnd.sycle 1686 | vnd.syft 1687 | vnd.syncml 1688 | vnd.syncml.dm 1689 | vnd.syncml.dm.notification 1690 | vnd.syncml.dmddf 1691 | vnd.syncml.dmtnds 1692 | vnd.syncml.ds.notification 1693 | vnd.tableschema 1694 | vnd.tao.intent-module-archive 1695 | vnd.tcpdump.pcap 1696 | vnd.tencent.tap 1697 | vnd.think-cell.ppttc 1698 | vnd.tmd.mediaflex.api 1699 | vnd.tml 1700 | vnd.tmobile-livetv 1701 | vnd.tri.onesource 1702 | vnd.trid.tpt 1703 | vnd.triscape.mxs 1704 | vnd.trolltech.linguist 1705 | vnd.trueapp 1706 | vnd.truedoc 1707 | vnd.ubisoft.webplayer 1708 | vnd.ufdl 1709 | vnd.uiq.theme 1710 | vnd.umajin 1711 | vnd.unity 1712 | vnd.uoml 1713 | vnd.uplanet.alert 1714 | vnd.uplanet.alert-wbxml 1715 | vnd.uplanet.bearer-choice 1716 | vnd.uplanet.bearer-choice-wbxml 1717 | vnd.uplanet.cacheop 1718 | vnd.uplanet.cacheop-wbxml 1719 | vnd.uplanet.channel 1720 | vnd.uplanet.channel-wbxml 1721 | vnd.uplanet.list 1722 | vnd.uplanet.list-wbxml 1723 | vnd.uplanet.listcmd 1724 | vnd.uplanet.listcmd-wbxml 1725 | vnd.uplanet.signal 1726 | vnd.uri-map 1727 | vnd.usdz 1728 | vnd.uvvu.mp4 1729 | vnd.valve.source.compiled-map 1730 | vnd.valve.source.material 1731 | vnd.valve.source.texture 1732 | vnd.vcx 1733 | vnd.vd-study 1734 | vnd.vectorworks 1735 | vnd.vel 1736 | vnd.verimatrix.vcas 1737 | vnd.veritone.aion 1738 | vnd.veryant.thin 1739 | vnd.ves.encrypted 1740 | vnd.vidsoft.vidconference 1741 | vnd.visio 1742 | vnd.visionary 1743 | vnd.vividence.scriptfile 1744 | vnd.vivo 1745 | vnd.vmx.cvsd 1746 | vnd.vsf 1747 | vnd.vtu 1748 | vnd.wap.si 1749 | vnd.wap.sic 1750 | vnd.wap.sl 1751 | vnd.wap.slc 1752 | vnd.wap.wbmp 1753 | vnd.wap.wbxml 1754 | vnd.wap.wml 1755 | vnd.wap.wmlc 1756 | vnd.wap.wmlscript 1757 | vnd.wap.wmlscriptc 1758 | vnd.webturbo 1759 | vnd.wfa.dpp 1760 | vnd.wfa.p2p 1761 | vnd.wfa.wsc 1762 | vnd.windows.devicepairing 1763 | vnd.wmc 1764 | vnd.wmf.bootstrap 1765 | vnd.wolfram.mathematica 1766 | vnd.wolfram.mathematica.package 1767 | vnd.wolfram.player 1768 | vnd.wordperfect 1769 | vnd.wqd 1770 | vnd.wrq-hp3000-labelled 1771 | vnd.wt.stf 1772 | vnd.wv.csp 1773 | vnd.wv.ssp 1774 | vnd.xacml 1775 | vnd.xara 1776 | vnd.xfdl 1777 | vnd.xfdl.webform 1778 | vnd.xiff 1779 | vnd.xmi 1780 | vnd.xmpie.cpkg 1781 | vnd.xmpie.dpkg 1782 | vnd.xmpie.plan 1783 | vnd.xmpie.ppkg 1784 | vnd.xmpie.xlim 1785 | vnd.yamaha.hv-dic 1786 | vnd.yamaha.hv-script 1787 | vnd.yamaha.hv-voice 1788 | vnd.yamaha.openscoreformat 1789 | vnd.yamaha.openscoreformat.osfpvg 1790 | vnd.yamaha.remote-setup 1791 | vnd.yamaha.smaf-audio 1792 | vnd.yamaha.smaf-phrase 1793 | vnd.yamaha.through-ngn 1794 | vnd.yamaha.tunnel-udpencap 1795 | vnd.yaoweme 1796 | vnd.yellowriver-custom-menu 1797 | vnd.youtube.yt 1798 | vnd.zbrush.pcx 1799 | vnd.zul 1800 | vnd.zzazz.deck 1801 | voice-message 1802 | voicexml 1803 | vorbis 1804 | vorbis-config 1805 | voucher-cms 1806 | VP8 1807 | VP9 1808 | vq-rtcpxr 1809 | vrml 1810 | vtt 1811 | wasm 1812 | watcherinfo 1813 | wav 1814 | wbxml 1815 | webm 1816 | webp 1817 | webpush-options 1818 | whoispp-query 1819 | whoispp-response 1820 | widget 1821 | wita 1822 | wmf 1823 | woff 1824 | woff2 1825 | wordperfect5.1 1826 | wsdl 1827 | wspolicy 1828 | x-7z-compressed 1829 | x-abiword 1830 | x-bzip 1831 | x-bzip2 1832 | x-cdf 1833 | x-csh 1834 | x-ecmascript 1835 | x-emf 1836 | x-freearc 1837 | x-httpd-php 1838 | x-icon 1839 | x-javascript 1840 | x-midi 1841 | x-mixed-replace 1842 | x-msgpack 1843 | x-msvideo 1844 | x-pki-message 1845 | x-sh 1846 | x-shockwave-flash 1847 | x-tar 1848 | x-wmf 1849 | x-www-form-urlencoded 1850 | x-x509-ca-cert 1851 | x-x509-ca-ra-cert 1852 | x-x509-next-ca-cert 1853 | x-yaml 1854 | x3d 1855 | x3d-vrml 1856 | x400-bp 1857 | xacml 1858 | xcap-att 1859 | xcap-caps 1860 | xcap-diff 1861 | xcap-el 1862 | xcap-error 1863 | xcap-ns 1864 | xcon-conference-info 1865 | xcon-conference-info-diff 1866 | xenc 1867 | xhtml 1868 | xliff 1869 | xml 1870 | xml-dtd 1871 | xml-external-parsed-entity 1872 | xml-patch 1873 | xmpp 1874 | xop 1875 | xslt 1876 | xv 1877 | yang 1878 | yang-data 1879 | yang-patch 1880 | yin 1881 | zip 1882 | zlib 1883 | zstd 1884 | --------------------------------------------------------------------------------