2024-03-23 22:40:09 +01:00
|
|
|
use std::{
|
|
|
|
env,
|
|
|
|
fs::{self, File},
|
2024-03-24 18:15:39 +01:00
|
|
|
io::{BufReader, Read, Seek, Write},
|
2024-03-23 22:40:09 +01:00
|
|
|
net::{TcpListener, TcpStream},
|
|
|
|
path::{Path, PathBuf},
|
2024-03-24 18:15:39 +01:00
|
|
|
thread,
|
2024-03-23 22:40:09 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
mod http;
|
2024-03-24 18:15:39 +01:00
|
|
|
use http::{Content, Method, Request, RequestRange, Response, Status};
|
2022-06-04 19:22:41 +02:00
|
|
|
|
|
|
|
fn main() {
|
2022-06-05 20:45:19 +02:00
|
|
|
let args: Vec<String> = env::args().collect();
|
2022-06-04 19:22:41 +02:00
|
|
|
|
2022-06-05 20:45:19 +02:00
|
|
|
let host = if args.len() < 2 {
|
2022-06-11 05:38:09 +02:00
|
|
|
"127.0.0.1:55566"
|
2022-06-05 20:45:19 +02:00
|
|
|
} else {
|
|
|
|
&args[1]
|
|
|
|
};
|
|
|
|
println!("Starting server on {:?}...\n", &host);
|
|
|
|
|
|
|
|
let listener = TcpListener::bind(host).expect("Could not bind to address");
|
2022-06-04 19:22:41 +02:00
|
|
|
|
2024-03-24 18:15:39 +01:00
|
|
|
let mut threads = Vec::new();
|
|
|
|
|
2022-06-05 20:45:19 +02:00
|
|
|
for stream in listener.incoming() {
|
2024-03-23 18:03:05 +01:00
|
|
|
match stream {
|
2024-03-24 18:15:39 +01:00
|
|
|
Ok(stream) => threads.push(thread::spawn(|| handle_connection(stream))),
|
2024-03-25 23:12:37 +01:00
|
|
|
Err(err) => println!("Error with incoming stream: {err}"),
|
2022-06-05 20:45:19 +02:00
|
|
|
}
|
2024-03-25 23:12:37 +01:00
|
|
|
threads.retain(|j| !j.is_finished());
|
2024-03-25 22:27:27 +01:00
|
|
|
println!("{} connections open", threads.len());
|
2022-06-04 19:22:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_connection(mut stream: TcpStream) {
|
2024-03-25 22:27:27 +01:00
|
|
|
const MAX_REQUEST_SIZE: usize = 1024 * 4;
|
|
|
|
let Ok(client_ip) = stream.peer_addr() else {
|
2024-03-23 18:03:05 +01:00
|
|
|
return;
|
|
|
|
};
|
2024-03-25 22:27:27 +01:00
|
|
|
println!("[{client_ip}] new connection");
|
2024-03-23 18:03:05 +01:00
|
|
|
|
2024-03-24 18:15:39 +01:00
|
|
|
let mut buffer = Vec::with_capacity(2048);
|
|
|
|
loop {
|
|
|
|
let mut b = vec![0; 512];
|
|
|
|
let Ok(size) = stream.read(&mut b) else {
|
2024-03-25 22:27:27 +01:00
|
|
|
println!("[{client_ip}] connection broken");
|
2024-03-24 18:15:39 +01:00
|
|
|
return;
|
|
|
|
};
|
|
|
|
if size == 0 {
|
2024-03-25 22:27:27 +01:00
|
|
|
println!("[{client_ip}] connection closed by client");
|
2024-03-24 18:15:39 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
b.truncate(size);
|
|
|
|
buffer.extend_from_slice(&b);
|
2024-03-23 18:03:05 +01:00
|
|
|
|
2024-03-25 22:27:27 +01:00
|
|
|
if buffer.len() > MAX_REQUEST_SIZE {
|
|
|
|
println!("[{client_ip}] request over {MAX_REQUEST_SIZE} bytes, closing connection");
|
2024-03-24 18:15:39 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if buffer.ends_with(b"\r\n\r\n") {
|
|
|
|
let request = String::from_utf8_lossy(&buffer).to_string();
|
2024-03-25 22:27:27 +01:00
|
|
|
|
|
|
|
println!("[{client_ip}] received {} bytes", buffer.len());
|
2024-03-24 18:15:39 +01:00
|
|
|
// println!(
|
|
|
|
// "=======\n{}=======\n\n",
|
|
|
|
// request
|
|
|
|
// .escape_debug()
|
|
|
|
// .collect::<String>()
|
|
|
|
// .replace("\\r\\n", "\n")
|
|
|
|
// .replace("\\n", "\n")
|
|
|
|
// );
|
2024-03-25 23:12:37 +01:00
|
|
|
if handle_request(&request, &mut stream) {
|
2024-03-25 22:27:27 +01:00
|
|
|
println!("[{client_ip}] closing connection");
|
2024-03-24 18:15:39 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
buffer.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-03-23 18:03:05 +01:00
|
|
|
|
2024-03-25 23:12:37 +01:00
|
|
|
fn handle_request(request: &str, stream: &mut TcpStream) -> bool {
|
2024-03-25 22:27:27 +01:00
|
|
|
let Ok(client_ip) = stream.peer_addr() else {
|
|
|
|
return true;
|
|
|
|
};
|
2024-03-25 23:12:37 +01:00
|
|
|
let request = Request::parse(request);
|
2024-03-23 22:40:09 +01:00
|
|
|
let response;
|
2024-03-24 18:15:39 +01:00
|
|
|
let mut end_connection = true;
|
2024-03-23 22:40:09 +01:00
|
|
|
|
|
|
|
if let Some(request) = request {
|
2024-03-25 22:27:27 +01:00
|
|
|
println!("[{client_ip}] {} {}", request.method, request.path);
|
2024-03-23 22:40:09 +01:00
|
|
|
let head_only = request.method == Method::Head;
|
|
|
|
let path = request.path.clone();
|
2024-03-25 23:12:37 +01:00
|
|
|
response = get_file(&request)
|
2024-03-24 18:15:39 +01:00
|
|
|
.map(|(content, end_of_file)| {
|
|
|
|
end_connection = end_of_file;
|
2024-03-25 22:27:27 +01:00
|
|
|
println!("[{client_ip}] sending file content");
|
2024-03-24 18:15:39 +01:00
|
|
|
Response::new(Status::Ok).with_content(content)
|
|
|
|
})
|
2024-03-23 22:40:09 +01:00
|
|
|
.unwrap_or_else(|| {
|
2024-03-25 22:27:27 +01:00
|
|
|
println!("[{client_ip}] file not found");
|
2024-03-23 22:40:09 +01:00
|
|
|
Response::new(Status::NotFound)
|
2024-03-25 23:12:37 +01:00
|
|
|
.with_content(Content::text(format!("404 NOT FOUND - '{path}'")))
|
2024-03-23 22:40:09 +01:00
|
|
|
})
|
|
|
|
.format(head_only);
|
|
|
|
} else {
|
2024-03-25 22:27:27 +01:00
|
|
|
println!("[{client_ip}] bad request");
|
2024-03-23 22:40:09 +01:00
|
|
|
response = Response::new(Status::BadRequest).format(false);
|
2024-03-23 18:03:05 +01:00
|
|
|
}
|
2024-03-23 22:40:09 +01:00
|
|
|
|
2024-03-25 22:27:27 +01:00
|
|
|
if stream.write_all(&response).is_err() || stream.flush().is_err() {
|
|
|
|
println!("[{client_ip}] failed to send response");
|
|
|
|
}
|
2024-03-24 18:15:39 +01:00
|
|
|
end_connection
|
2022-06-04 19:22:41 +02:00
|
|
|
}
|
2022-06-11 05:38:09 +02:00
|
|
|
|
2024-03-25 23:12:37 +01:00
|
|
|
fn get_file(request: &Request) -> Option<(Content, bool)> {
|
2024-03-24 18:15:39 +01:00
|
|
|
const MAX_SIZE: usize = 1024 * 1024 * 8;
|
|
|
|
|
2024-03-25 14:45:38 +01:00
|
|
|
let current_dir = env::current_dir().unwrap();
|
|
|
|
|
|
|
|
let path = current_dir
|
|
|
|
.join(request.path.strip_prefix('/')?)
|
2024-03-23 22:40:09 +01:00
|
|
|
.canonicalize()
|
|
|
|
.ok()?;
|
2024-03-25 14:45:38 +01:00
|
|
|
|
|
|
|
if path
|
|
|
|
.strip_prefix(current_dir.canonicalize().unwrap())
|
|
|
|
.is_err()
|
|
|
|
{
|
|
|
|
println!("illegal path: {}", request.path);
|
2024-03-23 22:40:09 +01:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
if path.is_dir() {
|
|
|
|
let index_file = path.join("index.html");
|
|
|
|
if index_file.is_file() {
|
2024-03-24 18:15:39 +01:00
|
|
|
Some((Content::html(fs::read_to_string(index_file).ok()?), true))
|
2024-03-23 22:40:09 +01:00
|
|
|
} else {
|
2024-03-24 18:15:39 +01:00
|
|
|
generate_index(&request.path, &path).map(|c| (c, true))
|
2024-03-23 22:40:09 +01:00
|
|
|
}
|
|
|
|
} else if path.is_file() {
|
|
|
|
let ext = path.extension().unwrap_or_default().to_str()?;
|
2024-03-24 18:15:39 +01:00
|
|
|
let file = File::open(&path).ok()?;
|
2024-03-25 14:22:06 +01:00
|
|
|
let size = file.metadata().ok()?.len() as usize;
|
2024-03-24 18:15:39 +01:00
|
|
|
|
|
|
|
let mut buf = vec![0; MAX_SIZE];
|
|
|
|
let mut reader = BufReader::new(file);
|
|
|
|
let start_pos = match request.range {
|
|
|
|
Some(RequestRange::From(p)) => p,
|
|
|
|
Some(RequestRange::Full(start, _end)) => start,
|
|
|
|
_ => 0,
|
|
|
|
};
|
|
|
|
reader
|
|
|
|
.seek(std::io::SeekFrom::Start(start_pos as u64))
|
|
|
|
.ok()?;
|
|
|
|
|
|
|
|
let size_read = reader.read(&mut buf).ok()?;
|
|
|
|
buf.truncate(size_read);
|
|
|
|
let mut end_of_file = false;
|
|
|
|
let range = if size_read < size {
|
|
|
|
end_of_file = start_pos + size_read == size;
|
|
|
|
Some((start_pos, start_pos + size_read - 1, size))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
Some((Content::file(ext, buf).with_range(range), end_of_file))
|
2024-03-23 18:03:05 +01:00
|
|
|
} else {
|
2024-03-23 22:40:09 +01:00
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn generate_index(relative_path: &str, path: &Path) -> Option<Content> {
|
2024-03-23 22:50:03 +01:00
|
|
|
let mut dirs: Vec<_> = path
|
2024-03-23 22:40:09 +01:00
|
|
|
.read_dir()
|
|
|
|
.ok()?
|
|
|
|
.flatten()
|
2024-03-23 22:50:03 +01:00
|
|
|
.filter_map(|d| {
|
2024-03-25 23:12:37 +01:00
|
|
|
let size = if d.file_type().ok()?.is_dir() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(d.metadata().ok()?.len())
|
|
|
|
};
|
|
|
|
|
|
|
|
d.file_name().to_str().map(|s| (s.to_owned(), size))
|
2024-03-23 22:50:03 +01:00
|
|
|
})
|
|
|
|
.collect();
|
2024-03-25 23:12:37 +01:00
|
|
|
dirs.sort_by(|(name_a, size_a), (name_b, size_b)| {
|
|
|
|
size_a
|
|
|
|
.is_some()
|
|
|
|
.cmp(&size_b.is_some())
|
|
|
|
.then(name_a.cmp(name_b))
|
|
|
|
});
|
2024-03-23 22:50:03 +01:00
|
|
|
let list = dirs
|
|
|
|
.into_iter()
|
2024-03-25 23:12:37 +01:00
|
|
|
.map(|(name, size)| {
|
|
|
|
let formatted_size = size.map(format_size).unwrap_or_default();
|
|
|
|
format!(
|
|
|
|
"<tr><td><a href=\"{href}\">{name}{trailing_slash}</a></td><td>{formatted_size}</td></tr>\n",
|
|
|
|
href = PathBuf::from(relative_path).join(&name).display(),
|
|
|
|
trailing_slash = if size.is_some() { "" } else { "/" },
|
|
|
|
)
|
2024-03-23 22:40:09 +01:00
|
|
|
})
|
|
|
|
.fold(String::new(), |mut content, entry| {
|
|
|
|
content.push_str(&entry);
|
|
|
|
content
|
|
|
|
});
|
2024-03-25 16:01:48 +01:00
|
|
|
let parent = if relative_path != "/" {
|
2024-03-25 23:12:37 +01:00
|
|
|
"<tr><td><a href=\"..\">../</a></td><td></td></tr>"
|
2024-03-25 16:01:48 +01:00
|
|
|
} else {
|
|
|
|
""
|
|
|
|
};
|
2024-03-23 22:40:09 +01:00
|
|
|
let page = format!(
|
|
|
|
r#"<!DOCTYPE html>
|
|
|
|
<head>
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
<title>Index of {relative_path}</title>
|
2024-03-25 23:12:37 +01:00
|
|
|
<style>
|
|
|
|
html {{ color-scheme: dark; }}
|
|
|
|
tr:nth-child(odd) {{ background-color: #333; }}
|
|
|
|
</style>
|
2024-03-23 22:40:09 +01:00
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<h3>Index of {relative_path}</h3>
|
2024-03-25 23:12:37 +01:00
|
|
|
<table>
|
|
|
|
{parent}
|
|
|
|
{list}
|
|
|
|
</table>
|
2024-03-23 22:40:09 +01:00
|
|
|
</body>
|
|
|
|
</html>"#,
|
|
|
|
);
|
|
|
|
Some(Content::html(page))
|
2022-06-11 05:38:09 +02:00
|
|
|
}
|
2024-03-25 23:12:37 +01:00
|
|
|
|
|
|
|
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))
|
|
|
|
}
|
|
|
|
}
|