├── .gitignore
├── devserver_lib
├── .gitignore
├── src
│ ├── identity.pfx
│ ├── reload.html
│ ├── reload.rs
│ └── lib.rs
├── examples
│ └── run.rs
├── index.html
├── Cargo.toml
└── README.md
├── Cargo.toml
├── .github
└── workflows
│ ├── lint.yml
│ └── create_gh_release.yml
├── LICENSE.md
├── README.md
├── src
└── main.rs
└── Cargo.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/devserver_lib/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | Cargo.lock
--------------------------------------------------------------------------------
/devserver_lib/src/identity.pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kettle11/devserver/HEAD/devserver_lib/src/identity.pfx
--------------------------------------------------------------------------------
/devserver_lib/examples/run.rs:
--------------------------------------------------------------------------------
1 | extern crate devserver_lib;
2 |
3 | /// Hosts a server at http://localhost:8080 serving whatever folder this is run from.
4 | fn main() {
5 | devserver_lib::run(&"localhost", 8080, "", false, "");
6 | }
7 |
--------------------------------------------------------------------------------
/devserver_lib/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello!
6 |
7 |
8 | Hello!
9 | Hi from Rust
10 |
11 |
--------------------------------------------------------------------------------
/devserver_lib/src/reload.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "devserver"
3 | version = "0.4.3"
4 | authors = ["Ian Kettlewell "]
5 | edition = "2018"
6 | license = "Zlib"
7 | repository = "https://github.com/kettle11/devserver"
8 | readme = "README.md"
9 | keywords = ["web", "server", "https", "http"]
10 | categories = ["command-line-utilities", "development-tools", "web-programming::http-server"]
11 | description = "A simple server for locally hosting a folder."
12 |
13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
14 |
15 | [dependencies]
16 | devserver_lib = { path = "devserver_lib", version = "0.4.1", features = ["reload", "https"] }
17 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Build
3 |
4 | # yamllint disable-line rule:truthy
5 | on: [push, pull_request]
6 |
7 | env:
8 | CARGO_TERM_COLOR: always
9 |
10 | jobs:
11 | build:
12 |
13 | runs-on: ${{ matrix.os }}
14 |
15 | strategy:
16 | matrix:
17 | os: [ubuntu-latest]
18 |
19 | steps:
20 | - uses: actions/checkout@v4
21 |
22 | - name: Check formatting (program)
23 | run: cargo fmt --check
24 |
25 | - name: Clippy (program)
26 | run: cargo clippy -- -D warnings
27 |
28 | - name: Check formatting (library)
29 | run: cd devserver_lib && cargo fmt --check
30 |
31 | - name: Clippy (library)
32 | run: cd devserver_lib && cargo clippy -- -D warnings
33 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Ian Kettlewell
2 |
3 | This software is provided 'as-is', without any express or implied
4 | warranty. In no event will the authors be held liable for any damages
5 | arising from the use of this software.
6 |
7 | Permission is granted to anyone to use this software for any purpose,
8 | including commercial applications, and to alter it and redistribute it
9 | freely, subject to the following restrictions:
10 |
11 | 1. The origin of this software must not be misrepresented; you must not
12 | claim that you wrote the original software. If you use this software
13 | in a product, an acknowledgment in the product documentation would be
14 | appreciated but is not required.
15 | 2. Altered source versions must be plainly marked as such, and must not be
16 | misrepresented as being the original software.
17 | 3. This notice may not be removed or altered from any source distribution.
--------------------------------------------------------------------------------
/devserver_lib/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "devserver_lib"
3 | version = "0.4.3"
4 | authors = ["Ian Kettlewell "]
5 | edition = "2018"
6 | license = "Zlib"
7 | description = "A zero configuration library for hosting a local folder via https. Refer to 'devserver' for the command line tool."
8 | repository = "https://github.com/kettle11/devserver/tree/master/devserver_lib"
9 | readme = "README.md"
10 | keywords = ["web", "server", "https", "http"]
11 | categories = ["web-programming::http-server"]
12 |
13 | [lib]
14 | name = "devserver_lib"
15 |
16 | [dependencies]
17 | native-tls = {version = "0.2.11", optional = true}
18 | notify = {version = "4.0.15", optional = true}
19 | sha-1 = {version = "0.8.2", optional = true}
20 | base64 = {version = "0.11.0", optional = true}
21 |
22 | [features]
23 | default = ["https"]
24 | https = ["native-tls"]
25 | reload = ["notify", "sha-1", "base64"]
26 |
--------------------------------------------------------------------------------
/devserver_lib/README.md:
--------------------------------------------------------------------------------
1 | # devserver_lib
2 | devserver_lib does (nearly) the minimum necessary to serve a static folder over https://localhost:8080.
3 |
4 | **DO NOT USE DEVSERVER_LIB IN PRODUCTION**
5 |
6 | `devserver_lib` should only be used for locally hosting files on a trusted network.
7 |
8 | `devserver_lib` does not properly handle the attacks robust servers must withstand on an open network.
9 |
10 | ## usage
11 | ```rust
12 | extern crate devserver_lib;
13 |
14 | fn main()
15 | {
16 | devserver_lib::run(&"localhost", 8080, "", /*Auto-reload:*/ true ); // Runs forever serving the current folder on http://localhost:8080
17 | }
18 | ```
19 |
20 | ## dependencies
21 | [rust-native-tls](https://github.com/sfackler/rust-native-tls)
22 |
23 | Dependencies only for the reload feature:
24 | [notify](https://github.com/notify-rs/notify)
25 | [sha-1](https://github.com/RustCrypto/hashes)
26 | [base64](https://github.com/marshallpierce/rust-base64)
27 |
28 | ## Resources to learn from
29 | https://doc.rust-lang.org/book/ch20-00-final-project-a-web-server.html
30 |
31 | http://concisecoder.io/2019/05/11/creating-a-static-http-server-with-rust-part-1/
32 |
--------------------------------------------------------------------------------
/.github/workflows/create_gh_release.yml:
--------------------------------------------------------------------------------
1 | name: Create GitHub release
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*.*.*"
7 |
8 | permissions:
9 | contents: write
10 |
11 | jobs:
12 | create-release:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v4
17 | - name: Release
18 | uses: softprops/action-gh-release@v1
19 |
20 | upload-assets:
21 | needs: create-release
22 |
23 | strategy:
24 | matrix:
25 | include:
26 | - target: x86_64-unknown-linux-gnu
27 | os: ubuntu-latest
28 | - target: x86_64-apple-darwin
29 | os: macos-latest
30 | - target: aarch64-apple-darwin
31 | os: macos-latest
32 | - target: x86_64-pc-windows-msvc
33 | os: windows-latest
34 |
35 | runs-on: ${{ matrix.os }}
36 |
37 | steps:
38 | - uses: actions/checkout@v4
39 | - uses: taiki-e/upload-rust-binary-action@v1
40 | with:
41 | bin: devserver
42 | target: ${{ matrix.target }}
43 | archive: devserver-$target
44 | checksum: sha512
45 | token: ${{ secrets.GITHUB_TOKEN }}
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # devserver [](https://crates.io/crates/devserver)
2 |
3 |
4 | An extremely tiny tool to serve a static folder locally.
5 |
6 | This tool is only for local development and makes no effort to be secure for other purposes.
7 |
8 | **DO NOT USE DEVSERVER IN PRODUCTION**
9 |
10 | devserver should only be used for locally hosting files on a trusted network.
11 |
12 | devserver does not properly handle the attacks robust servers must withstand on an open network.
13 |
14 | ## Installation
15 | ```
16 | cargo install devserver
17 | ```
18 |
19 | ## Usage
20 | Open a command line and navigate to the directory you'd like to host then run:
21 | ```
22 | devserver
23 | ```
24 |
25 | Visit http://localhost:8080 or https://localhost:8080 to see your hosted content.
26 |
27 | ## Options
28 | `--reload` Automatically refresh pages when a file in the hosted folder changes.
29 |
30 | `--address` Pass an address like "127.0.0.1:8080" or "localhost:8000" to change the address the server will host on.
31 |
32 | `--path` Changes the directory to be hosted.
33 |
34 | `--header` Pass a header like "Access-Control-Allow-Origin='\*'". Use multiple `--header` flags for multiple headers.
35 |
36 | `--help` Explains available options.
37 |
38 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | extern crate devserver_lib;
2 | use std::env;
3 | use std::path::Path;
4 |
5 | fn main() {
6 | let args: Vec = env::args().skip(1).collect();
7 |
8 | let mut address: String = "localhost:8080".to_string();
9 | let mut path: String = "".to_string();
10 | let mut headers = "".to_string();
11 | let mut args = args.iter();
12 | let mut reload = true;
13 | while let Some(arg) = args.next() {
14 | match arg.as_ref() {
15 | "--address" => {
16 | address = args
17 | .next()
18 | .expect("Pass an address with a port after the '--address' flag")
19 | .to_string()
20 | }
21 | "--reload" | "--refresh" => reload = true,
22 | "--noreload" | "--norefresh" => reload = false,
23 | "--path" => {
24 | path = args
25 | .next()
26 | .expect("Pass a path after the '--path' flag")
27 | .to_string()
28 | }
29 | "--header" => {
30 | let mut new_header = args
31 | .next()
32 | .expect("Pass a header after the '--header' flag")
33 | .to_string();
34 | if !new_header.contains(':') {
35 | if new_header.contains('=') {
36 | new_header = new_header.replacen('=', ":", 1);
37 | } else {
38 | panic!("Pass a ':' or '=' in the '--header' flag");
39 | }
40 | }
41 | if new_header.contains('\r') || new_header.contains('\n') || !new_header.is_ascii()
42 | {
43 | panic!("Only ASCII without line breaks is allowed in the '--header' flag");
44 | }
45 | headers.push_str("\r\n");
46 | headers.push_str(&new_header);
47 | }
48 | "--help" => {
49 | println!(
50 | r#"Run 'devserver' in a folder to host that folder.
51 |
52 | --reload Automatically refresh pages when a file in the hosted folder changes. Enabled by default.
53 | --noreload Do not automatically refresh pages when a file in the hosted folder changes.
54 | --address [address]:[port] Specify an address to use. The default is 'localhost:8080'.
55 | --path [path] Specify the path of the folder to be hosted.
56 | --header Specify an additional header to send in responses. Use multiple --header flags for multiple headers.
57 | --help Display the helpful information you're reading right now.
58 |
59 | Examples:
60 |
61 | devserver --address 127.0.0.1:8080 --path "some_directory/subdirectory" --header Access-Control-Allow-Origin='*'
62 |
63 | "#
64 | );
65 | return;
66 | }
67 | _ => {
68 | println!(
69 | "Unrecognized flag: `{:?}`.\nSee available options with `devserver --help`",
70 | arg
71 | );
72 | return;
73 | }
74 | }
75 | }
76 | let hosted_path = env::current_dir().unwrap().join(Path::new(&path));
77 |
78 | if !std::path::Path::new(&hosted_path).exists() {
79 | println!("Path [{}] does not exist!", hosted_path.display());
80 | return;
81 | }
82 |
83 | let parts: Vec<&str> = address.split(':').collect();
84 | let port = if let Some(port) = parts.get(1) {
85 | let port = port.parse();
86 | if let Ok(port) = port {
87 | port
88 | } else {
89 | println!("Error: Port must be a number");
90 | return;
91 | }
92 | } else {
93 | 8080
94 | };
95 |
96 | println!(
97 | "\nServing [{}] at [ https://{} ] or [ http://{} ]",
98 | hosted_path.display(),
99 | address,
100 | address
101 | );
102 |
103 | if reload {
104 | println!("Automatic reloading is enabled!");
105 | }
106 |
107 | println!("Stop with Ctrl+C");
108 |
109 | devserver_lib::run(
110 | parts[0],
111 | port,
112 | &hosted_path.to_string_lossy(),
113 | reload,
114 | &headers,
115 | );
116 | }
117 |
--------------------------------------------------------------------------------
/devserver_lib/src/reload.rs:
--------------------------------------------------------------------------------
1 | extern crate base64;
2 | extern crate notify;
3 |
4 | use sha1::{Digest, Sha1};
5 | use std::io::{Read, Write};
6 | use std::net::TcpListener;
7 | use std::path::Path;
8 | use std::str;
9 | use std::thread;
10 |
11 | pub const RELOAD_PORT: u32 = 8129; /* Arbitrary port */
12 |
13 | fn parse_websocket_handshake(bytes: &[u8]) -> String {
14 | let request_string = str::from_utf8(&bytes).unwrap();
15 | let lines = request_string.split("\r\n");
16 | let mut sec_websocket_key = "";
17 |
18 | for line in lines {
19 | let parts: Vec<&str> = line.split(':').collect();
20 | if let "Sec-WebSocket-Key" = parts[0] {
21 | sec_websocket_key = parts[1].trim();
22 | }
23 | }
24 |
25 | // Perform a ceremony of getting the SHA1 hash of the sec_websocket_key joined with
26 | // an arbitrary string and then take the base 64 encoding of that.
27 | let sec_websocket_accept = format!(
28 | "{}{}",
29 | sec_websocket_key, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
30 | );
31 | let mut hasher = Sha1::new();
32 | hasher.input(sec_websocket_accept.as_bytes());
33 | let result = hasher.result();
34 | let bytes = base64::encode(&result);
35 |
36 | format!("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {}\r\n\r\n",bytes)
37 | }
38 |
39 | // This function can send strings of text to a websocket stream.
40 | fn send_websocket_message(mut stream: T) -> Result<(), std::io::Error> {
41 | let payload_length = 0;
42 |
43 | stream.write_all(&[129])?; // Devserver always sends text messages. The combination of bitflags and opcode produces '129'
44 | let mut second_byte: u8 = 0;
45 |
46 | second_byte |= payload_length as u8;
47 | stream.write_all(&[second_byte])?;
48 |
49 | Ok(())
50 | }
51 |
52 | fn handle_websocket_handshake(mut stream: T) {
53 | let header = crate::read_header(&mut stream);
54 | let response = parse_websocket_handshake(&header);
55 | stream.write_all(response.as_bytes()).unwrap();
56 | stream.flush().unwrap();
57 | }
58 |
59 | pub fn watch_for_reloads(address: &str, path: &str) {
60 | use notify::{DebouncedEvent::*, RecommendedWatcher, RecursiveMode, Watcher};
61 |
62 | // Setup websocket receiver.
63 | let websocket_address = format!("{}:{:?}", address, RELOAD_PORT);
64 | let listener = TcpListener::bind(websocket_address).unwrap();
65 |
66 | // The only incoming message we expect to receive is the initial handshake.
67 | for stream in listener.incoming() {
68 | let path = path.to_owned();
69 |
70 | thread::spawn(move || {
71 | if let Ok(mut stream) = stream {
72 | handle_websocket_handshake(&mut stream);
73 |
74 | // We do not handle ping/pong requests. Is that bad?
75 | // This code also assumes the client will never send any messages
76 | // other than the initial handshake.
77 | let (tx, rx) = std::sync::mpsc::channel();
78 | /* Is a 10ms delay here too short?*/
79 | let mut watcher: RecommendedWatcher =
80 | Watcher::new(tx, std::time::Duration::from_millis(10)).unwrap();
81 | watcher
82 | .watch(Path::new(&path), RecursiveMode::Recursive)
83 | .unwrap();
84 |
85 | // Watch for file changes until the socket closes.
86 | loop {
87 | match rx.recv() {
88 | Ok(event) => {
89 | // Only refresh the web page for new and modified files
90 | // For now do not refresh for removed or renamed files, but in the future
91 | // it may be better to refresh then to immediately reflect path errors.
92 | let refresh = match event {
93 | NoticeWrite(..) | NoticeRemove(..) | Remove(..) | Rename(..)
94 | | Rescan => false,
95 | Create(..) | Write(..) | Chmod(..) => true,
96 | Error(..) => panic!(),
97 | };
98 |
99 | if refresh {
100 | // A blank message is sent triggering a refresh on any file change.
101 | // If this message fails to send, then likely the socket has been closed.
102 | if send_websocket_message(&stream).is_err() {
103 | break;
104 | };
105 | }
106 | }
107 | Err(e) => println!("File watch error: {:?}", e),
108 | };
109 | }
110 | }
111 | });
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "autocfg"
7 | version = "1.1.0"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
10 |
11 | [[package]]
12 | name = "base64"
13 | version = "0.11.0"
14 | source = "registry+https://github.com/rust-lang/crates.io-index"
15 | checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
16 |
17 | [[package]]
18 | name = "bitflags"
19 | version = "1.3.2"
20 | source = "registry+https://github.com/rust-lang/crates.io-index"
21 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
22 |
23 | [[package]]
24 | name = "block-buffer"
25 | version = "0.7.3"
26 | source = "registry+https://github.com/rust-lang/crates.io-index"
27 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
28 | dependencies = [
29 | "block-padding",
30 | "byte-tools",
31 | "byteorder",
32 | "generic-array",
33 | ]
34 |
35 | [[package]]
36 | name = "block-padding"
37 | version = "0.1.5"
38 | source = "registry+https://github.com/rust-lang/crates.io-index"
39 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
40 | dependencies = [
41 | "byte-tools",
42 | ]
43 |
44 | [[package]]
45 | name = "byte-tools"
46 | version = "0.3.1"
47 | source = "registry+https://github.com/rust-lang/crates.io-index"
48 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
49 |
50 | [[package]]
51 | name = "byteorder"
52 | version = "1.4.3"
53 | source = "registry+https://github.com/rust-lang/crates.io-index"
54 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
55 |
56 | [[package]]
57 | name = "cc"
58 | version = "1.0.79"
59 | source = "registry+https://github.com/rust-lang/crates.io-index"
60 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
61 |
62 | [[package]]
63 | name = "cfg-if"
64 | version = "0.1.10"
65 | source = "registry+https://github.com/rust-lang/crates.io-index"
66 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
67 |
68 | [[package]]
69 | name = "cfg-if"
70 | version = "1.0.0"
71 | source = "registry+https://github.com/rust-lang/crates.io-index"
72 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
73 |
74 | [[package]]
75 | name = "core-foundation"
76 | version = "0.9.3"
77 | source = "registry+https://github.com/rust-lang/crates.io-index"
78 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
79 | dependencies = [
80 | "core-foundation-sys",
81 | "libc",
82 | ]
83 |
84 | [[package]]
85 | name = "core-foundation-sys"
86 | version = "0.8.3"
87 | source = "registry+https://github.com/rust-lang/crates.io-index"
88 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
89 |
90 | [[package]]
91 | name = "devserver"
92 | version = "0.4.3"
93 | dependencies = [
94 | "devserver_lib",
95 | ]
96 |
97 | [[package]]
98 | name = "devserver_lib"
99 | version = "0.4.3"
100 | dependencies = [
101 | "base64",
102 | "native-tls",
103 | "notify",
104 | "sha-1",
105 | ]
106 |
107 | [[package]]
108 | name = "digest"
109 | version = "0.8.1"
110 | source = "registry+https://github.com/rust-lang/crates.io-index"
111 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
112 | dependencies = [
113 | "generic-array",
114 | ]
115 |
116 | [[package]]
117 | name = "errno"
118 | version = "0.2.8"
119 | source = "registry+https://github.com/rust-lang/crates.io-index"
120 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
121 | dependencies = [
122 | "errno-dragonfly",
123 | "libc",
124 | "winapi 0.3.9",
125 | ]
126 |
127 | [[package]]
128 | name = "errno-dragonfly"
129 | version = "0.1.2"
130 | source = "registry+https://github.com/rust-lang/crates.io-index"
131 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
132 | dependencies = [
133 | "cc",
134 | "libc",
135 | ]
136 |
137 | [[package]]
138 | name = "fake-simd"
139 | version = "0.1.2"
140 | source = "registry+https://github.com/rust-lang/crates.io-index"
141 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
142 |
143 | [[package]]
144 | name = "fastrand"
145 | version = "1.9.0"
146 | source = "registry+https://github.com/rust-lang/crates.io-index"
147 | checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
148 | dependencies = [
149 | "instant",
150 | ]
151 |
152 | [[package]]
153 | name = "filetime"
154 | version = "0.2.20"
155 | source = "registry+https://github.com/rust-lang/crates.io-index"
156 | checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412"
157 | dependencies = [
158 | "cfg-if 1.0.0",
159 | "libc",
160 | "redox_syscall",
161 | "windows-sys 0.45.0",
162 | ]
163 |
164 | [[package]]
165 | name = "foreign-types"
166 | version = "0.3.2"
167 | source = "registry+https://github.com/rust-lang/crates.io-index"
168 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
169 | dependencies = [
170 | "foreign-types-shared",
171 | ]
172 |
173 | [[package]]
174 | name = "foreign-types-shared"
175 | version = "0.1.1"
176 | source = "registry+https://github.com/rust-lang/crates.io-index"
177 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
178 |
179 | [[package]]
180 | name = "fsevent"
181 | version = "0.4.0"
182 | source = "registry+https://github.com/rust-lang/crates.io-index"
183 | checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6"
184 | dependencies = [
185 | "bitflags",
186 | "fsevent-sys",
187 | ]
188 |
189 | [[package]]
190 | name = "fsevent-sys"
191 | version = "2.0.1"
192 | source = "registry+https://github.com/rust-lang/crates.io-index"
193 | checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0"
194 | dependencies = [
195 | "libc",
196 | ]
197 |
198 | [[package]]
199 | name = "fuchsia-zircon"
200 | version = "0.3.3"
201 | source = "registry+https://github.com/rust-lang/crates.io-index"
202 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
203 | dependencies = [
204 | "bitflags",
205 | "fuchsia-zircon-sys",
206 | ]
207 |
208 | [[package]]
209 | name = "fuchsia-zircon-sys"
210 | version = "0.3.3"
211 | source = "registry+https://github.com/rust-lang/crates.io-index"
212 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
213 |
214 | [[package]]
215 | name = "generic-array"
216 | version = "0.12.4"
217 | source = "registry+https://github.com/rust-lang/crates.io-index"
218 | checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
219 | dependencies = [
220 | "typenum",
221 | ]
222 |
223 | [[package]]
224 | name = "hermit-abi"
225 | version = "0.3.1"
226 | source = "registry+https://github.com/rust-lang/crates.io-index"
227 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
228 |
229 | [[package]]
230 | name = "inotify"
231 | version = "0.7.1"
232 | source = "registry+https://github.com/rust-lang/crates.io-index"
233 | checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f"
234 | dependencies = [
235 | "bitflags",
236 | "inotify-sys",
237 | "libc",
238 | ]
239 |
240 | [[package]]
241 | name = "inotify-sys"
242 | version = "0.1.5"
243 | source = "registry+https://github.com/rust-lang/crates.io-index"
244 | checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
245 | dependencies = [
246 | "libc",
247 | ]
248 |
249 | [[package]]
250 | name = "instant"
251 | version = "0.1.12"
252 | source = "registry+https://github.com/rust-lang/crates.io-index"
253 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
254 | dependencies = [
255 | "cfg-if 1.0.0",
256 | ]
257 |
258 | [[package]]
259 | name = "io-lifetimes"
260 | version = "1.0.9"
261 | source = "registry+https://github.com/rust-lang/crates.io-index"
262 | checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb"
263 | dependencies = [
264 | "hermit-abi",
265 | "libc",
266 | "windows-sys 0.45.0",
267 | ]
268 |
269 | [[package]]
270 | name = "iovec"
271 | version = "0.1.4"
272 | source = "registry+https://github.com/rust-lang/crates.io-index"
273 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
274 | dependencies = [
275 | "libc",
276 | ]
277 |
278 | [[package]]
279 | name = "kernel32-sys"
280 | version = "0.2.2"
281 | source = "registry+https://github.com/rust-lang/crates.io-index"
282 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
283 | dependencies = [
284 | "winapi 0.2.8",
285 | "winapi-build",
286 | ]
287 |
288 | [[package]]
289 | name = "lazy_static"
290 | version = "1.4.0"
291 | source = "registry+https://github.com/rust-lang/crates.io-index"
292 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
293 |
294 | [[package]]
295 | name = "lazycell"
296 | version = "1.3.0"
297 | source = "registry+https://github.com/rust-lang/crates.io-index"
298 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
299 |
300 | [[package]]
301 | name = "libc"
302 | version = "0.2.140"
303 | source = "registry+https://github.com/rust-lang/crates.io-index"
304 | checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
305 |
306 | [[package]]
307 | name = "linux-raw-sys"
308 | version = "0.1.4"
309 | source = "registry+https://github.com/rust-lang/crates.io-index"
310 | checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
311 |
312 | [[package]]
313 | name = "log"
314 | version = "0.4.17"
315 | source = "registry+https://github.com/rust-lang/crates.io-index"
316 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
317 | dependencies = [
318 | "cfg-if 1.0.0",
319 | ]
320 |
321 | [[package]]
322 | name = "mio"
323 | version = "0.6.23"
324 | source = "registry+https://github.com/rust-lang/crates.io-index"
325 | checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
326 | dependencies = [
327 | "cfg-if 0.1.10",
328 | "fuchsia-zircon",
329 | "fuchsia-zircon-sys",
330 | "iovec",
331 | "kernel32-sys",
332 | "libc",
333 | "log",
334 | "miow",
335 | "net2",
336 | "slab",
337 | "winapi 0.2.8",
338 | ]
339 |
340 | [[package]]
341 | name = "mio-extras"
342 | version = "2.0.6"
343 | source = "registry+https://github.com/rust-lang/crates.io-index"
344 | checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
345 | dependencies = [
346 | "lazycell",
347 | "log",
348 | "mio",
349 | "slab",
350 | ]
351 |
352 | [[package]]
353 | name = "miow"
354 | version = "0.2.2"
355 | source = "registry+https://github.com/rust-lang/crates.io-index"
356 | checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
357 | dependencies = [
358 | "kernel32-sys",
359 | "net2",
360 | "winapi 0.2.8",
361 | "ws2_32-sys",
362 | ]
363 |
364 | [[package]]
365 | name = "native-tls"
366 | version = "0.2.11"
367 | source = "registry+https://github.com/rust-lang/crates.io-index"
368 | checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
369 | dependencies = [
370 | "lazy_static",
371 | "libc",
372 | "log",
373 | "openssl",
374 | "openssl-probe",
375 | "openssl-sys",
376 | "schannel",
377 | "security-framework",
378 | "security-framework-sys",
379 | "tempfile",
380 | ]
381 |
382 | [[package]]
383 | name = "net2"
384 | version = "0.2.38"
385 | source = "registry+https://github.com/rust-lang/crates.io-index"
386 | checksum = "74d0df99cfcd2530b2e694f6e17e7f37b8e26bb23983ac530c0c97408837c631"
387 | dependencies = [
388 | "cfg-if 0.1.10",
389 | "libc",
390 | "winapi 0.3.9",
391 | ]
392 |
393 | [[package]]
394 | name = "notify"
395 | version = "4.0.17"
396 | source = "registry+https://github.com/rust-lang/crates.io-index"
397 | checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257"
398 | dependencies = [
399 | "bitflags",
400 | "filetime",
401 | "fsevent",
402 | "fsevent-sys",
403 | "inotify",
404 | "libc",
405 | "mio",
406 | "mio-extras",
407 | "walkdir",
408 | "winapi 0.3.9",
409 | ]
410 |
411 | [[package]]
412 | name = "once_cell"
413 | version = "1.17.1"
414 | source = "registry+https://github.com/rust-lang/crates.io-index"
415 | checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
416 |
417 | [[package]]
418 | name = "opaque-debug"
419 | version = "0.2.3"
420 | source = "registry+https://github.com/rust-lang/crates.io-index"
421 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
422 |
423 | [[package]]
424 | name = "openssl"
425 | version = "0.10.48"
426 | source = "registry+https://github.com/rust-lang/crates.io-index"
427 | checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2"
428 | dependencies = [
429 | "bitflags",
430 | "cfg-if 1.0.0",
431 | "foreign-types",
432 | "libc",
433 | "once_cell",
434 | "openssl-macros",
435 | "openssl-sys",
436 | ]
437 |
438 | [[package]]
439 | name = "openssl-macros"
440 | version = "0.1.0"
441 | source = "registry+https://github.com/rust-lang/crates.io-index"
442 | checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
443 | dependencies = [
444 | "proc-macro2",
445 | "quote",
446 | "syn",
447 | ]
448 |
449 | [[package]]
450 | name = "openssl-probe"
451 | version = "0.1.5"
452 | source = "registry+https://github.com/rust-lang/crates.io-index"
453 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
454 |
455 | [[package]]
456 | name = "openssl-sys"
457 | version = "0.9.83"
458 | source = "registry+https://github.com/rust-lang/crates.io-index"
459 | checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b"
460 | dependencies = [
461 | "autocfg",
462 | "cc",
463 | "libc",
464 | "pkg-config",
465 | "vcpkg",
466 | ]
467 |
468 | [[package]]
469 | name = "pkg-config"
470 | version = "0.3.26"
471 | source = "registry+https://github.com/rust-lang/crates.io-index"
472 | checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
473 |
474 | [[package]]
475 | name = "proc-macro2"
476 | version = "1.0.53"
477 | source = "registry+https://github.com/rust-lang/crates.io-index"
478 | checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73"
479 | dependencies = [
480 | "unicode-ident",
481 | ]
482 |
483 | [[package]]
484 | name = "quote"
485 | version = "1.0.26"
486 | source = "registry+https://github.com/rust-lang/crates.io-index"
487 | checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
488 | dependencies = [
489 | "proc-macro2",
490 | ]
491 |
492 | [[package]]
493 | name = "redox_syscall"
494 | version = "0.2.16"
495 | source = "registry+https://github.com/rust-lang/crates.io-index"
496 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
497 | dependencies = [
498 | "bitflags",
499 | ]
500 |
501 | [[package]]
502 | name = "rustix"
503 | version = "0.36.11"
504 | source = "registry+https://github.com/rust-lang/crates.io-index"
505 | checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e"
506 | dependencies = [
507 | "bitflags",
508 | "errno",
509 | "io-lifetimes",
510 | "libc",
511 | "linux-raw-sys",
512 | "windows-sys 0.45.0",
513 | ]
514 |
515 | [[package]]
516 | name = "same-file"
517 | version = "1.0.6"
518 | source = "registry+https://github.com/rust-lang/crates.io-index"
519 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
520 | dependencies = [
521 | "winapi-util",
522 | ]
523 |
524 | [[package]]
525 | name = "schannel"
526 | version = "0.1.21"
527 | source = "registry+https://github.com/rust-lang/crates.io-index"
528 | checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
529 | dependencies = [
530 | "windows-sys 0.42.0",
531 | ]
532 |
533 | [[package]]
534 | name = "security-framework"
535 | version = "2.8.2"
536 | source = "registry+https://github.com/rust-lang/crates.io-index"
537 | checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254"
538 | dependencies = [
539 | "bitflags",
540 | "core-foundation",
541 | "core-foundation-sys",
542 | "libc",
543 | "security-framework-sys",
544 | ]
545 |
546 | [[package]]
547 | name = "security-framework-sys"
548 | version = "2.8.0"
549 | source = "registry+https://github.com/rust-lang/crates.io-index"
550 | checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4"
551 | dependencies = [
552 | "core-foundation-sys",
553 | "libc",
554 | ]
555 |
556 | [[package]]
557 | name = "sha-1"
558 | version = "0.8.2"
559 | source = "registry+https://github.com/rust-lang/crates.io-index"
560 | checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
561 | dependencies = [
562 | "block-buffer",
563 | "digest",
564 | "fake-simd",
565 | "opaque-debug",
566 | ]
567 |
568 | [[package]]
569 | name = "slab"
570 | version = "0.4.8"
571 | source = "registry+https://github.com/rust-lang/crates.io-index"
572 | checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
573 | dependencies = [
574 | "autocfg",
575 | ]
576 |
577 | [[package]]
578 | name = "syn"
579 | version = "1.0.109"
580 | source = "registry+https://github.com/rust-lang/crates.io-index"
581 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
582 | dependencies = [
583 | "proc-macro2",
584 | "quote",
585 | "unicode-ident",
586 | ]
587 |
588 | [[package]]
589 | name = "tempfile"
590 | version = "3.4.0"
591 | source = "registry+https://github.com/rust-lang/crates.io-index"
592 | checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95"
593 | dependencies = [
594 | "cfg-if 1.0.0",
595 | "fastrand",
596 | "redox_syscall",
597 | "rustix",
598 | "windows-sys 0.42.0",
599 | ]
600 |
601 | [[package]]
602 | name = "typenum"
603 | version = "1.16.0"
604 | source = "registry+https://github.com/rust-lang/crates.io-index"
605 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
606 |
607 | [[package]]
608 | name = "unicode-ident"
609 | version = "1.0.8"
610 | source = "registry+https://github.com/rust-lang/crates.io-index"
611 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
612 |
613 | [[package]]
614 | name = "vcpkg"
615 | version = "0.2.15"
616 | source = "registry+https://github.com/rust-lang/crates.io-index"
617 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
618 |
619 | [[package]]
620 | name = "walkdir"
621 | version = "2.3.3"
622 | source = "registry+https://github.com/rust-lang/crates.io-index"
623 | checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
624 | dependencies = [
625 | "same-file",
626 | "winapi-util",
627 | ]
628 |
629 | [[package]]
630 | name = "winapi"
631 | version = "0.2.8"
632 | source = "registry+https://github.com/rust-lang/crates.io-index"
633 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
634 |
635 | [[package]]
636 | name = "winapi"
637 | version = "0.3.9"
638 | source = "registry+https://github.com/rust-lang/crates.io-index"
639 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
640 | dependencies = [
641 | "winapi-i686-pc-windows-gnu",
642 | "winapi-x86_64-pc-windows-gnu",
643 | ]
644 |
645 | [[package]]
646 | name = "winapi-build"
647 | version = "0.1.1"
648 | source = "registry+https://github.com/rust-lang/crates.io-index"
649 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
650 |
651 | [[package]]
652 | name = "winapi-i686-pc-windows-gnu"
653 | version = "0.4.0"
654 | source = "registry+https://github.com/rust-lang/crates.io-index"
655 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
656 |
657 | [[package]]
658 | name = "winapi-util"
659 | version = "0.1.5"
660 | source = "registry+https://github.com/rust-lang/crates.io-index"
661 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
662 | dependencies = [
663 | "winapi 0.3.9",
664 | ]
665 |
666 | [[package]]
667 | name = "winapi-x86_64-pc-windows-gnu"
668 | version = "0.4.0"
669 | source = "registry+https://github.com/rust-lang/crates.io-index"
670 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
671 |
672 | [[package]]
673 | name = "windows-sys"
674 | version = "0.42.0"
675 | source = "registry+https://github.com/rust-lang/crates.io-index"
676 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
677 | dependencies = [
678 | "windows_aarch64_gnullvm",
679 | "windows_aarch64_msvc",
680 | "windows_i686_gnu",
681 | "windows_i686_msvc",
682 | "windows_x86_64_gnu",
683 | "windows_x86_64_gnullvm",
684 | "windows_x86_64_msvc",
685 | ]
686 |
687 | [[package]]
688 | name = "windows-sys"
689 | version = "0.45.0"
690 | source = "registry+https://github.com/rust-lang/crates.io-index"
691 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
692 | dependencies = [
693 | "windows-targets",
694 | ]
695 |
696 | [[package]]
697 | name = "windows-targets"
698 | version = "0.42.2"
699 | source = "registry+https://github.com/rust-lang/crates.io-index"
700 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
701 | dependencies = [
702 | "windows_aarch64_gnullvm",
703 | "windows_aarch64_msvc",
704 | "windows_i686_gnu",
705 | "windows_i686_msvc",
706 | "windows_x86_64_gnu",
707 | "windows_x86_64_gnullvm",
708 | "windows_x86_64_msvc",
709 | ]
710 |
711 | [[package]]
712 | name = "windows_aarch64_gnullvm"
713 | version = "0.42.2"
714 | source = "registry+https://github.com/rust-lang/crates.io-index"
715 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
716 |
717 | [[package]]
718 | name = "windows_aarch64_msvc"
719 | version = "0.42.2"
720 | source = "registry+https://github.com/rust-lang/crates.io-index"
721 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
722 |
723 | [[package]]
724 | name = "windows_i686_gnu"
725 | version = "0.42.2"
726 | source = "registry+https://github.com/rust-lang/crates.io-index"
727 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
728 |
729 | [[package]]
730 | name = "windows_i686_msvc"
731 | version = "0.42.2"
732 | source = "registry+https://github.com/rust-lang/crates.io-index"
733 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
734 |
735 | [[package]]
736 | name = "windows_x86_64_gnu"
737 | version = "0.42.2"
738 | source = "registry+https://github.com/rust-lang/crates.io-index"
739 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
740 |
741 | [[package]]
742 | name = "windows_x86_64_gnullvm"
743 | version = "0.42.2"
744 | source = "registry+https://github.com/rust-lang/crates.io-index"
745 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
746 |
747 | [[package]]
748 | name = "windows_x86_64_msvc"
749 | version = "0.42.2"
750 | source = "registry+https://github.com/rust-lang/crates.io-index"
751 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
752 |
753 | [[package]]
754 | name = "ws2_32-sys"
755 | version = "0.2.1"
756 | source = "registry+https://github.com/rust-lang/crates.io-index"
757 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
758 | dependencies = [
759 | "winapi 0.2.8",
760 | "winapi-build",
761 | ]
762 |
--------------------------------------------------------------------------------
/devserver_lib/src/lib.rs:
--------------------------------------------------------------------------------
1 | /// A local host only for serving static files.
2 | /// Simple and easy, but not robust or tested.
3 |
4 | #[cfg(feature = "https")]
5 | use native_tls::{Identity, TlsAcceptor};
6 | #[cfg(feature = "https")]
7 | use std::sync::Arc;
8 |
9 | use std::ffi::OsStr;
10 | use std::fs;
11 | use std::io::BufRead;
12 | use std::io::{Read, Write};
13 | use std::net::TcpListener;
14 | use std::path::Path;
15 | use std::str;
16 | use std::thread;
17 |
18 | #[cfg(feature = "reload")]
19 | mod reload;
20 |
21 | pub fn read_header(stream: &mut T) -> Vec {
22 | let mut buffer = Vec::new();
23 | let mut reader = std::io::BufReader::new(stream);
24 | loop {
25 | reader.read_until(b'\n', &mut buffer).unwrap();
26 | // Read until end of header.
27 | if buffer.ends_with(b"\r\n\r\n") {
28 | break;
29 | }
30 | }
31 | buffer
32 | }
33 |
34 | #[allow(unused)]
35 | fn handle_client(mut stream: T, root_path: &str, reload: bool, headers: &str) {
36 | let buffer = read_header(&mut stream);
37 | let request_string = str::from_utf8(&buffer).unwrap();
38 |
39 | if request_string.is_empty() {
40 | return;
41 | }
42 |
43 | // Split the request into different parts.
44 | let mut parts = request_string.split(' ');
45 |
46 | let _method = parts.next().unwrap().trim();
47 | let mut path = parts.next().unwrap().trim();
48 | let _http_version = parts.next().unwrap().trim();
49 |
50 | // Trim parameters from URL
51 | if let Some(parameters_index) = path.find('?') {
52 | path = &path[..parameters_index];
53 | }
54 |
55 | // Replace white space characters with proper whitespace and remove any paths that refer to the parent.
56 | let path = path.replace("../", "").replace("%20", " ");
57 | let path = if path.ends_with('/') {
58 | Path::new(root_path).join(Path::new(&format!(
59 | "{}{}",
60 | path.trim_start_matches('/'),
61 | "index.html"
62 | )))
63 | } else {
64 | Path::new(root_path).join(path.trim_matches('/'))
65 | };
66 |
67 | let extension = path.extension().and_then(OsStr::to_str);
68 |
69 | let (file_contents, extension) = if extension.is_some() {
70 | (fs::read(&path), extension)
71 | } else {
72 | // If the request has no extension look first for a matching file without an extension
73 | if let Ok(file_contents) = fs::read(&path) {
74 | println!("WARNING: Serving file without extension: [ {} ] with media type 'application/octet-stream'", &path.to_str().unwrap());
75 | (Ok(file_contents), None)
76 | } else {
77 | // If no file without an extension is found see if there's a file with a ".html" extension
78 | // This enables "pretty URLs" without a trailing `/` like: `example.com/blog-post`
79 | let file = fs::read(path.with_extension("html"));
80 | (file, Some("html"))
81 | }
82 | };
83 |
84 | if let Ok(mut file_contents) = file_contents {
85 | // Pair the file extension to a media (also known as MIME) type.
86 | let content_type = extension_to_mime_impl(extension);
87 |
88 | #[allow(unused_mut)]
89 | let mut content_length = file_contents.len();
90 |
91 | // Prepare to inject code into HTML if reload is enabled.
92 | #[cfg(feature = "reload")]
93 | let reload_append = include_bytes!("reload.html");
94 | #[cfg(feature = "reload")]
95 | {
96 | if extension == Some("html") && reload {
97 | content_length += reload_append.len();
98 | }
99 | }
100 |
101 | let response = format!(
102 | "HTTP/1.1 200 OK\r\nContent-type: {}\r\nContent-Length: {}{}\r\n\r\n",
103 | content_type, content_length, headers
104 | );
105 |
106 | let mut bytes = response.as_bytes().to_vec();
107 | bytes.append(&mut file_contents);
108 | stream.write_all(&bytes).unwrap();
109 |
110 | // Inject code into HTML if reload is enabled
111 | #[cfg(feature = "reload")]
112 | {
113 | if extension == Some("html") && reload {
114 | // Insert javascript for reloading
115 | stream.write_all(reload_append).unwrap();
116 | }
117 | }
118 |
119 | stream.flush().unwrap();
120 | } else {
121 | println!("Could not find file: {}", path.to_str().unwrap());
122 | let response = "HTTP/1.1 404 NOT FOUND\r\n\r\n";
123 | stream.write_all(response.as_bytes()).unwrap();
124 | stream.flush().unwrap();
125 | }
126 | }
127 |
128 | pub fn run(address: &str, port: u32, path: &str, reload: bool, headers: &str) {
129 | #[cfg(feature = "https")]
130 | let acceptor = {
131 | // Hard coded certificate generated with the following commands:
132 | // openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 36500 -nodes -subj "/"
133 | // openssl pkcs12 -export -out identity.pfx -inkey key.pem -in cert.pem
134 | // password for second command: 'debug'
135 | let bytes = include_bytes!("identity.pfx");
136 | let identity = Identity::from_pkcs12(bytes, "debug").unwrap();
137 | Arc::new(TlsAcceptor::new(identity).unwrap())
138 | };
139 |
140 | #[cfg(feature = "reload")]
141 | {
142 | if reload {
143 | let address = address.to_owned();
144 | let path = path.to_owned();
145 | thread::spawn(move || {
146 | reload::watch_for_reloads(&address, &path);
147 | });
148 | }
149 | }
150 |
151 | let address_with_port = format!("{}:{:?}", address, port);
152 | let listener = TcpListener::bind(address_with_port).unwrap();
153 | for stream in listener.incoming().flatten() {
154 | #[cfg(feature = "https")]
155 | let acceptor = acceptor.clone();
156 |
157 | let path = path.to_owned();
158 | let headers = headers.to_owned();
159 | thread::spawn(move || {
160 | // HTTP requests always begin with a verb like 'GET'.
161 | // HTTPS requests begin with a number, so peeking and checking for a number
162 | // is used to determine if a request is HTTPS or HTTP
163 | let mut buf = [0; 2];
164 | stream.peek(&mut buf).expect("peek failed");
165 |
166 | #[cfg(feature = "https")]
167 | let is_https = !((buf[0] as char).is_alphabetic() && (buf[1] as char).is_alphabetic());
168 |
169 | #[cfg(not(feature = "https"))]
170 | let is_https = false;
171 |
172 | if is_https {
173 | // acceptor.accept will block indefinitely if called with an HTTP stream.
174 | #[cfg(feature = "https")]
175 | if let Ok(stream) = acceptor.accept(stream) {
176 | handle_client(stream, &path, reload, &headers);
177 | }
178 | } else {
179 | handle_client(stream, &path, reload, &headers);
180 | }
181 | });
182 | }
183 | }
184 |
185 | /// Taken from Rouille:
186 | /// https://github.com/tomaka/rouille/blob/master/src/assets.rs
187 | /// Returns the mime type of a file based on its extension.
188 | fn extension_to_mime_impl(extension: Option<&str>) -> &'static str {
189 | // List taken from https://github.com/cybergeek94/mime_guess/blob/master/src/mime_types.rs,
190 | // itself taken from a dead link.
191 | match extension {
192 | Some("323") => "text/h323; charset=utf8",
193 | Some("3g2") => "video/3gpp2",
194 | Some("3gp") => "video/3gpp",
195 | Some("3gp2") => "video/3gpp2",
196 | Some("3gpp") => "video/3gpp",
197 | Some("7z") => "application/x-7z-compressed",
198 | Some("aa") => "audio/audible",
199 | Some("aac") => "audio/aac",
200 | Some("aaf") => "application/octet-stream",
201 | Some("aax") => "audio/vnd.audible.aax",
202 | Some("ac3") => "audio/ac3",
203 | Some("aca") => "application/octet-stream",
204 | Some("accda") => "application/msaccess.addin",
205 | Some("accdb") => "application/msaccess",
206 | Some("accdc") => "application/msaccess.cab",
207 | Some("accde") => "application/msaccess",
208 | Some("accdr") => "application/msaccess.runtime",
209 | Some("accdt") => "application/msaccess",
210 | Some("accdw") => "application/msaccess.webapplication",
211 | Some("accft") => "application/msaccess.ftemplate",
212 | Some("acx") => "application/internet-property-stream",
213 | Some("addin") => "application/xml",
214 | Some("ade") => "application/msaccess",
215 | Some("adobebridge") => "application/x-bridge-url",
216 | Some("adp") => "application/msaccess",
217 | Some("adt") => "audio/vnd.dlna.adts",
218 | Some("adts") => "audio/aac",
219 | Some("afm") => "application/octet-stream",
220 | Some("ai") => "application/postscript",
221 | Some("aif") => "audio/x-aiff",
222 | Some("aifc") => "audio/aiff",
223 | Some("aiff") => "audio/aiff",
224 | Some("air") => "application/vnd.adobe.air-application-installer-package+zip",
225 | Some("amc") => "application/x-mpeg",
226 | Some("application") => "application/x-ms-application",
227 | Some("art") => "image/x-jg",
228 | Some("asa") => "application/xml",
229 | Some("asax") => "application/xml",
230 | Some("ascx") => "application/xml",
231 | Some("asd") => "application/octet-stream",
232 | Some("asf") => "video/x-ms-asf",
233 | Some("ashx") => "application/xml",
234 | Some("asi") => "application/octet-stream",
235 | Some("asm") => "text/plain; charset=utf8",
236 | Some("asmx") => "application/xml",
237 | Some("aspx") => "application/xml",
238 | Some("asr") => "video/x-ms-asf",
239 | Some("asx") => "video/x-ms-asf",
240 | Some("atom") => "application/atom+xml",
241 | Some("au") => "audio/basic",
242 | Some("avi") => "video/x-msvideo",
243 | Some("axs") => "application/olescript",
244 | Some("bas") => "text/plain; charset=utf8",
245 | Some("bcpio") => "application/x-bcpio",
246 | Some("bin") => "application/octet-stream",
247 | Some("bmp") => "image/bmp",
248 | Some("c") => "text/plain; charset=utf8",
249 | Some("cab") => "application/octet-stream",
250 | Some("caf") => "audio/x-caf",
251 | Some("calx") => "application/vnd.ms-office.calx",
252 | Some("cat") => "application/vnd.ms-pki.seccat",
253 | Some("cc") => "text/plain; charset=utf8",
254 | Some("cd") => "text/plain; charset=utf8",
255 | Some("cdda") => "audio/aiff",
256 | Some("cdf") => "application/x-cdf",
257 | Some("cer") => "application/x-x509-ca-cert",
258 | Some("chm") => "application/octet-stream",
259 | Some("class") => "application/x-java-applet",
260 | Some("clp") => "application/x-msclip",
261 | Some("cmx") => "image/x-cmx",
262 | Some("cnf") => "text/plain; charset=utf8",
263 | Some("cod") => "image/cis-cod",
264 | Some("config") => "application/xml",
265 | Some("contact") => "text/x-ms-contact; charset=utf8",
266 | Some("coverage") => "application/xml",
267 | Some("cpio") => "application/x-cpio",
268 | Some("cpp") => "text/plain; charset=utf8",
269 | Some("crd") => "application/x-mscardfile",
270 | Some("crl") => "application/pkix-crl",
271 | Some("crt") => "application/x-x509-ca-cert",
272 | Some("cs") => "text/plain; charset=utf8",
273 | Some("csdproj") => "text/plain; charset=utf8",
274 | Some("csh") => "application/x-csh",
275 | Some("csproj") => "text/plain; charset=utf8",
276 | Some("css") => "text/css; charset=utf8",
277 | Some("csv") => "text/csv; charset=utf8",
278 | Some("cur") => "application/octet-stream",
279 | Some("cxx") => "text/plain; charset=utf8",
280 | Some("dat") => "application/octet-stream",
281 | Some("datasource") => "application/xml",
282 | Some("dbproj") => "text/plain; charset=utf8",
283 | Some("dcr") => "application/x-director",
284 | Some("def") => "text/plain; charset=utf8",
285 | Some("deploy") => "application/octet-stream",
286 | Some("der") => "application/x-x509-ca-cert",
287 | Some("dgml") => "application/xml",
288 | Some("dib") => "image/bmp",
289 | Some("dif") => "video/x-dv",
290 | Some("dir") => "application/x-director",
291 | Some("disco") => "application/xml",
292 | Some("dll") => "application/x-msdownload",
293 | Some("dll.config") => "application/xml",
294 | Some("dlm") => "text/dlm; charset=utf8",
295 | Some("doc") => "application/msword",
296 | Some("docm") => "application/vnd.ms-word.document.macroEnabled.12",
297 | Some("docx") => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
298 | Some("dot") => "application/msword",
299 | Some("dotm") => "application/vnd.ms-word.template.macroEnabled.12",
300 | Some("dotx") => "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
301 | Some("dsp") => "application/octet-stream",
302 | Some("dsw") => "text/plain; charset=utf8",
303 | Some("dtd") => "application/xml",
304 | Some("dtsConfig") => "application/xml",
305 | Some("dv") => "video/x-dv",
306 | Some("dvi") => "application/x-dvi",
307 | Some("dwf") => "drawing/x-dwf",
308 | Some("dwp") => "application/octet-stream",
309 | Some("dxr") => "application/x-director",
310 | Some("eml") => "message/rfc822",
311 | Some("emz") => "application/octet-stream",
312 | Some("eot") => "application/vnd.ms-fontobject",
313 | Some("eps") => "application/postscript",
314 | Some("etl") => "application/etl",
315 | Some("etx") => "text/x-setext; charset=utf8",
316 | Some("evy") => "application/envoy",
317 | Some("exe") => "application/octet-stream",
318 | Some("exe.config") => "application/xml",
319 | Some("fdf") => "application/vnd.fdf",
320 | Some("fif") => "application/fractals",
321 | Some("filters") => "Application/xml",
322 | Some("fla") => "application/octet-stream",
323 | Some("flr") => "x-world/x-vrml",
324 | Some("flv") => "video/x-flv",
325 | Some("fsscript") => "application/fsharp-script",
326 | Some("fsx") => "application/fsharp-script",
327 | Some("generictest") => "application/xml",
328 | Some("gif") => "image/gif",
329 | Some("group") => "text/x-ms-group; charset=utf8",
330 | Some("gsm") => "audio/x-gsm",
331 | Some("gtar") => "application/x-gtar",
332 | Some("gz") => "application/x-gzip",
333 | Some("h") => "text/plain; charset=utf8",
334 | Some("hdf") => "application/x-hdf",
335 | Some("hdml") => "text/x-hdml; charset=utf8",
336 | Some("hhc") => "application/x-oleobject",
337 | Some("hhk") => "application/octet-stream",
338 | Some("hhp") => "application/octet-stream",
339 | Some("hlp") => "application/winhlp",
340 | Some("hpp") => "text/plain; charset=utf8",
341 | Some("hqx") => "application/mac-binhex40",
342 | Some("hta") => "application/hta",
343 | Some("htc") => "text/x-component; charset=utf8",
344 | Some("htm") => "text/html; charset=utf8",
345 | Some("html") => "text/html; charset=utf8",
346 | Some("htt") => "text/webviewhtml; charset=utf8",
347 | Some("hxa") => "application/xml",
348 | Some("hxc") => "application/xml",
349 | Some("hxd") => "application/octet-stream",
350 | Some("hxe") => "application/xml",
351 | Some("hxf") => "application/xml",
352 | Some("hxh") => "application/octet-stream",
353 | Some("hxi") => "application/octet-stream",
354 | Some("hxk") => "application/xml",
355 | Some("hxq") => "application/octet-stream",
356 | Some("hxr") => "application/octet-stream",
357 | Some("hxs") => "application/octet-stream",
358 | Some("hxt") => "text/html; charset=utf8",
359 | Some("hxv") => "application/xml",
360 | Some("hxw") => "application/octet-stream",
361 | Some("hxx") => "text/plain; charset=utf8",
362 | Some("i") => "text/plain; charset=utf8",
363 | Some("ico") => "image/x-icon",
364 | Some("ics") => "application/octet-stream",
365 | Some("idl") => "text/plain; charset=utf8",
366 | Some("ief") => "image/ief",
367 | Some("iii") => "application/x-iphone",
368 | Some("inc") => "text/plain; charset=utf8",
369 | Some("inf") => "application/octet-stream",
370 | Some("inl") => "text/plain; charset=utf8",
371 | Some("ins") => "application/x-internet-signup",
372 | Some("ipa") => "application/x-itunes-ipa",
373 | Some("ipg") => "application/x-itunes-ipg",
374 | Some("ipproj") => "text/plain; charset=utf8",
375 | Some("ipsw") => "application/x-itunes-ipsw",
376 | Some("iqy") => "text/x-ms-iqy; charset=utf8",
377 | Some("isp") => "application/x-internet-signup",
378 | Some("ite") => "application/x-itunes-ite",
379 | Some("itlp") => "application/x-itunes-itlp",
380 | Some("itms") => "application/x-itunes-itms",
381 | Some("itpc") => "application/x-itunes-itpc",
382 | Some("ivf") => "video/x-ivf",
383 | Some("jar") => "application/java-archive",
384 | Some("java") => "application/octet-stream",
385 | Some("jck") => "application/liquidmotion",
386 | Some("jcz") => "application/liquidmotion",
387 | Some("jfif") => "image/pjpeg",
388 | Some("jnlp") => "application/x-java-jnlp-file",
389 | Some("jpb") => "application/octet-stream",
390 | Some("jpe") => "image/jpeg",
391 | Some("jpeg") => "image/jpeg",
392 | Some("jpg") => "image/jpeg",
393 | Some("js") => "application/javascript",
394 | Some("json") => "application/json",
395 | Some("jsx") => "text/jscript; charset=utf8",
396 | Some("jsxbin") => "text/plain; charset=utf8",
397 | Some("latex") => "application/x-latex",
398 | Some("library-ms") => "application/windows-library+xml",
399 | Some("lit") => "application/x-ms-reader",
400 | Some("loadtest") => "application/xml",
401 | Some("lpk") => "application/octet-stream",
402 | Some("lsf") => "video/x-la-asf",
403 | Some("lst") => "text/plain; charset=utf8",
404 | Some("lsx") => "video/x-la-asf",
405 | Some("lzh") => "application/octet-stream",
406 | Some("m13") => "application/x-msmediaview",
407 | Some("m14") => "application/x-msmediaview",
408 | Some("m1v") => "video/mpeg",
409 | Some("m2t") => "video/vnd.dlna.mpeg-tts",
410 | Some("m2ts") => "video/vnd.dlna.mpeg-tts",
411 | Some("m2v") => "video/mpeg",
412 | Some("m3u") => "audio/x-mpegurl",
413 | Some("m3u8") => "audio/x-mpegurl",
414 | Some("m4a") => "audio/m4a",
415 | Some("m4b") => "audio/m4b",
416 | Some("m4p") => "audio/m4p",
417 | Some("m4r") => "audio/x-m4r",
418 | Some("m4v") => "video/x-m4v",
419 | Some("mac") => "image/x-macpaint",
420 | Some("mak") => "text/plain; charset=utf8",
421 | Some("man") => "application/x-troff-man",
422 | Some("manifest") => "application/x-ms-manifest",
423 | Some("map") => "text/plain; charset=utf8",
424 | Some("master") => "application/xml",
425 | Some("mda") => "application/msaccess",
426 | Some("mdb") => "application/x-msaccess",
427 | Some("mde") => "application/msaccess",
428 | Some("mdp") => "application/octet-stream",
429 | Some("me") => "application/x-troff-me",
430 | Some("mfp") => "application/x-shockwave-flash",
431 | Some("mht") => "message/rfc822",
432 | Some("mhtml") => "message/rfc822",
433 | Some("mid") => "audio/mid",
434 | Some("midi") => "audio/mid",
435 | Some("mix") => "application/octet-stream",
436 | Some("mjs") => "application/javascript",
437 | Some("mk") => "text/plain; charset=utf8",
438 | Some("mmf") => "application/x-smaf",
439 | Some("mno") => "application/xml",
440 | Some("mny") => "application/x-msmoney",
441 | Some("mod") => "video/mpeg",
442 | Some("mov") => "video/quicktime",
443 | Some("movie") => "video/x-sgi-movie",
444 | Some("mp2") => "video/mpeg",
445 | Some("mp2v") => "video/mpeg",
446 | Some("mp3") => "audio/mpeg",
447 | Some("mp4") => "video/mp4",
448 | Some("mp4v") => "video/mp4",
449 | Some("mpa") => "video/mpeg",
450 | Some("mpe") => "video/mpeg",
451 | Some("mpeg") => "video/mpeg",
452 | Some("mpf") => "application/vnd.ms-mediapackage",
453 | Some("mpg") => "video/mpeg",
454 | Some("mpp") => "application/vnd.ms-project",
455 | Some("mpv2") => "video/mpeg",
456 | Some("mqv") => "video/quicktime",
457 | Some("ms") => "application/x-troff-ms",
458 | Some("msi") => "application/octet-stream",
459 | Some("mso") => "application/octet-stream",
460 | Some("mts") => "video/vnd.dlna.mpeg-tts",
461 | Some("mtx") => "application/xml",
462 | Some("mvb") => "application/x-msmediaview",
463 | Some("mvc") => "application/x-miva-compiled",
464 | Some("mxp") => "application/x-mmxp",
465 | Some("nc") => "application/x-netcdf",
466 | Some("nsc") => "video/x-ms-asf",
467 | Some("nws") => "message/rfc822",
468 | Some("ocx") => "application/octet-stream",
469 | Some("oda") => "application/oda",
470 | Some("odc") => "text/x-ms-odc; charset=utf8",
471 | Some("odh") => "text/plain; charset=utf8",
472 | Some("odl") => "text/plain; charset=utf8",
473 | Some("odp") => "application/vnd.oasis.opendocument.presentation",
474 | Some("ods") => "application/oleobject",
475 | Some("odt") => "application/vnd.oasis.opendocument.text",
476 | Some("ogg") => "application/ogg",
477 | Some("one") => "application/onenote",
478 | Some("onea") => "application/onenote",
479 | Some("onepkg") => "application/onenote",
480 | Some("onetmp") => "application/onenote",
481 | Some("onetoc") => "application/onenote",
482 | Some("onetoc2") => "application/onenote",
483 | Some("orderedtest") => "application/xml",
484 | Some("osdx") => "application/opensearchdescription+xml",
485 | Some("otf") => "application/x-font-opentype",
486 | Some("p10") => "application/pkcs10",
487 | Some("p12") => "application/x-pkcs12",
488 | Some("p7b") => "application/x-pkcs7-certificates",
489 | Some("p7c") => "application/pkcs7-mime",
490 | Some("p7m") => "application/pkcs7-mime",
491 | Some("p7r") => "application/x-pkcs7-certreqresp",
492 | Some("p7s") => "application/pkcs7-signature",
493 | Some("pbm") => "image/x-portable-bitmap",
494 | Some("pcast") => "application/x-podcast",
495 | Some("pct") => "image/pict",
496 | Some("pcx") => "application/octet-stream",
497 | Some("pcz") => "application/octet-stream",
498 | Some("pdf") => "application/pdf",
499 | Some("pfb") => "application/octet-stream",
500 | Some("pfm") => "application/octet-stream",
501 | Some("pfx") => "application/x-pkcs12",
502 | Some("pgm") => "image/x-portable-graymap",
503 | Some("pic") => "image/pict",
504 | Some("pict") => "image/pict",
505 | Some("pkgdef") => "text/plain; charset=utf8",
506 | Some("pkgundef") => "text/plain; charset=utf8",
507 | Some("pko") => "application/vnd.ms-pki.pko",
508 | Some("pls") => "audio/scpls",
509 | Some("pma") => "application/x-perfmon",
510 | Some("pmc") => "application/x-perfmon",
511 | Some("pml") => "application/x-perfmon",
512 | Some("pmr") => "application/x-perfmon",
513 | Some("pmw") => "application/x-perfmon",
514 | Some("png") => "image/png",
515 | Some("pnm") => "image/x-portable-anymap",
516 | Some("pnt") => "image/x-macpaint",
517 | Some("pntg") => "image/x-macpaint",
518 | Some("pnz") => "image/png",
519 | Some("pot") => "application/vnd.ms-powerpoint",
520 | Some("potm") => "application/vnd.ms-powerpoint.template.macroEnabled.12",
521 | Some("potx") => "application/vnd.openxmlformats-officedocument.presentationml.template",
522 | Some("ppa") => "application/vnd.ms-powerpoint",
523 | Some("ppam") => "application/vnd.ms-powerpoint.addin.macroEnabled.12",
524 | Some("ppm") => "image/x-portable-pixmap",
525 | Some("pps") => "application/vnd.ms-powerpoint",
526 | Some("ppsm") => "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
527 | Some("ppsx") => "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
528 | Some("ppt") => "application/vnd.ms-powerpoint",
529 | Some("pptm") => "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
530 | Some("pptx") => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
531 | Some("prf") => "application/pics-rules",
532 | Some("prm") => "application/octet-stream",
533 | Some("prx") => "application/octet-stream",
534 | Some("ps") => "application/postscript",
535 | Some("psc1") => "application/PowerShell",
536 | Some("psd") => "application/octet-stream",
537 | Some("psess") => "application/xml",
538 | Some("psm") => "application/octet-stream",
539 | Some("psp") => "application/octet-stream",
540 | Some("pub") => "application/x-mspublisher",
541 | Some("pwz") => "application/vnd.ms-powerpoint",
542 | Some("qht") => "text/x-html-insertion; charset=utf8",
543 | Some("qhtm") => "text/x-html-insertion; charset=utf8",
544 | Some("qt") => "video/quicktime",
545 | Some("qti") => "image/x-quicktime",
546 | Some("qtif") => "image/x-quicktime",
547 | Some("qtl") => "application/x-quicktimeplayer",
548 | Some("qxd") => "application/octet-stream",
549 | Some("ra") => "audio/x-pn-realaudio",
550 | Some("ram") => "audio/x-pn-realaudio",
551 | Some("rar") => "application/octet-stream",
552 | Some("ras") => "image/x-cmu-raster",
553 | Some("rat") => "application/rat-file",
554 | Some("rc") => "text/plain; charset=utf8",
555 | Some("rc2") => "text/plain; charset=utf8",
556 | Some("rct") => "text/plain; charset=utf8",
557 | Some("rdlc") => "application/xml",
558 | Some("resx") => "application/xml",
559 | Some("rf") => "image/vnd.rn-realflash",
560 | Some("rgb") => "image/x-rgb",
561 | Some("rgs") => "text/plain; charset=utf8",
562 | Some("rm") => "application/vnd.rn-realmedia",
563 | Some("rmi") => "audio/mid",
564 | Some("rmp") => "application/vnd.rn-rn_music_package",
565 | Some("roff") => "application/x-troff",
566 | Some("rpm") => "audio/x-pn-realaudio-plugin",
567 | Some("rqy") => "text/x-ms-rqy; charset=utf8",
568 | Some("rtf") => "application/rtf",
569 | Some("rtx") => "text/richtext; charset=utf8",
570 | Some("ruleset") => "application/xml",
571 | Some("s") => "text/plain; charset=utf8",
572 | Some("safariextz") => "application/x-safari-safariextz",
573 | Some("scd") => "application/x-msschedule",
574 | Some("sct") => "text/scriptlet; charset=utf8",
575 | Some("sd2") => "audio/x-sd2",
576 | Some("sdp") => "application/sdp",
577 | Some("sea") => "application/octet-stream",
578 | Some("searchConnector-ms") => "application/windows-search-connector+xml",
579 | Some("setpay") => "application/set-payment-initiation",
580 | Some("setreg") => "application/set-registration-initiation",
581 | Some("settings") => "application/xml",
582 | Some("sfnt") => "application/font-sfnt",
583 | Some("sgimb") => "application/x-sgimb",
584 | Some("sgml") => "text/sgml; charset=utf8",
585 | Some("sh") => "application/x-sh",
586 | Some("shar") => "application/x-shar",
587 | Some("shtml") => "text/html; charset=utf8",
588 | Some("sit") => "application/x-stuffit",
589 | Some("sitemap") => "application/xml",
590 | Some("skin") => "application/xml",
591 | Some("sldm") => "application/vnd.ms-powerpoint.slide.macroEnabled.12",
592 | Some("sldx") => "application/vnd.openxmlformats-officedocument.presentationml.slide",
593 | Some("slk") => "application/vnd.ms-excel",
594 | Some("sln") => "text/plain; charset=utf8",
595 | Some("slupkg-ms") => "application/x-ms-license",
596 | Some("smd") => "audio/x-smd",
597 | Some("smi") => "application/octet-stream",
598 | Some("smx") => "audio/x-smd",
599 | Some("smz") => "audio/x-smd",
600 | Some("snd") => "audio/basic",
601 | Some("snippet") => "application/xml",
602 | Some("snp") => "application/octet-stream",
603 | Some("sol") => "text/plain; charset=utf8",
604 | Some("sor") => "text/plain; charset=utf8",
605 | Some("spc") => "application/x-pkcs7-certificates",
606 | Some("spl") => "application/futuresplash",
607 | Some("src") => "application/x-wais-source",
608 | Some("srf") => "text/plain; charset=utf8",
609 | Some("ssisdeploymentmanifest") => "application/xml",
610 | Some("ssm") => "application/streamingmedia",
611 | Some("sst") => "application/vnd.ms-pki.certstore",
612 | Some("stl") => "application/vnd.ms-pki.stl",
613 | Some("sv4cpio") => "application/x-sv4cpio",
614 | Some("sv4crc") => "application/x-sv4crc",
615 | Some("svc") => "application/xml",
616 | Some("svg") => "image/svg+xml",
617 | Some("swf") => "application/x-shockwave-flash",
618 | Some("t") => "application/x-troff",
619 | Some("tar") => "application/x-tar",
620 | Some("tcl") => "application/x-tcl",
621 | Some("testrunconfig") => "application/xml",
622 | Some("testsettings") => "application/xml",
623 | Some("tex") => "application/x-tex",
624 | Some("texi") => "application/x-texinfo",
625 | Some("texinfo") => "application/x-texinfo",
626 | Some("tgz") => "application/x-compressed",
627 | Some("thmx") => "application/vnd.ms-officetheme",
628 | Some("thn") => "application/octet-stream",
629 | Some("tif") => "image/tiff",
630 | Some("tiff") => "image/tiff",
631 | Some("tlh") => "text/plain; charset=utf8",
632 | Some("tli") => "text/plain; charset=utf8",
633 | Some("toc") => "application/octet-stream",
634 | Some("tr") => "application/x-troff",
635 | Some("trm") => "application/x-msterminal",
636 | Some("trx") => "application/xml",
637 | Some("ts") => "video/vnd.dlna.mpeg-tts",
638 | Some("tsv") => "text/tab-separated-values; charset=utf8",
639 | Some("ttf") => "application/x-font-ttf",
640 | Some("tts") => "video/vnd.dlna.mpeg-tts",
641 | Some("txt") => "text/plain; charset=utf8",
642 | Some("u32") => "application/octet-stream",
643 | Some("uls") => "text/iuls; charset=utf8",
644 | Some("user") => "text/plain; charset=utf8",
645 | Some("ustar") => "application/x-ustar",
646 | Some("vb") => "text/plain; charset=utf8",
647 | Some("vbdproj") => "text/plain; charset=utf8",
648 | Some("vbk") => "video/mpeg",
649 | Some("vbproj") => "text/plain; charset=utf8",
650 | Some("vbs") => "text/vbscript; charset=utf8",
651 | Some("vcf") => "text/x-vcard; charset=utf8",
652 | Some("vcproj") => "Application/xml",
653 | Some("vcs") => "text/plain; charset=utf8",
654 | Some("vcxproj") => "Application/xml",
655 | Some("vddproj") => "text/plain; charset=utf8",
656 | Some("vdp") => "text/plain; charset=utf8",
657 | Some("vdproj") => "text/plain; charset=utf8",
658 | Some("vdx") => "application/vnd.ms-visio.viewer",
659 | Some("vml") => "application/xml",
660 | Some("vscontent") => "application/xml",
661 | Some("vsct") => "application/xml",
662 | Some("vsd") => "application/vnd.visio",
663 | Some("vsi") => "application/ms-vsi",
664 | Some("vsix") => "application/vsix",
665 | Some("vsixlangpack") => "application/xml",
666 | Some("vsixmanifest") => "application/xml",
667 | Some("vsmdi") => "application/xml",
668 | Some("vspscc") => "text/plain; charset=utf8",
669 | Some("vss") => "application/vnd.visio",
670 | Some("vsscc") => "text/plain; charset=utf8",
671 | Some("vssettings") => "application/xml",
672 | Some("vssscc") => "text/plain; charset=utf8",
673 | Some("vst") => "application/vnd.visio",
674 | Some("vstemplate") => "application/xml",
675 | Some("vsto") => "application/x-ms-vsto",
676 | Some("vsw") => "application/vnd.visio",
677 | Some("vsx") => "application/vnd.visio",
678 | Some("vtx") => "application/vnd.visio",
679 | Some("wasm") => "application/wasm",
680 | Some("wav") => "audio/wav",
681 | Some("wave") => "audio/wav",
682 | Some("wax") => "audio/x-ms-wax",
683 | Some("wbk") => "application/msword",
684 | Some("wbmp") => "image/vnd.wap.wbmp",
685 | Some("wcm") => "application/vnd.ms-works",
686 | Some("wdb") => "application/vnd.ms-works",
687 | Some("wdp") => "image/vnd.ms-photo",
688 | Some("webarchive") => "application/x-safari-webarchive",
689 | Some("webtest") => "application/xml",
690 | Some("wiq") => "application/xml",
691 | Some("wiz") => "application/msword",
692 | Some("wks") => "application/vnd.ms-works",
693 | Some("wlmp") => "application/wlmoviemaker",
694 | Some("wlpginstall") => "application/x-wlpg-detect",
695 | Some("wlpginstall3") => "application/x-wlpg3-detect",
696 | Some("wm") => "video/x-ms-wm",
697 | Some("wma") => "audio/x-ms-wma",
698 | Some("wmd") => "application/x-ms-wmd",
699 | Some("wmf") => "application/x-msmetafile",
700 | Some("wml") => "text/vnd.wap.wml; charset=utf8",
701 | Some("wmlc") => "application/vnd.wap.wmlc",
702 | Some("wmls") => "text/vnd.wap.wmlscript; charset=utf8",
703 | Some("wmlsc") => "application/vnd.wap.wmlscriptc",
704 | Some("wmp") => "video/x-ms-wmp",
705 | Some("wmv") => "video/x-ms-wmv",
706 | Some("wmx") => "video/x-ms-wmx",
707 | Some("wmz") => "application/x-ms-wmz",
708 | Some("woff") => "application/font-woff",
709 | Some("woff2") => "application/font-woff2",
710 | Some("wpl") => "application/vnd.ms-wpl",
711 | Some("wps") => "application/vnd.ms-works",
712 | Some("wri") => "application/x-mswrite",
713 | Some("wrl") => "x-world/x-vrml",
714 | Some("wrz") => "x-world/x-vrml",
715 | Some("wsc") => "text/scriptlet; charset=utf8",
716 | Some("wsdl") => "application/xml",
717 | Some("wvx") => "video/x-ms-wvx",
718 | Some("x") => "application/directx",
719 | Some("xaf") => "x-world/x-vrml",
720 | Some("xaml") => "application/xaml+xml",
721 | Some("xap") => "application/x-silverlight-app",
722 | Some("xbap") => "application/x-ms-xbap",
723 | Some("xbm") => "image/x-xbitmap",
724 | Some("xdr") => "text/plain; charset=utf8",
725 | Some("xht") => "application/xhtml+xml",
726 | Some("xhtml") => "application/xhtml+xml",
727 | Some("xla") => "application/vnd.ms-excel",
728 | Some("xlam") => "application/vnd.ms-excel.addin.macroEnabled.12",
729 | Some("xlc") => "application/vnd.ms-excel",
730 | Some("xld") => "application/vnd.ms-excel",
731 | Some("xlk") => "application/vnd.ms-excel",
732 | Some("xll") => "application/vnd.ms-excel",
733 | Some("xlm") => "application/vnd.ms-excel",
734 | Some("xls") => "application/vnd.ms-excel",
735 | Some("xlsb") => "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
736 | Some("xlsm") => "application/vnd.ms-excel.sheet.macroEnabled.12",
737 | Some("xlsx") => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
738 | Some("xlt") => "application/vnd.ms-excel",
739 | Some("xltm") => "application/vnd.ms-excel.template.macroEnabled.12",
740 | Some("xltx") => "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
741 | Some("xlw") => "application/vnd.ms-excel",
742 | Some("xml") => "application/xml",
743 | Some("xmta") => "application/xml",
744 | Some("xof") => "x-world/x-vrml",
745 | Some("xoml") => "text/plain; charset=utf8",
746 | Some("xpm") => "image/x-xpixmap",
747 | Some("xps") => "application/vnd.ms-xpsdocument",
748 | Some("xrm-ms") => "application/xml",
749 | Some("xsc") => "application/xml",
750 | Some("xsd") => "application/xml",
751 | Some("xsf") => "application/xml",
752 | Some("xsl") => "application/xml",
753 | Some("xslt") => "application/xslt+xml",
754 | Some("xsn") => "application/octet-stream",
755 | Some("xss") => "application/xml",
756 | Some("xtp") => "application/octet-stream",
757 | Some("xwd") => "image/x-xwindowdump",
758 | Some("z") => "application/x-compress",
759 | Some("zip") => "application/zip",
760 | _ => "application/octet-stream",
761 | }
762 | }
763 |
--------------------------------------------------------------------------------