Compare commits

..

No commits in common. "7a392e9c6c18b6e904efc7bf21b59efa069e8608" and "210e24415edb7439fc4d7e0094673c25fe8f4a5d" have entirely different histories.

2 changed files with 47 additions and 85 deletions

View file

@ -13,7 +13,7 @@ pub enum RequestRange {
Suffix(usize), Suffix(usize),
} }
#[derive(Debug, PartialEq, Clone, Copy)] #[derive(Debug, PartialEq)]
pub enum Method { pub enum Method {
Get, Get,
Head, Head,
@ -35,7 +35,7 @@ pub enum Status {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Content { pub struct Content {
mime_type: &'static str, content_type: &'static str,
range: Option<(usize, usize, usize)>, range: Option<(usize, usize, usize)>,
bytes: Vec<u8>, bytes: Vec<u8>,
} }
@ -93,14 +93,14 @@ impl Response {
let mut buffer = format!( let mut buffer = format!(
"{}\r\nContent-Type: {}\r\nAccept-Ranges: bytes\r\nContent-Length: {}\r\n", "{}\r\nContent-Type: {}\r\nAccept-Ranges: bytes\r\nContent-Length: {}\r\n",
self.status.header(), self.status.header(),
content.mime_type, content.content_type,
content.bytes.len(), content.bytes.len(),
) )
.into_bytes(); .into_bytes();
if let Some((start, end, size)) = content.range { if let Some((start, end, size)) = content.range {
buffer.extend_from_slice( buffer.extend_from_slice(
format!("Content-Range: bytes {start}-{end}/{size}\r\n").as_bytes(), format!("Content-Range: bytes {}-{}/{}\r\n", start, end, size).as_bytes(),
); )
} }
buffer.extend_from_slice(b"\r\n"); buffer.extend_from_slice(b"\r\n");
if !head_only { if !head_only {
@ -168,7 +168,7 @@ impl Content {
} }
}; };
Self { Self {
mime_type: content_type, content_type,
range: None, range: None,
bytes, bytes,
} }
@ -209,15 +209,6 @@ impl Method {
} }
} }
impl std::fmt::Display for Method {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Method::Get => write!(f, "GET"),
Method::Head => write!(f, "HEAD"),
}
}
}
impl RequestRange { impl RequestRange {
fn parse(source: &str) -> Option<Self> { fn parse(source: &str) -> Option<Self> {
let source = source.strip_prefix("bytes=")?; let source = source.strip_prefix("bytes=")?;

View file

@ -27,42 +27,40 @@ fn main() {
for stream in listener.incoming() { for stream in listener.incoming() {
match stream { match stream {
Ok(stream) => threads.push(thread::spawn(|| handle_connection(stream))), Ok(stream) => threads.push(thread::spawn(|| handle_connection(stream))),
Err(err) => println!("Error with incoming stream: {err}"), Err(err) => println!("Error with incoming stream: {}", err),
} }
threads.retain(|j| !j.is_finished()); threads = threads.into_iter().filter(|j| !j.is_finished()).collect();
println!("{} connections open", threads.len()); println!("{} threads open", threads.len());
} }
} }
fn handle_connection(mut stream: TcpStream) { fn handle_connection(mut stream: TcpStream) {
const MAX_REQUEST_SIZE: usize = 1024 * 4; let Ok(peer_addr) = stream.peer_addr() else {
let Ok(client_ip) = stream.peer_addr() else {
return; return;
}; };
println!("[{client_ip}] new connection"); println!("#### new connection from {peer_addr}");
let mut buffer = Vec::with_capacity(2048); let mut buffer = Vec::with_capacity(2048);
loop { loop {
let mut b = vec![0; 512]; let mut b = vec![0; 512];
let Ok(size) = stream.read(&mut b) else { let Ok(size) = stream.read(&mut b) else {
println!("[{client_ip}] connection broken"); println!("failed to read ");
return; return;
}; };
if size == 0 { if size == 0 {
println!("[{client_ip}] connection closed by client"); println!("nothing read");
return; return;
} }
b.truncate(size); b.truncate(size);
buffer.extend_from_slice(&b); buffer.extend_from_slice(&b);
if buffer.len() > MAX_REQUEST_SIZE { if buffer.len() > 4096 {
println!("[{client_ip}] request over {MAX_REQUEST_SIZE} bytes, closing connection"); println!("request too long");
return; return;
} }
if buffer.ends_with(b"\r\n\r\n") { if buffer.ends_with(b"\r\n\r\n") {
let request = String::from_utf8_lossy(&buffer).to_string(); let request = String::from_utf8_lossy(&buffer).to_string();
// println!("Received {} bytes from {}", buffer.len(), peer_addr);
println!("[{client_ip}] received {} bytes", buffer.len());
// println!( // println!(
// "=======\n{}=======\n\n", // "=======\n{}=======\n\n",
// request // request
@ -71,51 +69,48 @@ fn handle_connection(mut stream: TcpStream) {
// .replace("\\r\\n", "\n") // .replace("\\r\\n", "\n")
// .replace("\\n", "\n") // .replace("\\n", "\n")
// ); // );
if handle_request(&request, &mut stream) { if handle_request(request, &mut stream) {
println!("[{client_ip}] closing connection"); println!("closing connection");
return; return;
} }
// println!("keeping connection");
buffer.clear(); buffer.clear();
} }
} }
} }
fn handle_request(request: &str, stream: &mut TcpStream) -> bool { fn handle_request(request: String, stream: &mut TcpStream) -> bool {
let Ok(client_ip) = stream.peer_addr() else { let request = Request::parse(&request);
return true;
};
let request = Request::parse(request);
let response; let response;
let mut end_connection = true; let mut end_connection = true;
if let Some(request) = request { if let Some(request) = request {
println!("[{client_ip}] {} {}", request.method, request.path);
let head_only = request.method == Method::Head; let head_only = request.method == Method::Head;
let path = request.path.clone(); let path = request.path.clone();
response = get_file(&request) response = get_file(request)
.map(|(content, end_of_file)| { .map(|(content, end_of_file)| {
end_connection = end_of_file; end_connection = end_of_file;
println!("[{client_ip}] sending file content");
Response::new(Status::Ok).with_content(content) Response::new(Status::Ok).with_content(content)
}) })
.unwrap_or_else(|| { .unwrap_or_else(|| {
println!("[{client_ip}] file not found");
Response::new(Status::NotFound) Response::new(Status::NotFound)
.with_content(Content::text(format!("404 NOT FOUND - '{path}'"))) .with_content(Content::text(format!("FILE NOT FOUND - '{}'", path)))
}) })
.format(head_only); .format(head_only);
} else { } else {
println!("[{client_ip}] bad request");
response = Response::new(Status::BadRequest).format(false); response = Response::new(Status::BadRequest).format(false);
} }
if stream.write_all(&response).is_err() || stream.flush().is_err() { stream
println!("[{client_ip}] failed to send response"); .write_all(&response)
} .unwrap_or_else(|_| println!("failed to respond"));
stream
.flush()
.unwrap_or_else(|_| println!("failed to flush"));
end_connection end_connection
} }
fn get_file(request: &Request) -> Option<(Content, bool)> { fn get_file(request: Request) -> Option<(Content, bool)> {
const MAX_SIZE: usize = 1024 * 1024 * 8; const MAX_SIZE: usize = 1024 * 1024 * 8;
let current_dir = env::current_dir().unwrap(); let current_dir = env::current_dir().unwrap();
@ -177,37 +172,31 @@ fn generate_index(relative_path: &str, path: &Path) -> Option<Content> {
.ok()? .ok()?
.flatten() .flatten()
.filter_map(|d| { .filter_map(|d| {
let size = if d.file_type().ok()?.is_dir() { let is_dir = d.file_type().ok()?.is_dir();
None d.file_name().to_str().map(|s| (s.to_owned(), is_dir))
} else {
Some(d.metadata().ok()?.len())
};
d.file_name().to_str().map(|s| (s.to_owned(), size))
}) })
.collect(); .collect();
dirs.sort_by(|(name_a, size_a), (name_b, size_b)| { dirs.sort_by(|(name_a, dir_a), (name_b, dir_b)| dir_b.cmp(dir_a).then(name_a.cmp(name_b)));
size_a
.is_some()
.cmp(&size_b.is_some())
.then(name_a.cmp(name_b))
});
let list = dirs let list = dirs
.into_iter() .into_iter()
.map(|(name, size)| { .filter_map(|(name, is_dir)| {
let formatted_size = size.map(format_size).unwrap_or_default(); let mut s = format!(
format!( " <li><a href=\"{}\">{}",
"<tr><td><a href=\"{href}\">{name}{trailing_slash}</a></td><td>{formatted_size}</td></tr>\n", PathBuf::from(relative_path).join(&name).display(),
href = PathBuf::from(relative_path).join(&name).display(), name
trailing_slash = if size.is_some() { "" } else { "/" }, );
) if is_dir {
s.push('/');
}
s.push_str("</a></li>\n");
Some(s)
}) })
.fold(String::new(), |mut content, entry| { .fold(String::new(), |mut content, entry| {
content.push_str(&entry); content.push_str(&entry);
content content
}); });
let parent = if relative_path != "/" { let parent = if relative_path != "/" {
"<tr><td><a href=\"..\">../</a></td><td></td></tr>" r#" <li><a href="..">../</a></li>"#
} else { } else {
"" ""
}; };
@ -216,31 +205,13 @@ fn generate_index(relative_path: &str, path: &Path) -> Option<Content> {
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Index of {relative_path}</title> <title>Index of {relative_path}</title>
<style>
html {{ color-scheme: dark; }}
tr:nth-child(odd) {{ background-color: #333; }}
</style>
</head> </head>
<body> <body>
<h3>Index of {relative_path}</h3> <h3>Index of {relative_path}</h3>
<table> <ul>
{parent} {parent}{list} </ul>
{list}
</table>
</body> </body>
</html>"#, </html>"#,
); );
Some(Content::html(page)) Some(Content::html(page))
} }
fn format_size(bytes: u64) -> String {
if bytes < 1024 {
format!("{bytes} B")
} else if bytes < 1024 * 1024 {
format!("{:.1} KiB", bytes as f64 / 1024.0)
} else if bytes < 1024 * 1024 * 1024 {
format!("{:.1} MiB", bytes as f64 / (1024.0 * 1024.0))
} else {
format!("{:.1} GiB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
}
}