From d4c3b70d6290c52f903e674e9957b979abd4ce07 Mon Sep 17 00:00:00 2001
From: lamp
Date: Sun, 5 Mar 2023 21:37:45 +0000
Subject: init

---
 .gitignore                |   1 +
 Cargo.lock                | 722 ++++++++++++++++++++++++++++++++++++++++++++++
 Cargo.toml                |  14 +
 LICENSE                   |  13 +
 README.md                 |  12 +
 res/400.html              |  14 +
 res/404.html              |  14 +
 res/500.html              |  14 +
 res/501.html              |  14 +
 res/listing.html          |  35 +++
 res/listing_entry.html    |  11 +
 src/chunked_bufreader.rs  |  48 +++
 src/http/flatten.rs       |  37 +++
 src/http/mod.rs           |   6 +
 src/http/parser.rs        | 523 +++++++++++++++++++++++++++++++++
 src/http/rule.rs          | 123 ++++++++
 src/main.rs               | 159 ++++++++++
 src/peekable_bufreader.rs |  47 +++
 src/response.rs           |  55 ++++
 19 files changed, 1862 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 Cargo.lock
 create mode 100644 Cargo.toml
 create mode 100644 LICENSE
 create mode 100644 README.md
 create mode 100644 res/400.html
 create mode 100644 res/404.html
 create mode 100644 res/500.html
 create mode 100644 res/501.html
 create mode 100644 res/listing.html
 create mode 100644 res/listing_entry.html
 create mode 100644 src/chunked_bufreader.rs
 create mode 100644 src/http/flatten.rs
 create mode 100644 src/http/mod.rs
 create mode 100644 src/http/parser.rs
 create mode 100644 src/http/rule.rs
 create mode 100644 src/main.rs
 create mode 100644 src/peekable_bufreader.rs
 create mode 100644 src/response.rs

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..f2c988d
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,722 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "async-attributes"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "async-channel"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319"
+dependencies = [
+ "concurrent-queue",
+ "event-listener",
+ "futures-core",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965"
+dependencies = [
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "once_cell",
+ "slab",
+]
+
+[[package]]
+name = "async-global-executor"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6"
+dependencies = [
+ "async-channel",
+ "async-executor",
+ "async-io",
+ "async-mutex",
+ "blocking",
+ "futures-lite",
+ "num_cpus",
+ "once_cell",
+]
+
+[[package]]
+name = "async-io"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b"
+dependencies = [
+ "concurrent-queue",
+ "futures-lite",
+ "libc",
+ "log",
+ "once_cell",
+ "parking",
+ "polling",
+ "slab",
+ "socket2",
+ "waker-fn",
+ "winapi",
+]
+
+[[package]]
+name = "async-lock"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b"
+dependencies = [
+ "event-listener",
+]
+
+[[package]]
+name = "async-mutex"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e"
+dependencies = [
+ "event-listener",
+]
+
+[[package]]
+name = "async-std"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9f06685bad74e0570f5213741bea82158279a4103d988e57bfada11ad230341"
+dependencies = [
+ "async-attributes",
+ "async-channel",
+ "async-global-executor",
+ "async-io",
+ "async-lock",
+ "crossbeam-utils",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-lite",
+ "gloo-timers",
+ "kv-log-macro",
+ "log",
+ "memchr",
+ "num_cpus",
+ "once_cell",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+ "wasm-bindgen-futures",
+]
+
+[[package]]
+name = "async-task"
+version = "4.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
+
+[[package]]
+name = "atomic-waker"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "blocking"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9"
+dependencies = [
+ "async-channel",
+ "async-task",
+ "atomic-waker",
+ "fastrand",
+ "futures-lite",
+ "once_cell",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
+
+[[package]]
+name = "cache-padded"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba"
+
+[[package]]
+name = "cc"
+version = "1.0.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
+dependencies = [
+ "cache-padded",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
+dependencies = [
+ "cfg-if",
+ "lazy_static",
+]
+
+[[package]]
+name = "ctor"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
+
+[[package]]
+name = "fastrand"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77b705829d1e87f762c2df6da140b26af5839e1033aa84aa5f56bb688e4e1bdb"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1"
+
+[[package]]
+name = "futures-lite"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite",
+ "waker-fn",
+]
+
+[[package]]
+name = "futures-macro"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121"
+dependencies = [
+ "autocfg",
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282"
+
+[[package]]
+name = "futures-task"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae"
+
+[[package]]
+name = "futures-util"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967"
+dependencies = [
+ "autocfg",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "proc-macro-hack",
+ "proc-macro-nested",
+ "slab",
+]
+
+[[package]]
+name = "gloo-timers"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "kv-log-macro"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.97"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+ "value-bag",
+]
+
+[[package]]
+name = "memchr"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
+
+[[package]]
+name = "parking"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
+
+[[package]]
+name = "pin-project"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "polling"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92341d779fa34ea8437ef4d82d440d5e1ce3f3ff7f824aa64424cd481f9a1f25"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "log",
+ "wepoll-ffi",
+ "winapi",
+]
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
+[[package]]
+name = "proc-macro-nested"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
+
+[[package]]
+name = "socket2"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "time"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+dependencies = [
+ "libc",
+ "wasi",
+ "winapi",
+]
+
+[[package]]
+name = "tiny-serve"
+version = "0.1.0"
+dependencies = [
+ "async-std",
+ "chrono",
+ "futures",
+ "lazy_static",
+ "pin-project",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "value-bag"
+version = "1.0.0-alpha.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd320e1520f94261153e96f7534476ad869c14022aee1e59af7c778075d840ae"
+dependencies = [
+ "ctor",
+ "version_check",
+]
+
+[[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "waker-fn"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fba7978c679d53ce2d0ac80c8c175840feb849a161664365d1287b41f2e67f1"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f"
+
+[[package]]
+name = "web-sys"
+version = "0.3.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "wepoll-ffi"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..ae2c800
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "tiny-serve"
+version = "0.1.0"
+authors = ["matthew"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+futures = "0.3"
+async-std = { version = "1.6", features = ["attributes"] }
+lazy_static = "1.4"
+pin-project = "1.0"
+chrono = "0.4"
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..456c488
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+        DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 
+                    Version 2, December 2004 
+
+ Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> 
+
+ Everyone is permitted to copy and distribute verbatim or modified 
+ copies of this license document, and changing it is allowed as long 
+ as the name is changed. 
+
+            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 
+
+  0. You just DO WHAT THE FUCK YOU WANT TO.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..54071bf
--- /dev/null
+++ b/README.md
@@ -0,0 +1,12 @@
+# tiny-serve
+A small, fast, fully asynchronous, and concurrent HTTP/1.1 server for static content stored on the filesystem, written in Rust using `async_std` and `futures`. 
+
+It uses a custom hand-written parser based on the relevant RFCs that sweeps over the message with a single byte of lookahead. The parser supports a reasonable subset of HTTP/1.1, but lacks some unnecessary grammar and such that are only used for optional, unimplemented HTTP/1.1 verbs.
+
+It accepts one command line argument, the port number to bind to:
+
+```
+tiny-serve 8080
+```
+
+which defaults to port 8000 if not specified or invalid.
\ No newline at end of file
diff --git a/res/400.html b/res/400.html
new file mode 100644
index 0000000..db6465c
--- /dev/null
+++ b/res/400.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8"/>
+        <meta name="viewport" content="width=device-width, initial-scale=1"/>
+        <title>400 Bad Request</title>
+        <link href="data:," rel="icon"/>
+    </head>
+    <body>
+        <h1>
+            400 Bad Request
+        </h1>
+    </body>
+</html>
diff --git a/res/404.html b/res/404.html
new file mode 100644
index 0000000..ce779a4
--- /dev/null
+++ b/res/404.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8"/>
+        <meta name="viewport" content="width=device-width, initial-scale=1"/>
+        <title>404 Not Found</title>
+        <link href="data:," rel="icon"/>
+    </head>
+    <body>
+        <h1>
+            404 Not Found
+        </h1>
+    </body>
+</html>
diff --git a/res/500.html b/res/500.html
new file mode 100644
index 0000000..84abbae
--- /dev/null
+++ b/res/500.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8"/>
+        <meta name="viewport" content="width=device-width, initial-scale=1"/>
+        <title>500 Internal Server Error</title>
+        <link href="data:," rel="icon"/>
+    </head>
+    <body>
+        <h1>
+            500 Internal Server Error
+        </h1>
+    </body>
+</html>
diff --git a/res/501.html b/res/501.html
new file mode 100644
index 0000000..64b1a64
--- /dev/null
+++ b/res/501.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8"/>
+        <meta name="viewport" content="width=device-width, initial-scale=1"/>
+        <title>501 Not Implemented</title>
+        <link href="data:," rel="icon"/>
+    </head>
+    <body>
+        <h1>
+            501 Not Implemented
+        </h1>
+    </body>
+</html>
diff --git a/res/listing.html b/res/listing.html
new file mode 100644
index 0000000..51e88a2
--- /dev/null
+++ b/res/listing.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8"/>
+        <meta name="viewport" content="width=device-width, initial-scale=1"/>
+        <title>/{}</title>
+        <link href="data:," rel="icon"/>
+        <style>
+            body {{
+                font-family: sans-serif;
+                padding: 5px;
+            }}
+
+            table {{
+                border-collapse: collapse;
+            }}
+
+            tr, td {{
+                padding: 5px;
+            }}
+            
+            tr + tr {{
+                border-top: 1px solid black;
+            }}
+        </style>
+    </head>
+    <body>
+        <h1>
+            /{}
+        </h1>
+        <table>
+            {}
+        </table>
+    </body>
+</html>
diff --git a/res/listing_entry.html b/res/listing_entry.html
new file mode 100644
index 0000000..40183fd
--- /dev/null
+++ b/res/listing_entry.html
@@ -0,0 +1,11 @@
+<tr>
+    <td>
+        <a href="/{}">{}</a>
+    </td>
+    <td>
+        {}
+    </td>
+    <td>
+        {}
+    </td>
+</tr>
diff --git a/src/chunked_bufreader.rs b/src/chunked_bufreader.rs
new file mode 100644
index 0000000..02c2bb8
--- /dev/null
+++ b/src/chunked_bufreader.rs
@@ -0,0 +1,48 @@
+use std::pin::Pin;
+
+use async_std::prelude::*;
+use async_std::io::BufReader;
+use async_std::io::Read;
+use futures::task::{Context, Poll};
+use pin_project::pin_project;
+
+const CHUNK_SIZE: usize = 4096;
+
+#[pin_project]
+pub struct ChunkedBufReader<T>
+    where T: Read + Unpin {
+    #[pin]
+    reader: BufReader<T>,
+}
+
+impl<T> ChunkedBufReader<T>
+    where T: Read + Unpin {
+    pub fn new(reader: BufReader<T>) -> Self {
+        Self {
+            reader,
+        }
+    }
+}
+
+impl<T> Stream for ChunkedBufReader<T>
+    where T: Read + Unpin {
+    type Item = Vec<u8>;
+    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
+        let this = self.project();
+        // This is quite wasteful, but perfomance is fine and the only real optimization (other
+        // than some zero-copy shenanigans) would be to not initialize this vec, with unsafe{}
+        let mut chunk = vec![0; CHUNK_SIZE];
+        match this.reader.poll_read(cx, &mut chunk[0..(CHUNK_SIZE - 1)]) {
+            Poll::Ready(Ok(size)) => {
+                if size == 0 {
+                    Poll::Ready(None)
+                } else {
+                    chunk.truncate(size);
+                    Poll::Ready(Some(chunk))
+                }
+            },
+            Poll::Ready(Err(_)) => Poll::Ready(None),
+            Poll::Pending => Poll::Pending,
+        }
+    }
+}
diff --git a/src/http/flatten.rs b/src/http/flatten.rs
new file mode 100644
index 0000000..51ead0c
--- /dev/null
+++ b/src/http/flatten.rs
@@ -0,0 +1,37 @@
+use std::collections::HashMap;
+
+use super::rule::{HTTPMessage, Method};
+
+#[derive(Debug)]
+pub enum Version {
+    Http0_9,
+    Http1_0,
+    Http1_1,
+    // HTTP/2 won't parse, anyway.
+}
+
+#[derive(Debug)]
+pub struct HTTPRequest {
+    pub method: Method,
+    pub version: Version,
+    pub requested_path: Vec<String>,
+    pub headers: HashMap<String, Vec<u8>>,
+}
+
+pub fn flatten(message: HTTPMessage) -> Option<HTTPRequest> {
+    let method = message.request_line.method;
+    let version = match (message.request_line.http_version.major, message.request_line.http_version.minor) {
+        (0, 9) => Version::Http0_9,
+        (1, 0) => Version::Http1_0,
+        (1, 1) => Version::Http1_1,
+        _ => return None,
+    };
+    let requested_path = message.request_line.request_target.absolute_path.segments.into_iter().map(|segment| segment.lexeme).collect();
+    let headers = message.header_fields.into_iter().map(|field| (field.name.lexeme, field.value.content)).collect();
+    Some(HTTPRequest{
+        method,
+        version,
+        requested_path,
+        headers,
+    })
+}
diff --git a/src/http/mod.rs b/src/http/mod.rs
new file mode 100644
index 0000000..d2fd5ac
--- /dev/null
+++ b/src/http/mod.rs
@@ -0,0 +1,6 @@
+mod parser;
+mod rule;
+mod flatten;
+
+pub use parser::Parser;
+pub use rule::Method;
diff --git a/src/http/parser.rs b/src/http/parser.rs
new file mode 100644
index 0000000..259ddf2
--- /dev/null
+++ b/src/http/parser.rs
@@ -0,0 +1,523 @@
+use async_std::io::Read;
+
+use super::rule::{HTTPMessage, HeaderField, FieldName, FieldValue, FieldContent, RequestLine, Method, OriginForm, HTTPVersion, AbsolutePath, Query, Segment};
+use super::flatten::{flatten, HTTPRequest};
+use crate::peekable_bufreader::PeekableBufReader;
+
+const HTAB: u8 = 0x09;
+const SPACE: u8 = 0x20;
+const PERCENT: u8 = 0x25;
+const SLASH: u8 = 0x2F;
+const DOT: u8 = 0x2E;
+const COLON: u8 = 0x3A;
+const QUESTION_MARK: u8 = 0x3F;
+const ATSIGN: u8 = 0x40;
+
+enum ErrorType {
+    Missing,
+    Malformed,
+}
+
+pub struct Parser<T>
+    where T: Read + Unpin {
+    source: PeekableBufReader<T>,
+}
+
+impl<T> Parser<T>
+    where T: Read + Unpin {
+    pub fn new(source: PeekableBufReader<T>) -> Self {
+        Self {
+            source,
+        }
+    }
+
+    pub async fn parse(self) -> Option<HTTPRequest> {
+        flatten(self.http_message().await?)
+    }
+
+    /*
+    * RFC 7230, Page 19
+    */
+    async fn http_message(mut self) -> Option<HTTPMessage> {
+        let start_line = self.start_line().await?;
+        let mut header_fields = Vec::new();
+        loop {
+            if let Some(_) = self.consume_carriage_return().await {
+                break;
+            }
+            header_fields.push(self.header_field().await?);
+            self.consume_carriage_return().await?;
+        }
+        // GET Requests don't have a message body, and we only really deal with GET requests.
+        // There's no need to examine the headers and attempt to read a message body.
+        Some(HTTPMessage {
+            request_line: start_line,
+            header_fields,
+        })
+    }
+
+    /*
+    * RFC 7230, Page 23
+    */
+    async fn header_field(&mut self) -> Option<HeaderField> {
+        let name = self.field_name().await?;
+        self.consume_char(&COLON).await?;
+        self.consume_optional_whitespace().await;
+        let value = self.field_value().await?;
+        self.consume_optional_whitespace().await;
+        Some(HeaderField {
+            name,
+            value,
+        })
+    }
+
+    /*
+    * RFC 7230, Page 23
+    */
+    async fn field_name(&mut self) -> Option<FieldName> {
+        Some(FieldName {
+            lexeme: self.logical_token().await?,
+        })
+    }
+
+    /*
+    * RFC 7230, Page 23
+    * obs-fold is deprecated except within message/http media. This isn't going to come up for us,
+    * so we deviate from the grammar slightly.
+    */
+    async fn field_value(&mut self) -> Option<FieldValue> {
+        let mut content = Vec::new();
+        loop {
+            // Look, no obs-fold!
+            if let Some(value) = self.field_content().await {
+                // Let's do some pre-emptive flattening here.
+                content.push(value.first_char);
+                if let Some(second_char) = value.second_char {
+                    content.push(SPACE);
+                    content.push(second_char);
+                }
+            } else {
+                break;
+            }
+        }
+        Some(FieldValue {
+            content,
+        })
+    }
+
+    /*
+    * RFC 7230, Page 23
+    */
+    async fn field_content(&mut self) -> Option<FieldContent> {
+        let first_char = self.field_vchar().await?;
+        let second_char = if let Some(_) = self.consume_required_whitespace().await {
+            Some(self.field_vchar().await?)
+        } else {
+            None
+        };
+        Some(FieldContent{
+            first_char,
+            second_char,
+        })
+    }
+
+    async fn field_vchar(&mut self) -> Option<u8> {
+        let next_char = self.source.peek().await?;
+        if Self::is_visible_char(next_char) || Self::is_obs_text_char(next_char) {
+            return self.source.next().await;
+        }
+        None
+    }
+
+    /*
+    * RFC 7230, Page 21
+    * This is a server, so the start-line is exclusively a request-line.
+    */
+    async fn start_line(&mut self) -> Option<RequestLine> {
+        self.request_line().await
+    }
+    
+    /*
+    * RFC 7230, Page 21
+    */
+    async fn request_line(&mut self) -> Option<RequestLine> {
+        let method = self.method().await?;
+        self.consume_char(&SPACE).await?;
+        let request_target = self.request_target().await?;
+        self.consume_char(&SPACE).await?;
+        let http_version = self.http_version().await?;
+        self.consume_carriage_return().await?;
+        Some(RequestLine {
+            method,
+            request_target,
+            http_version,
+        })
+    }
+
+    /*
+    * RFC 7230, Page 41
+    * We only serve some static content; therefore we only need support origin-form.
+    */
+    async fn request_target(&mut self) -> Option<OriginForm> {
+        self.origin_form().await
+    }
+
+    /*
+    * RFC 7230, Page 42
+    */
+    async fn origin_form(&mut self) -> Option<OriginForm> {
+        let absolute_path = self.absolute_path().await?;
+        let query = if let Some(_) = self.consume_char(&QUESTION_MARK).await {
+            Some(self.query().await?)
+        } else {
+            None
+        };
+        Some(OriginForm {
+            absolute_path,
+            query,
+        })
+    }
+
+    /*
+    * RFC 7230, Page 16
+    */
+    async fn absolute_path(&mut self) -> Option<AbsolutePath> {
+        let mut segments = Vec::new();
+        self.consume_char(&SLASH).await?;
+        segments.push(self.segment().await?);
+        loop {
+            if let None = self.consume_char(&SLASH).await {
+                break;
+            }
+            if let Some(segment) = self.segment().await {
+                segments.push(segment);
+            } else {
+                return None;
+            }
+        }
+        Some(AbsolutePath {
+            segments,
+        })
+    }
+
+    /*
+    * RFC 3986, Page 23
+    */
+    async fn segment(&mut self) -> Option<Segment> {
+        let mut segment = Vec::new();
+        while self.source.peek().await.is_some() {
+            match self.consume_path_character().await {
+                Ok(character) => segment.push(character as char),
+                Err(ErrorType::Missing) => break,
+                Err(ErrorType::Malformed) => return None,
+            }
+        }
+        Some(Segment{
+            lexeme: segment.into_iter().collect(),
+        })
+    }
+
+    /*
+    * RFC 3986, Page 50
+    */
+    async fn query(&mut self) -> Option<Query> {
+        let mut query = Vec::new();
+        while self.source.peek().await.is_some() {
+            match self.consume_query_character().await {
+                Ok(character) => query.push(character as char),
+                Err(ErrorType::Missing) => break,
+                Err(ErrorType::Malformed) => return None,
+            }
+        }
+        Some(Query{
+            lexeme: query.into_iter().collect(),
+        })
+    }
+
+    /*
+    * RFC 7230, Page 14
+    */
+    async fn http_version(&mut self) -> Option<HTTPVersion> {
+        self.consume_logical_token("HTTP").await?;
+        self.consume_char(&SLASH).await?;
+        let major = Self::ascii_digit_to_value(&self.consume_digit().await?);
+        self.consume_char(&DOT).await?;
+        let minor = Self::ascii_digit_to_value(&self.consume_digit().await?);
+        Some(HTTPVersion{
+            major,
+            minor,
+        })
+    }
+    
+    /*
+    * RFC 7230, Page 21
+    */
+    async fn method(&mut self) -> Option<Method> {
+        Method::from_string(&self.logical_token().await?)
+    }
+
+    /*
+    * RFC 7230, Page 27
+    */
+    async fn logical_token(&mut self) -> Option<String> {
+        let mut logical_token = Vec::new();
+        if !Self::is_logical_token_char(self.source.peek().await?) {
+            return None;
+        }
+        while self.source.peek().await.is_some() && Self::is_logical_token_char(self.source.peek().await.unwrap()) {
+            logical_token.push(self.source.next().await.unwrap() as char);
+        }
+        Some(logical_token.into_iter().collect())
+    }
+
+    async fn consume_char(&mut self, character: &u8) -> Option<u8> {
+        let next_char = self.source.peek().await?;
+        if *next_char == *character {
+            return self.source.next().await;
+        }
+        None
+    }
+    
+    async fn consume_logical_token(&mut self, value: &str) -> Option<String> {
+        let logical_token = self.logical_token().await?;
+        if logical_token == value {
+            return Some(logical_token);
+        }
+        None
+    }
+
+    /*
+    * RFC 3986, Page 23
+    */
+    async fn consume_path_character(&mut self) -> Result<u8, ErrorType> {
+        match self.consume_unreserved_character().await {
+            Some(character) => return Ok(character),
+            _ => {}
+        }
+        match self.consume_sub_delim_character().await {
+            Some(character) => return Ok(character),
+            _ => {}
+        }
+        match self.consume_char(&COLON).await {
+            Some(character) => return Ok(character),
+            _ => {}
+        }
+        match self.consume_char(&ATSIGN).await {
+            Some(character) => return Ok(character),
+            _ => {}
+        }
+        self.consume_percent_encoded().await
+    }
+
+    /*
+    * RFC 3986, Page 50
+    */
+    async fn consume_query_character(&mut self) -> Result<u8, ErrorType> {
+        match self.consume_unreserved_character().await {
+            Some(character) => return Ok(character),
+            _ => {}
+        }
+        match self.consume_sub_delim_character().await {
+            Some(character) => return Ok(character),
+            _ => {}
+        }
+        match self.consume_char(&COLON).await {
+            Some(character) => return Ok(character),
+            _ => {}
+        }
+        match self.consume_char(&ATSIGN).await {
+            Some(character) => return Ok(character),
+            _ => {}
+        }
+        match self.consume_char(&SLASH).await {
+            Some(character) => return Ok(character),
+            _ => {}
+        }
+        match self.consume_char(&QUESTION_MARK).await {
+            Some(character) => return Ok(character),
+            _ => {}
+        }
+        self.consume_percent_encoded().await
+    }
+
+    /*
+    * RFC 5234, Page 5
+    */
+    async fn consume_carriage_return(&mut self) -> Option<()> {
+        self.consume_char(&0x0D).await?;
+        self.consume_char(&0x0A).await?;
+        Some(())
+    }
+
+    async fn consume_optional_whitespace(&mut self) {
+        loop {
+            if let None = self.consume_char(&SPACE).await {
+                if let None = self.consume_char(&HTAB).await {
+                    break;
+                }
+            }
+        }
+    }
+
+    async fn consume_required_whitespace(&mut self) -> Option<()> {
+        if let None = self.consume_char(&SPACE).await {
+            if let None = self.consume_char(&HTAB).await {
+                return None;
+            }
+        }
+        loop {
+            if let None = self.consume_char(&SPACE).await {
+                if let None = self.consume_char(&HTAB).await {
+                    break;
+                }
+            }
+        }
+        Some(())
+    }
+
+    /*
+    * RFC 5234, Page 14
+    */
+    async fn consume_digit(&mut self) -> Option<u8> {
+        let next_char = self.source.peek().await?;
+        if Self::is_digit_char(next_char) {
+            return self.source.next().await
+        }
+        None
+    }
+
+    async fn consume_unreserved_character(&mut self) -> Option<u8> {
+        let next_char = self.source.peek().await?;
+        if Self::is_unreserved_char(next_char) {
+            return self.source.next().await
+        }
+        None
+    }
+
+    async fn consume_sub_delim_character(&mut self) -> Option<u8> {
+        let next_char = self.source.peek().await?;
+        if Self::is_sub_delim_char(next_char) {
+            return self.source.next().await
+        }
+        None
+    }
+
+    async fn consume_percent_encoded(&mut self) -> Result<u8, ErrorType> {
+        self.consume_char(&PERCENT).await.ok_or(ErrorType::Missing)?;
+        let high_word = self.consume_hex_digit().await.ok_or(ErrorType::Malformed)?;
+        let low_word = self.consume_hex_digit().await.ok_or(ErrorType::Malformed)?;
+        Self::hex_digits_to_byte(high_word, low_word).ok_or(ErrorType::Malformed)
+    }
+
+    async fn consume_hex_digit(&mut self) -> Option<u8> {
+        let next_char = self.source.peek().await?;
+        if Self::is_hex_digit_char(next_char) {
+            return self.source.next().await
+        }
+        None
+    }
+
+    fn ascii_digit_to_value(character: &u8) -> u32 {
+        *character as u32 - 0x30
+    }
+
+    fn hex_digits_to_byte(high_word: u8, low_word: u8) -> Option<u8> {
+        u8::from_str_radix(&((high_word as char).to_string() + &(low_word as char).to_string()), 16).ok()
+    }
+
+    /*
+    * RFC 7230, Page 27
+    */
+    fn is_logical_token_char(character: &u8) -> bool {
+        *character == 0x21 || // !
+        *character == 0x23 || // #
+        *character == 0x24 || // $
+        *character == 0x25 || // %
+        *character == 0x26 || // &
+        *character == 0x27 || // '
+        *character == 0x2A || // *
+        *character == 0x2B || // +
+        *character == 0x2D || // -
+        *character == 0x2E || // .
+        *character == 0x5E || // ^
+        *character == 0x5F || // _
+        *character == 0x60 || // `
+        *character == 0x7C || // |
+        *character == 0x7E || // ~
+        Self::is_digit_char(character) ||
+        Self::is_alpha_char(character)
+    }
+
+    /*
+    * RFC 3986, Page 13
+    */
+    fn is_unreserved_char(character: &u8) -> bool {
+        Self::is_alpha_char(character) ||
+        Self::is_digit_char(character) ||
+        *character == 0x2D || // -
+        *character == 0x2E || // .
+        *character == 0x5F || // _
+        *character == 0x7E    // ~
+    }
+
+    /*
+    * RFC 3986, Page 13
+    */
+    fn is_sub_delim_char(character: &u8) -> bool {
+        *character == 0x21 || // !
+        *character == 0x24 || // $
+        *character == 0x26 || // &
+        *character == 0x27 || // '
+        *character == 0x28 || // (
+        *character == 0x29 || // )
+        *character == 0x2A || // *
+        *character == 0x2B || // +
+        *character == 0x2C || // ,
+        *character == 0x3B || // ;
+        *character == 0x3D    // =
+    }
+
+    /*
+    * RFC 5234, Page 13
+    */
+    fn is_alpha_char(character: &u8) -> bool {
+        (*character >= 0x41 && *character <= 0x5A) || (*character >= 0x61 && *character <= 0x7A)
+    }
+
+    /*
+    * RFC 5234, Page 14
+    */
+    fn is_digit_char(character: &u8) -> bool {
+        *character >= 0x30 && *character <= 0x39
+    }
+
+    /*
+    * RFC 5234, Page 14
+    */
+    fn is_hex_digit_char(character: &u8) -> bool {
+        Self::is_digit_char(character) ||
+        *character == 0x41 || // A
+        *character == 0x42 || // B
+        *character == 0x43 || // C
+        *character == 0x44 || // D
+        *character == 0x45 || // E
+        *character == 0x46 || // F
+        *character == 0x61 || // a
+        *character == 0x62 || // b
+        *character == 0x63 || // c
+        *character == 0x64 || // d
+        *character == 0x65 || // e
+        *character == 0x66    // f
+    }
+
+    /*
+    * RFC 5234, Page 14
+    */
+    fn is_visible_char(character: &u8) -> bool {
+        *character >= 0x21 && *character <= 0x7E
+    }
+
+    fn is_obs_text_char(character: &u8) -> bool {
+        *character >= 0x80
+    }
+}
diff --git a/src/http/rule.rs b/src/http/rule.rs
new file mode 100644
index 0000000..1672d99
--- /dev/null
+++ b/src/http/rule.rs
@@ -0,0 +1,123 @@
+use std::collections::HashMap;
+
+lazy_static! {
+    static ref METHODS: HashMap<&'static str, Method> = {
+        let mut m = HashMap::new();
+        m.insert("GET", Method::GET);
+        m.insert("HEAD", Method::HEAD);
+        m.insert("POST", Method::POST);
+        m.insert("PUT", Method::PUT);
+        m.insert("DELETE", Method::DELETE);
+        m.insert("CONNECT", Method::CONNECT);
+        m.insert("OPTIONS", Method::OPTIONS);
+        m.insert("TRACE", Method::TRACE);
+        m
+    };
+}
+
+/*
+* RFC 7230, Page 19
+*/
+#[derive(Debug)]
+pub struct HTTPMessage {
+    pub request_line: RequestLine,
+    pub header_fields: Vec<HeaderField>,
+}
+
+/*
+* RFC 7230, Page 23
+*/
+#[derive(Debug)]
+pub struct HeaderField {
+    pub name: FieldName,
+    pub value: FieldValue,
+}
+
+/*
+* RFC 7230, Page 23
+*/
+#[derive(Debug)]
+pub struct FieldName {
+    pub lexeme: String,
+}
+
+/*
+* RFC 7230, Page 23
+*/
+#[derive(Debug)]
+pub struct FieldValue {
+    pub content: Vec<u8>,
+}
+
+/*
+* RFC 7230, Page 23
+*/
+#[derive(Debug)]
+pub struct FieldContent {
+    pub first_char: u8,
+    pub second_char: Option<u8>,
+}
+
+/*
+* RFC 7230, Page 21
+*/
+#[derive(Debug)]
+pub struct RequestLine {
+    pub method: Method,
+    pub request_target: OriginForm,
+    pub http_version: HTTPVersion,
+}
+
+/*
+* RFC 7231, Page 22
+*/
+#[derive(Debug, Clone)]
+pub enum Method {
+    GET,
+    HEAD,
+    POST,
+    PUT,
+    DELETE,
+    CONNECT,
+    OPTIONS,
+    TRACE,
+}
+
+impl Method {
+    pub fn from_string(string: &str) -> Option<Method> {
+        METHODS.get(string).cloned()
+    }
+}
+
+/*
+* RFC 7230, Page 41
+*/
+#[derive(Debug)]
+pub struct OriginForm {
+    pub absolute_path: AbsolutePath,
+    pub query: Option<Query>,
+}
+
+/*
+* RFC 7230, Page 14
+*/
+#[derive(Debug)]
+pub struct HTTPVersion {
+    pub major: u32,
+    pub minor: u32,
+}
+
+#[derive(Debug)]
+pub struct AbsolutePath {
+    pub segments: Vec<Segment>,
+}
+
+#[derive(Debug)]
+pub struct Query {
+    pub lexeme: String,
+}
+
+#[derive(Debug)]
+pub struct Segment {
+    pub lexeme: String,
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..36699af
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,159 @@
+#[macro_use]
+extern crate lazy_static;
+
+mod http;
+mod peekable_bufreader;
+mod response;
+mod chunked_bufreader;
+
+use std::env;
+use std::path::PathBuf;
+use std::path::Path;
+
+use futures::stream;
+use futures::stream::StreamExt;
+
+use async_std::prelude::*;
+use async_std::task;
+use async_std::net::{TcpStream, TcpListener};
+use async_std::io::{BufReader, BufWriter};
+use async_std::fs::File;
+use async_std::fs;
+
+use chrono::offset::Local;
+use chrono::DateTime;
+
+use http::{Parser, Method};
+use peekable_bufreader::PeekableBufReader;
+use chunked_bufreader::ChunkedBufReader;
+use response::{Response, BadRequest, NotFound, NotImplemented, InternalServerError};
+
+#[async_std::main]
+async fn main() {
+    let port = env::args().skip(1).next().unwrap_or("8000".to_owned()).parse::<u16>().unwrap_or(8000);
+    let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await;
+    match listener {
+        Err(e) => {
+            eprintln!("Failed to bind TCP Listener: {}", e);
+            return;
+        },
+        _ => {},
+    }
+    let listener = listener.unwrap();
+    listener
+        .incoming()
+        .for_each_concurrent(None, |stream| async move {
+            if let Ok(valid) = stream {
+                task::spawn(handle_connection(valid));
+            }
+        })
+        .await;
+}
+
+async fn handle_connection(mut stream: TcpStream) {
+    let response = generate_response(&stream).await.response_bytes();
+    let writer = BufWriter::new(&mut stream);
+    let mut writer = response.fold(writer, |mut writer, bytes| async move {
+        writer.write(&bytes).await.unwrap();
+        writer
+    }).await;
+    writer.flush().await.unwrap();
+    stream.flush().await.unwrap();
+}
+
+async fn generate_response(stream: &TcpStream) -> Box<dyn Response> {
+    let reader = PeekableBufReader::new(BufReader::new(stream));
+    let request = match Parser::new(reader).parse().await {
+        Some(success) => success,
+        None => return Box::new(BadRequest{}),
+    };
+    match request.method {
+        Method::GET => {
+            if request.requested_path.iter().filter(|segment| segment.contains("/")).count() != 0 {
+                return Box::new(BadRequest{});
+            } else {
+                let path = match PathBuf::from("./".to_owned() + &request.requested_path.join("/")).canonicalize() {
+                    Ok(canonical_path) => canonical_path,
+                    Err(_) => return Box::new(NotFound{}),
+                };
+                let current_dir = match env::current_dir() {
+                    Ok(dir) => match dir.canonicalize() {
+                        Ok(canonical_path) => canonical_path,
+                        Err(_) => return Box::new(InternalServerError{}),
+                    },
+                    Err(_) => return Box::new(InternalServerError{}),
+                };
+                if !is_path_ancestor_of(&current_dir, &path) {
+                    return Box::new(NotFound{});
+                }
+                let metadata = match fs::metadata(&path).await {
+                    Ok(data) => data,
+                    Err(_) => return Box::new(NotFound{}),
+                };
+                if metadata.is_dir() {
+                    let friendly_name = match path.strip_prefix(&current_dir) {
+                        Ok(result) => {
+                            match result.to_str() {
+                                Some(result) => result,
+                                None => return Box::new(InternalServerError{}),
+                            }
+                        },
+                        Err(_) => return Box::new(InternalServerError{}),
+                    };
+                    let mut listings = Vec::new();
+                    if path != current_dir {
+                        listings.push(format!(include_str!("../res/listing_entry.html"), path.parent().unwrap().strip_prefix(&current_dir).unwrap().to_str().unwrap(), "..", "-", "-"));
+                    }
+                    for file in path.read_dir().unwrap() {
+                        let file_path = file.unwrap().path();
+                        let file_metadata = fs::metadata(&file_path).await.unwrap();
+                        let created_time = match file_metadata.created() {
+                            Ok(birth_time) => {
+                                let formatted_time: DateTime<Local> = birth_time.into();
+                                formatted_time.format("%d-%b-%Y %H:%M").to_string()
+                            },
+                            Err(_) => "-".to_owned(),
+                        };
+                        let file_size = if file_metadata.is_dir() {
+                            "-".to_owned()
+                        } else {
+                            file_metadata.len().to_string()
+                        };
+                        listings.push(format!(include_str!("../res/listing_entry.html"),
+                            file_path.strip_prefix(&current_dir).unwrap().to_str().unwrap(),
+                            file_path.file_name().unwrap().to_str().unwrap(),
+                            created_time,
+                            file_size,
+                        ))
+                    }
+                    return Box::new(response::Ok {
+                        file_stream: Box::new(stream::iter(vec![Vec::from(
+                                format!(
+                                    include_str!("../res/listing.html"),
+                                    friendly_name,
+                                    friendly_name,
+                                    listings.join("\n"),
+                                ).as_bytes()
+                            )].into_iter().map(|entry| entry.to_owned())))
+                    });
+                } else {
+                    return Box::new(response::Ok{ file_stream: Box::new(ChunkedBufReader::new(BufReader::new(File::open(&path).await.unwrap()))) })
+                }
+            }
+        },
+        _ => return Box::new(NotImplemented{}),
+    }
+}
+
+fn is_path_ancestor_of(ancestor: &Path, child: &Path) -> bool {
+    let mut ancestors = child.ancestors();
+    loop {
+        if let Some(parent) = ancestors.next() {
+            if parent == ancestor {
+                return true;
+            }
+        } else {
+            return false;
+        }
+    }
+}
diff --git a/src/peekable_bufreader.rs b/src/peekable_bufreader.rs
new file mode 100644
index 0000000..ae02155
--- /dev/null
+++ b/src/peekable_bufreader.rs
@@ -0,0 +1,47 @@
+use async_std::prelude::*;
+use async_std::io::BufReader;
+use async_std::io::Read;
+
+pub struct PeekableBufReader<T>
+    where T: Read + Unpin {
+    reader: BufReader<T>,
+    buffer: [u8; 1],
+    peeked_last: bool,
+}
+
+impl<T> PeekableBufReader<T>
+    where T: Read + Unpin {
+    pub fn new(reader: BufReader<T>) -> Self {
+        Self {
+            reader,
+            buffer: [0],
+            peeked_last: false,
+        }
+    }
+
+    pub async fn next(&mut self) -> Option<u8> {
+        if self.peeked_last {
+            self.peeked_last = false;
+            Some(self.buffer[0])
+        } else {
+            match self.reader.read(&mut self.buffer).await.ok()? {
+                1 => Some(self.buffer[0]),
+                _ => None,
+            }
+        }
+    }
+
+    pub async fn peek(&mut self) -> Option<&u8> {
+        if self.peeked_last {
+            Some(&self.buffer[0])
+        } else {
+            match self.reader.read(&mut self.buffer).await.ok()? {
+                1 => {
+                    self.peeked_last = true;
+                    Some(&self.buffer[0])
+                },
+                _ => None,
+            }
+        }
+    }
+}
diff --git a/src/response.rs b/src/response.rs
new file mode 100644
index 0000000..8817148
--- /dev/null
+++ b/src/response.rs
@@ -0,0 +1,55 @@
+use futures::prelude::*;
+
+pub trait Response: Send + Sync {
+    fn response_bytes(self: Box<Self>) -> Box<dyn Stream<Item = Vec<u8>> + Unpin + Send>;
+}
+
+pub struct Ok {
+    pub file_stream: Box<dyn Stream<Item = Vec<u8>> + Unpin + Send + Sync>,
+}
+
+impl Response for Ok {
+    fn response_bytes(self: Box<Self>) -> Box<dyn Stream<Item = Vec<u8>> + Unpin + Send> {
+        Box::new(stream::iter(vec![Vec::from(b"HTTP/1.1 200 OK\r\n\r\n" as &[u8])].into_iter().map(|entry| entry.to_owned())).chain(self.file_stream))
+    }
+}
+
+pub struct BadRequest {
+
+}
+
+impl Response for BadRequest {
+    fn response_bytes(self: Box<Self>) -> Box<dyn Stream<Item = Vec<u8>> + Unpin + Send> {
+        Box::new(stream::iter(vec![Vec::from(b"HTTP/1.1 400 Bad Request\r\n\r\n" as &[u8]), include_bytes!("../res/400.html").to_vec()].into_iter().map(|entry| entry.to_owned())))
+    }
+}
+
+pub struct NotFound {
+
+}
+
+impl Response for NotFound {
+    fn response_bytes(self: Box<Self>) -> Box<dyn Stream<Item = Vec<u8>> + Unpin + Send> {
+        Box::new(stream::iter(vec![Vec::from(b"HTTP/1.1 404 Not Found\r\n\r\n" as &[u8]), include_bytes!("../res/404.html").to_vec()].into_iter().map(|entry| entry.to_owned())))
+    }
+}
+
+pub struct NotImplemented {
+
+}
+
+impl Response for NotImplemented {
+    fn response_bytes(self: Box<Self>) -> Box<dyn Stream<Item = Vec<u8>> + Unpin + Send> {
+        Box::new(stream::iter(vec![Vec::from(b"HTTP/1.1 501 Not Implemented\r\n\r\n" as &[u8]), include_bytes!("../res/501.html").to_vec()].into_iter().map(|entry| entry.to_owned())))
+    }
+}
+
+pub struct InternalServerError {
+
+}
+
+impl Response for InternalServerError {
+    fn response_bytes(self: Box<Self>) -> Box<dyn Stream<Item = Vec<u8>> + Unpin + Send> {
+        Box::new(stream::iter(vec![Vec::from(b"HTTP/1.1 500 InternalServerError\r\n\r\n" as &[u8]), include_bytes!("../res/500.html").to_vec()].into_iter().map(|entry| entry.to_owned())))
+    }
+}
-- 
cgit v1.2.3