Skip to main content

webview_bundle_protocol/
mime_type.rs

1// This code is originally from https://github.com/tauri-apps/tauri/blob/dev/crates/tauri-utils/src/mime_type.rs
2
3// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
4// SPDX-License-Identifier: Apache-2.0
5// SPDX-License-Identifier: MIT
6
7use std::fmt;
8
9const MIMETYPE_PLAIN: &str = "text/plain";
10
11/// [Web Compatible MimeTypes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#important_mime_types_for_web_developers)
12pub enum MimeType {
13  Css,
14  Csv,
15  Html,
16  Ico,
17  Js,
18  Json,
19  Jsonld,
20  Mp4,
21  OctetStream,
22  Rtf,
23  Svg,
24  Txt,
25}
26
27impl fmt::Display for MimeType {
28  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29    let mime = match self {
30      MimeType::Css => "text/css",
31      MimeType::Csv => "text/csv",
32      MimeType::Html => "text/html",
33      MimeType::Ico => "image/vnd.microsoft.icon",
34      MimeType::Js => "text/javascript",
35      MimeType::Json => "application/json",
36      MimeType::Jsonld => "application/ld+json",
37      MimeType::Mp4 => "video/mp4",
38      MimeType::OctetStream => "application/octet-stream",
39      MimeType::Rtf => "application/rtf",
40      MimeType::Svg => "image/svg+xml",
41      MimeType::Txt => MIMETYPE_PLAIN,
42    };
43    write!(f, "{mime}")
44  }
45}
46
47impl MimeType {
48  /// parse a URI suffix to convert text/plain mimeType to their actual web compatible mimeType.
49  pub fn parse_from_uri(uri: &str) -> MimeType {
50    Self::parse_from_uri_with_fallback(uri, Self::Html)
51  }
52
53  /// parse a URI suffix to convert text/plain mimeType to their actual web compatible mimeType with specified fallback for unknown file extensions.
54  pub fn parse_from_uri_with_fallback(uri: &str, fallback: MimeType) -> MimeType {
55    let suffix = uri.split('.').next_back();
56    match suffix {
57      Some("bin") => Self::OctetStream,
58      Some("css" | "less" | "sass" | "styl") => Self::Css,
59      Some("csv") => Self::Csv,
60      Some("html") => Self::Html,
61      Some("ico") => Self::Ico,
62      Some("js") | Some("mjs") => Self::Js,
63      Some("json") => Self::Json,
64      Some("jsonld") => Self::Jsonld,
65      Some("mp4") => Self::Mp4,
66      Some("rtf") => Self::Rtf,
67      Some("svg") => Self::Svg,
68      Some("txt") => Self::Txt,
69      // Assume HTML when a TLD is found for eg. `wry:://tauri.app` | `wry://hello.com`
70      Some(_) => fallback,
71      // using octet stream according to this:
72      // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types>
73      None => Self::OctetStream,
74    }
75  }
76
77  /// infer mimetype from content (or) URI if needed.
78  pub fn parse(content: &[u8], uri: &str) -> String {
79    Self::parse_with_fallback(content, uri, Self::Html)
80  }
81
82  /// infer mimetype from content (or) URI if needed with specified fallback for unknown file extensions.
83  pub fn parse_with_fallback(content: &[u8], uri: &str, fallback: MimeType) -> String {
84    let mime = if uri.ends_with(".svg") {
85      // when reading svg, we can't use `infer`
86      None
87    } else {
88      infer::get(content).map(|info| info.mime_type())
89    };
90
91    match mime {
92      Some(mime) if mime == MIMETYPE_PLAIN => {
93        Self::parse_from_uri_with_fallback(uri, fallback).to_string()
94      }
95      None => Self::parse_from_uri_with_fallback(uri, fallback).to_string(),
96      Some(mime) => mime.to_string(),
97    }
98  }
99}
100
101#[cfg(test)]
102mod tests {
103  use super::*;
104
105  #[test]
106  fn parse_mime_types() {
107    let css = MimeType::parse_from_uri(
108      "https://unpkg.com/browse/bootstrap@4.1.0/dist/css/bootstrap-grid.css",
109    )
110    .to_string();
111    assert_eq!(css, "text/css".to_string());
112
113    let csv: String = MimeType::parse_from_uri("https://example.com/random.csv").to_string();
114    assert_eq!(csv, "text/csv".to_string());
115
116    let ico: String =
117      MimeType::parse_from_uri("https://icons.duckduckgo.com/ip3/microsoft.com.ico").to_string();
118    assert_eq!(ico, String::from("image/vnd.microsoft.icon"));
119
120    let html: String = MimeType::parse_from_uri("https://tauri.app/index.html").to_string();
121    assert_eq!(html, String::from("text/html"));
122
123    let js: String =
124      MimeType::parse_from_uri("https://unpkg.com/react@17.0.1/umd/react.production.min.js")
125        .to_string();
126    assert_eq!(js, "text/javascript".to_string());
127
128    let json: String =
129      MimeType::parse_from_uri("https://unpkg.com/browse/react@17.0.1/build-info.json").to_string();
130    assert_eq!(json, String::from("application/json"));
131
132    let jsonld: String = MimeType::parse_from_uri("https:/example.com/hello.jsonld").to_string();
133    assert_eq!(jsonld, String::from("application/ld+json"));
134
135    let mjs: String = MimeType::parse_from_uri("https://example.com/bundled.mjs").to_string();
136    assert_eq!(mjs, String::from("text/javascript"));
137
138    let mp4: String = MimeType::parse_from_uri("https://example.com/video.mp4").to_string();
139    assert_eq!(mp4, String::from("video/mp4"));
140
141    let rtf: String = MimeType::parse_from_uri("https://example.com/document.rtf").to_string();
142    assert_eq!(rtf, String::from("application/rtf"));
143
144    let svg: String = MimeType::parse_from_uri("https://example.com/picture.svg").to_string();
145    assert_eq!(svg, String::from("image/svg+xml"));
146
147    let txt: String = MimeType::parse_from_uri("https://example.com/file.txt").to_string();
148    assert_eq!(txt, String::from("text/plain"));
149
150    let custom_scheme1 = MimeType::parse_from_uri("wry://tauri.app").to_string();
151    assert_eq!(custom_scheme1, String::from("text/html"));
152
153    let custom_scheme2 = MimeType::parse_from_uri("wry://tauri.app/some/path").to_string();
154    assert_eq!(custom_scheme2, String::from("text/html"));
155  }
156}