Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • codecraft/webserver
  • sorcaMriete/webserver
  • opnonmicgo/webserver
  • loposuezo/webserver
  • diufeYmike/webserver
  • contbuspecmi/webserver
  • mogamuboun/webserver
  • glabalwelre/webserver
8 results
Show changes
Commits on Source (38)
Showing
with 7779 additions and 11 deletions
1. If you collect an iterator which you don't index in, you don't need to collect it
2. avoid temporary hashmaps
3. rewrite POST request stuff TICK
4. Client struct
5. Mime-Display new implemented
6. Remove unwraps
7. Reusable allocations
API design 3. No decisions for the caller
......@@ -2,6 +2,458 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytes"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "http"
version = "0.1.0"
dependencies = [
"phf",
"tokio",
]
[[package]]
name = "libc"
version = "0.2.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
[[package]]
name = "lock_api"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "mio"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.45.0",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"libc",
]
[[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.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys 0.45.0",
]
[[package]]
name = "phf"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c"
dependencies = [
"phf_macros",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_macros"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92aacdc5f16768709a569e913f7451034034178b05bdc8acda226659a3dccc66"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "phf_shared"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "proc-macro2"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[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 = "siphasher"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
[[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 = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2"
dependencies = [
"autocfg",
"bytes",
"libc",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
]
[[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 2.0.15",
]
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[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-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.0",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"windows_aarch64_gnullvm 0.48.0",
"windows_aarch64_msvc 0.48.0",
"windows_i686_gnu 0.48.0",
"windows_i686_msvc 0.48.0",
"windows_x86_64_gnu 0.48.0",
"windows_x86_64_gnullvm 0.48.0",
"windows_x86_64_msvc 0.48.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[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.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[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.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[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.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[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.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[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.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[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.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
......@@ -6,3 +6,5 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version = "1.28.2", features = ["full"] }
phf = { version = "0.11", features = ["macros"] }
use std::path::PathBuf;
use tokio::{io::{AsyncReadExt, BufReader, AsyncBufReadExt, AsyncWriteExt}, net::TcpStream};
use crate::handling::{
file_handlers::NamedFile,
request::Request,
response::{Outcome, Response, ResponseBody, Status},
routes::Data, methods::Method,
};
use crate::setup::MountPoint;
static MAX_HTTP_MESSAGE_SIZE: u16 = 4196;
pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint<'_>>) {
let mut buf_reader = BufReader::new(&mut stream);
let mut http_request: Vec<String> = Vec::with_capacity(10);
loop {
let mut buffer = String::new();
if let Err(_) = buf_reader.read_line(&mut buffer).await {
eprintln!("\x1b[31mAborting due to invalid UTF-8 in request header\x1b[0m");
return;
}
if buffer == "\r\n" {
break;
}
http_request.push(buffer);
}
println!("{:?}", http_request);
let request_status_line = if let Some(status_line) = http_request.get(0).cloned() {
status_line
} else {
eprintln!("\x1b[31mAborting due to missing headers\x1b[0m");
return;
};
let request = Request {
uri: if let Some(uri) = &request_status_line.split(" ").nth(1) {
*uri
} else {
eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m");
return;
},
cookies: None,
headers: http_request,
method: if let Some(method) = request_status_line
.split(" ")
.next() {
if let Ok(ok) = method.parse() {
ok
} else {
eprintln!("\x1b[31mAborting due to invalid request method\x1b[0m");
return;
}
} else {
eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m");
return;
},
};
let mut data = Data {
is_complete: false,
buffer: vec![],
};
if request.can_have_body() {
let length = if let Some(len) = request
.headers
.iter()
.filter(|header| header.starts_with("Content-Length: "))
.map(|header| {
let header = header.strip_prefix("Content-Length: ").unwrap();
header.trim().parse::<usize>()
})
.next()
{
if let Ok(size) = len {
size
} else {
eprintln!("\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", request.method);
len_not_defined(stream, Status::LengthRequired).await;
return;
}
} else {
if request.mandatory_body() {
eprintln!("\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", request.method);
len_not_defined(stream, Status::LengthRequired).await;
return;
}
0
};
if length != 0 {
let mut buffer = vec![0u8; MAX_HTTP_MESSAGE_SIZE.into()];
let read = buf_reader.read(&mut buffer).await.unwrap();
if read != length {
len_not_defined(stream, Status::LengthRequired).await;
return;
}
data.is_complete = true;
data.buffer = buffer;
}
}
let mut handled_response: Option<Outcome<Response, Status, Data>> = None;
for mountpoint in mountpoints {
if !request.uri.starts_with(mountpoint.mountpoint) {
continue;
}
let mounted_request_uri = request.uri.strip_prefix(mountpoint.mountpoint).unwrap();
for route in mountpoint.routes {
if (route.method != request.method) &&
((route.method != Method::Get) && (request.method == Method::Head)) {
continue;
}
if !route.compare_uri(mounted_request_uri) {
continue;
}
handled_response = Some((route.handler)(
Request {
uri: mounted_request_uri,
..request.clone()
},
data.clone(),
));
if let Some(Outcome::Forward(_)) = handled_response {
continue;
}
break;
}
}
let response = match handled_response {
Some(val) => match val {
Outcome::Success(success) => (
format!("HTTP/1.1 {}\r\n", success.status.unwrap_or(Status::Ok))
+ &success.headers.join("\r\n")
+ "\r\n\r\n",
if request.method == Method::Head {
vec![]
} else {
success.body.get_data()
}
),
Outcome::Failure(error) => failure_handler(error),
Outcome::Forward(_) => failure_handler(Status::NotFound),
},
None => failure_handler(Status::NotFound),
};
let mut resp = response.0.as_bytes().to_vec();
resp.extend_from_slice(&response.1);
if let Err(e) = stream.write_all(&resp).await {
eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m");
return;
}
return;
}
fn failure_handler(status: Status) -> (String, Vec<u8>) {
let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap();
(
format!(
"HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n",
status,
page_404.get_len(),
page_404.get_mime()
),
page_404.get_data(),
)
}
async fn len_not_defined(mut stream: TcpStream, status: Status) {
let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap();
let mut response = format!("HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", status, page_411.get_len(), page_411.get_mime()).as_bytes().to_vec();
response.extend_from_slice(&page_411.get_data());
if let Err(e) = stream.write_all(&response).await {
eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m");
}
}
pub mod handlers;
use std::{fs, path::PathBuf};
use crate::{
handling::response::{ResponseBody, Status},
utils::mime::mime_enum::Mime,
};
#[derive(Debug)]
pub struct NamedFile {
pub content_len: usize,
pub content_type: Mime,
pub content: Vec<u8>,
}
impl ResponseBody for NamedFile {
fn get_data(&self) -> Vec<u8> {
self.content.clone()
}
fn get_mime(&self) -> Mime {
self.content_type.clone()
}
fn get_len(&self) -> usize {
self.content_len
}
}
impl NamedFile {
pub fn open(path: PathBuf) -> Result<NamedFile, Status> {
let path = proove_path(path);
let data = fs::read(&path);
let data = match data {
Ok(dat) => dat,
Err(_) => {
return Err(Status::NotFound);
}
};
Ok(NamedFile {
content_len: data.len(),
content_type: Mime::from_filename(path.to_str().unwrap()),
content: data,
})
}
}
fn proove_path(path: PathBuf) -> PathBuf {
PathBuf::from(
path.to_str()
.unwrap()
.split("/")
.filter(|&val| val != ".." && val != "")
.collect::<Vec<&str>>()
.join("/"),
)
}
use std::{fmt::Display, str::FromStr};
#[derive(PartialEq, Eq, Clone, Debug, Copy, PartialOrd, Ord)]
pub enum Method {
Get,
Head,
Post,
Put,
Delete,
Connect,
Options,
Trace,
Patch,
}
impl Display for Method {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Get => write!(f, "GET"),
Self::Head => write!(f, "HEAD"),
Self::Post => write!(f, "POST"),
Self::Put => write!(f, "PUT"),
Self::Delete => write!(f, "DELETE"),
Self::Connect => write!(f, "CONNECT"),
Self::Options => write!(f, "OPTIONS"),
Self::Trace => write!(f, "TRACE"),
Self::Patch => write!(f, "PATCH"),
}
}
}
impl FromStr for Method {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"GET" => Ok(Self::Get),
"HEAD" => Ok(Self::Head),
"POST" => Ok(Self::Post),
"PUT" => Ok(Self::Put),
"DELETE" => Ok(Self::Delete),
"CONNECT" => Ok(Self::Connect),
"OPTIONS" => Ok(Self::Options),
"TRACE" => Ok(Self::Trace),
"PATCH" => Ok(Self::Patch),
_ => Err("Not a Method"),
}
}
}
pub mod file_handlers;
pub mod methods;
pub mod request;
pub mod response;
pub mod routes;
// use std::net::SocketAddr;
use std::{collections::HashMap, error::Error, fmt::Display};
use crate::utils::mime::mime_enum::Mime;
static TWO_NEWLINES: u8 = 3;
use super::{
methods::Method,
routes::{Data, Uri},
};
pub trait FromRequest: Send {
fn get_data(&self) -> &Self;
fn set_data(&mut self, data: &Self);
fn append(&mut self, data: &Self);
}
impl FromRequest for Vec<u8> {
fn get_data(&self) -> &Self {
&self
}
fn set_data(&mut self, data: &Self) {
*self = data.to_vec();
}
fn append(&mut self, data: &Self) {
self.extend_from_slice(data);
}
}
type HeaderMap = Vec<String>;
#[derive(Clone)]
pub struct Request<'a> {
pub uri: Uri<'a>,
pub headers: HeaderMap,
pub method: Method,
pub cookies: Option<Vec<&'a str>>,
// pub connection: ConnectionMeta,
}
// struct ConnectionMeta {
// remote: Option<SocketAddr>,
// // certificates
// }
#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum MediaType {
Json,
Plain,
Html,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ParseErrors {
NoData,
BadData,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct ParseFormError {
pub error: ParseErrors,
}
impl Display for ParseFormError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.error)
}
}
impl Display for ParseErrors {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ParseErrors::NoData => write!(f, "No Data at key"),
ParseErrors::BadData => write!(f, "Bad Data at key"),
}
}
}
impl Error for ParseFormError {}
impl Request<'_> {
pub fn can_have_body(&self) -> bool {
match self.method {
Method::Post | Method::Put | Method::Patch | Method::Delete => true,
_ => false,
}
}
pub fn mandatory_body(&self) -> bool {
match self.method {
Method::Post | Method::Put | Method::Patch => true,
_ => false,
}
}
pub fn get_get_form_keys<'a>(
&'a self,
keys: &'a [&str],
) -> Result<HashMap<&str, Result<&str, ParseFormError>>, ParseFormError> {
let data = if let Some(val) = self.uri.split_once("?") {
val
} else {
return Err(ParseFormError {
error: ParseErrors::NoData,
});
};
let data = data
.1
.split("&")
.map(|kvp| kvp.split_once("="))
.collect::<Vec<Option<(&str, &str)>>>();
let mut values: HashMap<&str, &str> = HashMap::new();
for kvp in data {
let kvp = if let Some(kvp) = kvp {
kvp
} else {
continue;
};
values.insert(kvp.0, kvp.1);
}
let mut response = HashMap::new();
for key in keys {
let entry = if let Some(val) = values.get(key) {
Ok(*val)
} else {
Err(ParseFormError {
error: ParseErrors::NoData,
})
};
response.insert((*key).into(), entry);
}
Ok(response)
}
pub fn get_post_data<'a>(
&'a self,
keys: &[&'a str],
data: &Data,
) -> Result<HashMap<String, Result<Vec<u8>, ParseFormError>>, ParseFormError> {
let boundary;
let post_type = if let Some(val) = self
.headers
.iter()
.find(|header| header.contains("Content-Type: "))
{
let content_type = val.trim().strip_prefix("Content-Type: ").unwrap();
let type_vec = content_type.split(';').collect::<Vec<&str>>();
boundary = if let Some(bound) = type_vec.iter().find(|part| part.contains(" boundary="))
{
bound
.strip_prefix(" boundary=")
.unwrap()
.trim_end()
.trim_matches('"')
} else {
""
};
if let Ok(mime) = type_vec[0].trim().parse() {
mime
} else {
return Err(ParseFormError {
error: ParseErrors::BadData,
});
}
} else {
return Err(ParseFormError {
error: ParseErrors::NoData,
});
};
let data = data.buffer.as_slice();
let mut keymap: HashMap<String, Result<Vec<u8>, ParseFormError>> =
HashMap::with_capacity(keys.len());
for key in keys {
keymap.entry(key.to_string()).or_insert(Err(ParseFormError {
error: ParseErrors::NoData,
}));
}
match post_type {
Mime::ApplicationXWwwFormUrlencoded => {
for kvp in data.split(|byte| *byte == b'&') {
let kvp = kvp
.split(|byte| *byte == b'=')
.map(|list| list.to_vec())
.collect::<Vec<Vec<u8>>>();
let key = if let Some(kv) = kvp.get(0) {
kv
} else {
return Err(ParseFormError {
error: ParseErrors::BadData,
});
};
let key = if let Ok(kv) = String::from_utf8(key.to_vec()) {
kv
} else {
return Err(ParseFormError {
error: ParseErrors::BadData,
});
};
let value = kvp.get(1).ok_or(ParseFormError {
error: ParseErrors::NoData,
});
let thing = if let Some(val) = keymap.get_mut(key.as_str()) {
val
} else {
continue;
};
*thing = match value {
Ok(val) => Ok(val.to_vec()),
Err(err) => Err(err),
}
}
}
Mime::MultipartFormData => {
let mut temp_bound = "--".to_string();
temp_bound.push_str(&format!("{boundary}"));
let end_boundary = format!("{temp_bound}--\r").as_bytes().to_owned();
temp_bound.push('\r');
let boundary = temp_bound.as_bytes();
Self::get_multipart_data(data, boundary, &end_boundary, &mut keymap);
}
_ => {
return Err(ParseFormError {
error: ParseErrors::BadData,
})
}
};
Ok(keymap)
}
fn get_multipart_data(
data: &[u8],
boundary: &[u8],
end_boundary: &[u8],
map: &mut HashMap<String, Result<Vec<u8>, ParseFormError>>,
) {
let parts = data.split(|byte| byte == &b'\n').collect::<Vec<&[u8]>>();
let mut current_part: Vec<&[u8]> = vec![];
let mut current_key: Option<String> = None;
let mut ignore_line = 0;
for part in parts {
if part == &[b'\r'] {
if let Some(_) = current_key {
if ignore_line >= TWO_NEWLINES {
current_part.push(&[b'\n']);
continue;
}
ignore_line += 1;
}
continue;
}
if part == end_boundary {
if let Some(key) = &current_key {
let mut part = current_part.join(&b'\n');
if part.ends_with(&[b'\r']) {
part.pop();
}
map.insert(key.to_string(), Ok(part));
}
break;
}
if part == boundary {
if let Some(key) = &current_key {
let mut part = current_part.join(&b'\n');
if part.ends_with(&[b'\r']) {
part.pop();
}
map.insert(key.to_string(), Ok(part));
}
current_part = vec![];
current_key = None;
ignore_line = 0;
continue;
}
if part.starts_with(b"Content-Disposition: form-data; name=") {
let headers = part
.split(|byte| byte == &b';')
.filter(|header| !header.is_empty())
.collect::<Vec<_>>();
if headers.len() < 2 {
continue;
}
let name = headers[1].split(|byte| byte == &b'=').collect::<Vec<_>>();
if name.len() != 2 {
continue;
}
let mkey = String::from_utf8_lossy(name[1])
.as_ref()
.trim_end()
.trim_matches('"')
.to_owned();
if map.contains_key::<str>(&mkey) {
current_key = Some(mkey.to_owned());
}
continue;
} else if let Some(_) = &current_key {
current_part.push(part);
}
}
}
}
#[cfg(test)]
mod test {
use crate::handling::routes::Data;
use super::Request;
#[test]
fn try_post_text() {
let req = Request {
uri: "",
headers: vec!["Content-Type: application/x-www-form-urlencoded".to_string()],
method: crate::handling::methods::Method::Post,
cookies: None,
};
let data = Data {
buffer: b"message=23&message1=24".to_vec(),
is_complete: true,
};
let map = req.get_post_data(&["message", "message1"], &data).unwrap();
assert_eq!(
&b"23".to_vec(),
map.get("message").unwrap().as_ref().unwrap()
);
assert_eq!(
&b"24".to_vec(),
map.get("message1").unwrap().as_ref().unwrap()
);
let req = Request {
uri: "",
headers: vec!["Content-Type: multipart/form-data; boundary=\"boundary\"".to_string()],
method: crate::handling::methods::Method::Post,
cookies: None,
};
let data = Data {
buffer: b"--boundary\r
Content-Disposition: form-data; name=\"field1\"\r
\r
value1\r
--boundary\r
Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\"\r
\r
va\nlue2\r
--boundary--\r
"
.to_vec(),
is_complete: true,
};
let map = req.get_post_data(&["field1", "field2"], &data).unwrap();
assert_eq!(
&b"value1".to_vec(),
map.get("field1").unwrap().as_ref().unwrap()
);
assert_eq!(
&b"va\nlue2".to_vec(),
map.get("field2").unwrap().as_ref().unwrap()
);
}
}
use std::fmt::Display;
use crate::utils::mime::mime_enum::Mime;
use super::routes::Body;
type HeaderMap = Vec<String>;
#[derive(Debug)]
pub enum Outcome<S, E, F> {
Success(S),
Failure(E),
Forward(F),
}
pub trait ResponseBody: Send {
fn get_data(&self) -> Vec<u8>;
fn get_mime(&self) -> Mime;
fn get_len(&self) -> usize;
}
impl ResponseBody for Body {
fn get_data(&self) -> Vec<u8> {
self.body()
}
fn get_mime(&self) -> Mime {
self.mime_type()
}
fn get_len(&self) -> usize {
self.get_data().len()
}
}
impl ResponseBody for &str {
fn get_data(&self) -> Vec<u8> {
self.as_bytes().to_vec()
}
fn get_mime(&self) -> Mime {
Mime::TextPlain
}
fn get_len(&self) -> usize {
self.len()
}
}
impl ResponseBody for String {
fn get_data(&self) -> Vec<u8> {
self.as_bytes().to_vec()
}
fn get_mime(&self) -> Mime {
Mime::TextPlain
}
fn get_len(&self) -> usize {
self.len()
}
}
pub struct Response {
pub headers: HeaderMap,
pub status: Option<Status>,
pub body: Box<dyn ResponseBody>,
}
#[derive(Debug)]
pub enum Status {
Continue,
SwitchingProtocols,
WebDavProcessing,
ExperimentalEarlyHints,
Ok,
Created,
Accepted,
NonAuthorativeIfnormation,
NoContent,
ResetContent,
PartialContent,
WebDavMultiStatus,
WebDavAlreadyReported,
HttpDataEncodingImUsed,
MultipleChoices,
MovedPermanently,
Found,
SeeOther,
NotModfiied,
DeprecatedUseProxy,
UnusedUnused,
TemporaryRedirect,
PermanentRedirect,
BadRequest,
Unauthorized,
ExperimentalPaymentRequired,
Forbidden,
NotFound,
MethodNotAllowed,
NotAcceptable,
ProxyAuthenticationRequired,
RequestTimeout,
Conflict,
Gone,
LengthRequired,
PreconditionFailed,
PayloadTooLarge,
UriTooLong,
UnsupportedMediaType,
RangeNotSatisfiable,
ExpectationFailed,
ImATeapot,
MisdirectedRequest,
WebDavUnprocessableContent,
WebDavLocked,
WebDavFailedDependency,
ExperimenalTooEarly,
UpgradeRequred,
PreconditionRequired,
TooManyRequests,
RequestHeaderFieldsTooLarge,
UnavailableForLegalReasons,
InternalServerError,
NotImplemented,
BadGetaway,
ServiceUnavailable,
GetawayTimeout,
HttpVersionNotSupported,
VariantAlsoNegotiates,
WebDavInsufficientStorage,
WebDavLoopDetected,
NotExtended,
NetworkAuthenticationRequired,
}
impl Display for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Status::Continue => write!(f, "100 Continue"),
Status::SwitchingProtocols => write!(f, "101 Switching Protocols"),
Status::WebDavProcessing => write!(f, "102 Processing"),
Status::ExperimentalEarlyHints => write!(f, "103 Early Hints"),
Status::Ok => write!(f, "200 OK"),
Status::Created => write!(f, "201 Created"),
Status::Accepted => write!(f, "202 Accepted"),
Status::NonAuthorativeIfnormation => write!(f, "203 Non-Authorative Information"),
Status::NoContent => write!(f, "204 No Content"),
Status::ResetContent => write!(f, "205 Reset Content"),
Status::PartialContent => write!(f, "206 Partial Content"),
Status::WebDavMultiStatus => write!(f, "207 Mutli-Status"),
Status::WebDavAlreadyReported => write!(f, "208 Already Reported"),
Status::HttpDataEncodingImUsed => write!(f, "226 IM Used"),
Status::MultipleChoices => write!(f, "300 Multiple Choices"),
Status::MovedPermanently => write!(f, "301 Moved Permanently"),
Status::Found => write!(f, "302 Found"),
Status::SeeOther => write!(f, "303 See Other"),
Status::NotModfiied => write!(f, "304 Not Modified"),
Status::TemporaryRedirect => write!(f, "307 Temporary Redirect"),
Status::PermanentRedirect => write!(f, "308 Permanent Redirect"),
Status::DeprecatedUseProxy => write!(f, "305 Use Proxy"),
Status::UnusedUnused => write!(f, "306 unused"),
Status::BadRequest => write!(f, "400 Bad Request"),
Status::Unauthorized => write!(f, "401 Unauthorized"),
Status::ExperimentalPaymentRequired => write!(f, "402 Payment Required"),
Status::Forbidden => write!(f, "403 Forbidden"),
Status::NotFound => write!(f, "404 Not Found"),
Status::MethodNotAllowed => write!(f, "405 Method Not Allowed"),
Status::NotAcceptable => write!(f, "406 Not Acceptable"),
Status::ProxyAuthenticationRequired => {
write!(f, "407 Proxy Athentication Required")
}
Status::RequestTimeout => write!(f, "408 Request Timout"),
Status::Conflict => write!(f, "409 Conflict"),
Status::Gone => write!(f, "410 Gone"),
Status::LengthRequired => write!(f, "411 Length Required"),
Status::PreconditionFailed => write!(f, "412 Precondition Failed"),
Status::PayloadTooLarge => write!(f, "413 Payload Too Large"),
Status::UriTooLong => write!(f, "414 URI Too Long"),
Status::UnsupportedMediaType => write!(f, "415 Unsupported Media Type"),
Status::RangeNotSatisfiable => write!(f, "416 Range Not Satisfiable"),
Status::ExpectationFailed => write!(f, "417 Expectation Failed"),
Status::ImATeapot => write!(f, "418 I'm a Teapot"),
Status::MisdirectedRequest => write!(f, "421 Misdirected Request"),
Status::WebDavUnprocessableContent => write!(f, "422 Unprocessable Content"),
Status::WebDavLocked => write!(f, "423 Locked"),
Status::WebDavFailedDependency => write!(f, "424 Failed Dependency"),
Status::ExperimenalTooEarly => write!(f, "425 Too Early"),
Status::UpgradeRequred => write!(f, "426 Upgrade Required"),
Status::PreconditionRequired => write!(f, "428 Precondition Required"),
Status::TooManyRequests => write!(f, "429 Too Many Requests"),
Status::RequestHeaderFieldsTooLarge => {
write!(f, "431 Request Header Fields Too Large")
}
Status::UnavailableForLegalReasons => {
write!(f, "451 Unavailable For Legal Reasons")
}
Status::InternalServerError => write!(f, "500 Internal Server Error"),
Status::NotImplemented => write!(f, "501 Not Implmenented"),
Status::BadGetaway => write!(f, "502 Bad Getaway"),
Status::ServiceUnavailable => write!(f, "503 Service Unavailable"),
Status::GetawayTimeout => write!(f, "504 Getaway Timeout"),
Status::HttpVersionNotSupported => write!(f, "505 HTTP Version Not Supported"),
Status::VariantAlsoNegotiates => write!(f, "506 Variant Also Negotiates"),
Status::WebDavInsufficientStorage => write!(f, "507 Insufficient Storage"),
Status::WebDavLoopDetected => write!(f, "508 Loop Detected"),
Status::NotExtended => write!(f, "510 Not Extendend"),
Status::NetworkAuthenticationRequired => {
write!(f, "511 Network Authentication Required")
}
}
}
}
use crate::{
handling::{
methods::Method,
request::{MediaType, Request},
response::{Outcome, Response, Status},
},
utils::mime::mime_enum::Mime,
};
pub struct RoutInfo {
name: Option<&'static str>,
method: Method,
path: &'static str,
handler: fn(Request, Data) -> Outcome<Response, Status, Data>,
format: Option<MediaType>,
rank: Option<isize>,
}
#[derive(Clone, Copy)]
pub struct Route<'a> {
pub name: Option<&'static str>,
pub method: Method,
pub uri: Uri<'a>,
pub handler: fn(Request, Data) -> Outcome<Response, Status, Data>,
pub rank: isize,
pub format: Option<MediaType>,
}
impl Route<'_> {
pub fn from(routeinfo: RoutInfo) -> Self {
let rank = routeinfo.rank.unwrap_or(0);
Route {
name: routeinfo.name,
method: routeinfo.method,
uri: routeinfo.path,
handler: routeinfo.handler,
rank,
format: routeinfo.format,
}
}
pub fn compare_uri(&self, uri: Uri) -> bool {
let mut iter_comp_str = uri.split("/");
for true_str in self.uri.split("/") {
let comp_str = if let Some(str) = iter_comp_str.next() {
str
} else {
return false;
};
if (true_str.starts_with("<") && true_str.ends_with("..>"))
|| (comp_str.starts_with(true_str) && comp_str.contains("?"))
{
return true;
}
if true_str.starts_with("<") && true_str.ends_with(">") {
continue;
}
if true_str != comp_str {
return false;
}
}
true
}
}
pub type Uri<'a> = &'a str;
#[derive(Debug, Clone)]
pub struct Body {
body: Vec<u8>,
mime_type: Mime,
}
impl Body {
pub fn new(body: Vec<u8>, mime_type: Mime) -> Self {
Self { body, mime_type }
}
pub fn set_mime_type(&mut self, mime_type: Mime) {
self.mime_type = mime_type;
}
pub fn set_body(&mut self, body: Vec<u8>) {
self.body = body;
}
pub fn mime_type(&self) -> Mime {
self.mime_type
}
pub fn body(&self) -> Vec<u8> {
self.body.clone()
}
}
#[derive(Debug, Clone)]
pub struct Data {
pub buffer: Vec<u8>,
pub is_complete: bool,
}
impl Data {
pub fn is_empty(&self) -> bool {
self.buffer.len() == 0
}
}
pub fn add(left: usize, right: usize) -> usize {
left + right
}
pub mod handlers;
pub mod handling;
mod setup;
mod utils;
#[cfg(test)]
mod tests {
use super::*;
mod tests {}
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
pub use setup::build;
use std::thread::available_parallelism;
use tokio::{net::TcpListener, signal::unix::{SignalKind, signal}, select};
use crate::{
handlers::handlers::handle_connection,
handling::routes::{Route, Uri},
};
#[derive(Clone)]
pub struct MountPoint<'a> {
pub mountpoint: Uri<'a>,
pub routes: Vec<Route<'a>>,
}
pub struct Config {
mountpoints: Option<Vec<MountPoint<'static>>>,
address: TcpListener,
}
impl<'a> Config {
fn check_mountpoint_taken(&self, to_insert: Uri) -> bool {
if let Some(to_check) = &self.mountpoints {
let len = to_check.len();
for i in 0..len {
if to_check[i].mountpoint == to_insert {
return true; // Found a duplicate &str
}
}
};
false
}
pub fn mount(mut self, mountpoint: Uri<'static>, mut routes: Vec<Route<'static>>) -> Self {
if self.check_mountpoint_taken(mountpoint) {
eprintln!("\x1b[31mTrying to reassign a mountpoint, mountpoint `{mountpoint}` already taken.\x1b[0m");
return self;
}
routes.sort_by(|a, b| a.rank.cmp(&b.rank));
let mut mount_message = format!(" >> \x1b[35m{}\x1b[0m\n", mountpoint);
for (index, route) in routes.iter().enumerate() {
let indent_sign;
match index {
i if i == routes.len() - 1 => indent_sign = "└─",
_ => indent_sign = "├─",
};
mount_message += &format!(
" \x1b[35m{indent_sign}\x1b[0m \x1b[36m(\x1b[0m{}\x1b[36m)\x1b[0m \x1b[32m{}\x1b[0m \x1b[34;4m{}\x1b[24m{}\x1b[0m\n",
route.name.unwrap_or(""),
route.method,
mountpoint,
route.uri
)
}
println!("{mount_message}");
let mut temp_mountpoints = None;
std::mem::swap(&mut self.mountpoints, &mut temp_mountpoints);
if let Some(mut mountpoints) = temp_mountpoints {
mountpoints.push(MountPoint { mountpoint, routes });
self.mountpoints = Some(mountpoints);
} else {
self.mountpoints = Some(vec![MountPoint { mountpoint, routes }]);
}
self
}
pub async fn launch(self) {
println!("Server launched from http://{}", self.address.local_addr().unwrap());
let mut sigint = signal(SignalKind::interrupt()).unwrap();
loop {
select! {
_ = sigint.recv() => {
println!("Shutting down...");
break;
}
Ok((socket, _)) = self.address.accept() => {
let mountpoints = self.mountpoints.clone().unwrap();
tokio::spawn(async move { handle_connection(socket, mountpoints).await; });
}
}
}
}
}
pub async fn build(ip: &str) -> Config {
let listener = if let Ok(listener) = TcpListener::bind(ip).await {
listener
} else {
panic!("\x1b[31mCould't bind Listener to address\x1b[0m");
};
let ip = ip.splitn(2, ":").collect::<Vec<&str>>();
if ip.len() != 2 {
panic!("Invalid IP Address");
}
let port = ip[1];
let ip = ip[0];
let workers = available_parallelism().unwrap().get();
println!(
"\x1b[34m⚙ Configuration\x1b[0m
>> \x1b[34mIp\x1b[0m: {ip}
>> \x1b[34mPort\x1b[0m: {port}
>> \x1b[34mWorkers\x1b[0m: {workers}
\x1b[35m🛪 Mountpoints\x1b[0m"
);
Config {
mountpoints: None,
address: listener,
}
}
This diff is collapsed.
This diff is collapsed.
mod map;
pub mod mime_enum;
pub mod mime;
import csv
def generate_rust_enum(csv_file):
rust_enum = "use phf::phf_map;\n\nenum Mime {\n"
with open(csv_file, "r") as file:
csv_data = csv.reader(file)
next(csv_data) # Skip the header row
for row in csv_data:
if row[1] == "":
continue
if "DEPRECATED" in row[0]:
continue
name = format_enum_member(row[0:2])
rust_enum += f"\t{name},\n"
rust_enum += "}\n\n"
rust_enum += "impl std::fmt::Display for Mime {\n"
rust_enum += "\tfn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n"
rust_enum += "\t\tmatch self {\n"
with open(csv_file, "r") as file:
csv_data = csv.reader(file)
next(csv_data) # Skip the header row
for row in csv_data:
if row[1] == "":
continue
if "DEPRECATED" in row[0]:
continue
name = format_enum_member(row[0:2])
rust_enum += f'\t\t\tMime::{name} => write!(f, "{row[1]}"),\n'
rust_enum += "\t\t}\n\t}\n}\n\n"
rust_enum += "static MimeMap: phf::Map<&'static str, Mime> = phf_map! {\n"
with open(csv_file, "r") as file:
csv_data = csv.reader(file)
next(csv_data)
for row in csv_data:
if row[1] == "":
continue
if "DEPRECATED" in row[0]:
continue
key = row[1]
value = format_enum_member(row[0:2])
rust_enum += f'\t"{key}" => Mime::{value},\n'
rust_enum += "};"
return rust_enum
def format_enum_member(name):
prefix = "".join(name[1].split("/")[0:-1])
name = name[0].split("-")
words = []
parts = []
for part in name:
parts += part.split("+")
for part in parts:
words += part.split(".")
formatted_name = "".join(word.capitalize() for word in words)
if not formatted_name.startswith(prefix.capitalize()):
formatted_name = prefix.capitalize() + formatted_name
return formatted_name
# Usage example
csv_file = "mimes.csv"
rust_enum_code = generate_rust_enum(csv_file)
print(rust_enum_code)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>404</title>
</head>
<body>
<h1>404</h1>
<p>Hi from Rust</p>
<form action="/post/post" method="post" enctype="multipart/form-data">
<label for="message">Enter your message:</label>
<input type="text" id="message" name="message" />
<button type="submit">Send</button>
</form>
<form action="/static/hi" method="get">
<label for="jump">Enter asdf</label>
<input type="text" id="jump" name="asdf" />
<label for="jkl">Enter jkl;</label>
<input type="text" id="jkl" name="jkl" />
<button type="submit">Send</button>
</form>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>411</title>
</head>
<h1>411</h1>
</html>