├── .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 [![Crates.io](https://img.shields.io/crates/v/devserver.svg)](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 | --------------------------------------------------------------------------------