From cbb92993b592e6b68dbce7f283fb73d19fd1793e Mon Sep 17 00:00:00 2001 From: Tyler Murphy Date: Sun, 2 Jul 2023 18:45:41 -0400 Subject: [PATCH] hi --- .gitignore | 1 + Cargo.lock | 554 +++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 8 + LICENSE | 13 + README | 25 ++ config.example | 1 + src/bash.rs | 41 ++++ src/http/code.rs | 8 + src/http/header.rs | 96 ++++++++ src/http/method.rs | 30 +++ src/http/mod.rs | 6 + src/http/request.rs | 52 ++++ src/http/response.rs | 42 ++++ src/http/uri.rs | 106 +++++++++ src/main.rs | 92 +++++++ 15 files changed, 1075 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README create mode 100644 config.example create mode 100644 src/bash.rs create mode 100644 src/http/code.rs create mode 100644 src/http/header.rs create mode 100644 src/http/method.rs create mode 100644 src/http/mod.rs create mode 100644 src/http/request.rs create mode 100644 src/http/response.rs create mode 100644 src/http/uri.rs create mode 100644 src/main.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..ba0aab4 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,554 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[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.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "time", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" + +[[package]] +name = "proc-macro2" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "2.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "tokio" +version = "1.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +dependencies = [ + "autocfg", + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web" +version = "0.1.0" +dependencies = [ + "chrono", + "tokio", +] + +[[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" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0e5451e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "bashhttp" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio = { version = "1.29", features = ["full"] } +chrono = "0.4.26" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f85e174 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2023 Tyler Murphy + + 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 b/README new file mode 100644 index 0000000..d68c6cc --- /dev/null +++ b/README @@ -0,0 +1,25 @@ +# bashttp + +A very goofy http server that runs scripts based of the given URI route + +## Config + +All routs and scripts must be layed out in a file called config, or you can specift the path by setting the `CONFIG_PATH` env variable. + +An example config is shown below +``` +/ ./hello_world +/neo /usr/bin/neofetch +/joe ./bide +``` +As shown above, each route to script is a single line seperated by a single space. + +## License + +This project is licensed under the [WTFPL](https://www.wtfpl.net/) + +## Warrenty + +This project is probably not secure and if it beaks, uh... have fun + +...wtfpl diff --git a/config.example b/config.example new file mode 100644 index 0000000..8f9aba6 --- /dev/null +++ b/config.example @@ -0,0 +1 @@ +/ /usr/bin/neofetch diff --git a/src/bash.rs b/src/bash.rs new file mode 100644 index 0000000..059909f --- /dev/null +++ b/src/bash.rs @@ -0,0 +1,41 @@ +use std::{env, collections::HashMap, fs::read_to_string, process::{exit, Command}}; + +pub fn load_config() -> HashMap { + + let config_path = env::var("CONFIG_PATH").unwrap_or_else(|_| String::from("config")); + let config = match read_to_string(&config_path) { + Ok(data) => data, + Err(err) => { + eprintln!("cannot load '{config_path}': {err}"); + exit(1); + }, + }; + + let mut map = HashMap::new(); + let lines = config.split("\n").into_iter(); + + for line in lines { + let mut parts = line.trim().split(" "); + let Some(route) = parts.next() else { continue }; + let Some(script) = parts.next() else { continue }; + + println!("adding entry {route} => {script}"); + map.insert(route.to_owned(), script.to_owned()); + } + + map +} + +pub fn handle_script(script: &str, body: Option<&String>) -> Result { + let mut command = Command::new(script); + if let Some(body) = body { + command.args([body]); + } + + let output = match command.output() { + Ok(o) => o, + Err(err) => return Err(format!("{err}")), + }; + + Ok(String::from_utf8_lossy(&output.stdout).into_owned()) +} diff --git a/src/http/code.rs b/src/http/code.rs new file mode 100644 index 0000000..ba47282 --- /dev/null +++ b/src/http/code.rs @@ -0,0 +1,8 @@ +#[derive(Debug, Clone)] +#[allow(dead_code)] +pub enum Code { + Success = 200, + MethodNotAllowed = 405, + TooManyRequests = 429, + InternalServerError = 500, +} diff --git a/src/http/header.rs b/src/http/header.rs new file mode 100644 index 0000000..e6fc552 --- /dev/null +++ b/src/http/header.rs @@ -0,0 +1,96 @@ +use std::{collections::HashMap, str::Split}; + +#[derive(Debug, Clone)] +pub struct HeaderMap { + map: HashMap, + headers: Vec
+} + +impl HeaderMap { + + #[allow(dead_code)] + pub fn get(&self, key: &str) -> Option<&Header> { + let Some(index) = self.map.get(key) else { + return None; + }; + return Some(&self.headers[index.to_owned()]); + } + + pub fn put(&mut self, header: Header) { + if let Some(index) = self.map.get(&header.key) { + self.headers[index.to_owned()] = header; + return + } + + let index = self.headers.len(); + self.map.insert(header.key.clone(), index); + self.headers.push(header); + } + + #[allow(dead_code)] + pub fn del(&mut self, key: &str) -> Option
{ + let Some(index) = self.map.get(key) else { + return None + }; + + let removed = self.headers.remove(index.to_owned()); + for i in (index.to_owned())..self.headers.len() { + let key = &self.headers[i].key; + + let Some(index) = self.map.get(key) else { + continue; + }; + + self.map.insert(key.clone(), index - 1); + } + + Some(removed) + } + + pub fn deserialize(&self) -> String { + let mut string = String::new(); + + for header in &self.headers { + string += &format!("{}: {}\n", header.key, header.value); + } + + string + } + + pub fn serialize(lines: &mut Split) -> Self { + + let mut headers = Self::new(); + + loop { + let Some(header) = lines.next() else { break }; + if header.trim().len() < 1 { break } + + let mut parts = header.split(": ").into_iter(); + let Some(key) = parts.next() else { continue }; + let Some(value) = parts.next() else { continue }; + + headers.put(Header::new(key.trim(), value.trim())); + } + + headers + } + + pub fn new() -> Self { + Self { + map: HashMap::new(), + headers: Vec::new() + } + } +} + +#[derive(Debug, Clone)] +pub struct Header { + pub key: String, + pub value: String +} + +impl Header { + pub fn new(key: &str, value: &str) -> Self { + return Self {key: key.to_owned(), value: value.to_owned()} + } +} diff --git a/src/http/method.rs b/src/http/method.rs new file mode 100644 index 0000000..55cea65 --- /dev/null +++ b/src/http/method.rs @@ -0,0 +1,30 @@ +#[derive(Debug, Clone)] +pub enum Method { + Get, + Head, + Post, + Put, + Delete, + Connect, + Options, + Trace, + Patch +} + +impl Method { + pub fn serialize(string: &str) -> Option { + match string { + "GET" => Some(Self::Get), + "HEAD" => Some(Self::Head), + "POST" => Some(Self::Post), + "PUT" => Some(Self::Put), + "DELETE" => Some(Self::Delete), + "CONNECT" => Some(Self::Connect), + "OPTIONS" => Some(Self::Options), + "TRACE" => Some(Self::Trace), + "PATCH" => Some(Self::Patch), + _ => None + } + } +} + diff --git a/src/http/mod.rs b/src/http/mod.rs new file mode 100644 index 0000000..62151bb --- /dev/null +++ b/src/http/mod.rs @@ -0,0 +1,6 @@ +pub mod code; +pub mod method; +pub mod uri; +pub mod request; +pub mod response; +pub mod header; diff --git a/src/http/request.rs b/src/http/request.rs new file mode 100644 index 0000000..5ba72c9 --- /dev/null +++ b/src/http/request.rs @@ -0,0 +1,52 @@ +use super::{method::Method, uri::URI, header::HeaderMap}; + +#[derive(Debug, Clone)] +pub struct Request { + pub method: Method, + pub uri: URI, + pub headers: HeaderMap, + pub body: Option +} + +impl Request { + pub fn serialize(req: &str) -> Option { + let mut lines = req.split('\n').to_owned(); + + let Some(head) = lines.next() else { + eprintln!("missing head str"); + return None + }; + + let mut parts = head.trim().split(" "); + + let Some(method_str) = parts.next() else { + eprintln!("missing method str"); + return None + }; + + let Some(method) = Method::serialize(method_str.trim()) else { + eprintln!("invalid http method"); + return None + }; + + let Some(uri_str) = parts.next() else { + eprintln!("missing uri str"); + return None + }; + + let Some(uri) = URI::serialize(uri_str.trim()) else { + eprintln!("invalid http uri"); + return None + }; + + let headers = HeaderMap::serialize(&mut lines); + let body: String = lines.collect(); + + Some(Self { + method, + uri, + headers, + body: if body.len() > 0 { Some(body) } else { None }, + }) + } +} diff --git a/src/http/response.rs b/src/http/response.rs new file mode 100644 index 0000000..850f41e --- /dev/null +++ b/src/http/response.rs @@ -0,0 +1,42 @@ +use super::{code::Code, header::{HeaderMap, Header}}; + +#[derive(Debug, Clone)] +pub struct Response { + pub status: Code, + pub headers: HeaderMap, + pub body: Option +} + +impl Response { + + pub fn new() -> Self { + + let mut headers = HeaderMap::new(); + headers.put(Header::new("Connection", "close")); + + let date = chrono::offset::Utc::now(); + headers.put(Header::new("Date", &date.to_rfc2822())); + + headers.put(Header::new("Server", "bashttp")); + + return Self { + status: Code::Success, + headers, + body: None + } + } + + pub fn deserialize(&self) -> String { + let mut string = String::new(); + + string += &format!("HTTP/1.1 {}\n", self.status.clone() as u16); + string += &self.headers.deserialize(); + + if let Some(body) = &self.body { + string += "\n"; + string += body; + } + + string + } +} diff --git a/src/http/uri.rs b/src/http/uri.rs new file mode 100644 index 0000000..8faff69 --- /dev/null +++ b/src/http/uri.rs @@ -0,0 +1,106 @@ +#[derive(Debug, Clone)] +pub enum Protocol { + HTTP, + HTTPS, +} + +impl Protocol { + pub fn serialize(string: &str) -> Option { + match string { + "http" => return Some(Self::HTTP), + "https" => return Some(Self::HTTPS), + _ => return None + } + } + + pub fn deserialize(&self) -> &str { + match self { + Self::HTTP => "http", + Self::HTTPS => "https", + } + } +} + +#[derive(Debug, Clone)] +pub struct URI { + pub protocol: Option, + pub host: Option, + pub port: Option, + pub route: String +} + +impl URI { + + #[allow(dead_code)] + pub fn deserialize(&self) -> String { + let mut string = String::new(); + + if let Some(protocol) = &self.protocol { + string += protocol.deserialize(); + string += "://"; + } + if let Some(host) = &self.host { + string += host; + } + if let Some(port) = self.port { + string += &format!(":{port}"); + } + string += &self.route; + + string + } + + pub fn serialize(head: &str) -> Option { + + let protocol_end = match head.find("://") { + Some(i) => i, + None => 0 + }; + + let protocol: Option; + let host_start: usize; + if protocol_end == 0 { + protocol = None; + host_start = 0; + } else { + let Some(p) = Protocol::serialize(&head[..protocol_end]) else { + return None + }; + protocol = Some(p); + host_start = protocol_end + 3; + } + + let host_route = &head[host_start..]; + let host_end = host_route.find("/").unwrap_or(head.len()); + + let host: Option; + let port: Option; + if host_start == host_end { + host = None; + port = None; + } else { + if let Some (host_split) = host_route.find(":") { + let port_start = host_split + 1; + let port_str = &head[port_start..host_end]; + let Ok(p) = port_str.parse::() else { + return None + }; + host = Some(head[host_start..host_split].to_owned()); + port = Some(p); + } else { + host = Some(head[host_start..host_end].to_owned()); + port = None; + } + } + + let route = &head[host_end..]; + + Some(Self { + protocol, + host, + port, + route: route.to_owned() + }) + } + +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9cf8e90 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,92 @@ +use std::collections::HashMap; +use std::sync::Arc; +use http::code::Code; +use http::header::Header; +use http::request::Request; +use http::response::Response; +use tokio::net::{TcpListener, TcpStream}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +use crate::bash::handle_script; + +mod http; +mod bash; + +async fn handle_response(mut socket: TcpStream, code: Code, body: String) { + let mut res = Response::new(); + res.headers.put(Header::new("Content-Type", "text/plain")); + res.status = code; + res.body = Some(body); + + let res_str = res.deserialize(); + + let _ = socket.write(res_str.as_bytes()).await; +} + +async fn handle_connection(mut socket: TcpStream, config: Arc>) { + + let mut buf = [0; 1204]; + + let n: usize = match socket.read(&mut buf).await { + Ok(n) if n == 0 => return, + Ok(n) => n as usize, + Err(e) => { + eprintln!("failed to read from socket; err = {:?}", e); + return + } + }; + + let str = String::from_utf8_lossy(&buf[0..n]); + + let Some(req) = Request::serialize(&str) else { + return + }; + + + let Some(script) = config.get(&req.uri.route) else { + handle_response(socket, Code::MethodNotAllowed, "Method Not Allowed".to_owned()).await; + return + }; + + match handle_script(script, req.body.as_ref()) { + Ok(out) => { + handle_response(socket, Code::Success, out).await; + }, + Err(err) => { + handle_response(socket, Code::MethodNotAllowed, err).await; + }, + } +} + +#[tokio::main] +async fn main() { + + let config = Arc::new(bash::load_config()); + + let port = std::env::var("PORT") + .unwrap_or_else(|_| String::from("8080")) + .parse::() + .unwrap_or_else(|_| 8080); + + let addr = format!("127.0.0.1:{port}"); + + let Ok(listener) = TcpListener::bind(&addr).await else { + println!("failed to bind {addr}"); + return + }; + + println!("listening to tcp requests on {addr}"); + + loop { + let Ok((socket, _)) = listener.accept().await else { + println!("failed to accept new connection"); + continue + }; + + let config = config.clone(); + + tokio::spawn(async move { + handle_connection(socket, config).await; + }); + } +}