init
This commit is contained in:
26
src/main.rs
Normal file
26
src/main.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
mod server;
|
||||
mod router;
|
||||
mod response;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use logger_rust::*;
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
let port: u16 = args.get(1)
|
||||
.and_then(|p| p.parse().ok())
|
||||
.unwrap_or(8080);
|
||||
|
||||
let root = Arc::new(
|
||||
PathBuf::from(args.get(2).map(|s| s.as_str()).unwrap_or("."))
|
||||
);
|
||||
|
||||
if !root.exists() || !root.is_dir() {
|
||||
log_error!("Root {} isnt a valid directory", root.display());
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
server::run(root, port);
|
||||
}
|
||||
47
src/response.rs
Normal file
47
src/response.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use std::fs::File;
|
||||
use std::io::{Cursor, Read};
|
||||
use std::path::Path;
|
||||
use tiny_http::{Header, Response};
|
||||
|
||||
type BoxedResponse = Response<Cursor<Vec<u8>>>;
|
||||
|
||||
fn content_type(value: &str) -> Header {
|
||||
Header::from_bytes("Content-Type", value).unwrap()
|
||||
}
|
||||
|
||||
pub fn serve_file(path: &Path) -> BoxedResponse {
|
||||
let mime = mime_guess::from_path(path)
|
||||
.first_or_octet_stream()
|
||||
.to_string();
|
||||
|
||||
let mut file = match File::open(path) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return not_found(),
|
||||
};
|
||||
|
||||
let mut content = Vec::new();
|
||||
if file.read_to_end(&mut content).is_err() {
|
||||
return internal_error();
|
||||
}
|
||||
|
||||
Response::from_data(content)
|
||||
.with_header(content_type(&mime))
|
||||
}
|
||||
|
||||
pub fn not_found() -> BoxedResponse {
|
||||
Response::from_string("404 Not Found")
|
||||
.with_status_code(404)
|
||||
.with_header(content_type("text/plain"))
|
||||
}
|
||||
|
||||
pub fn forbidden() -> BoxedResponse {
|
||||
Response::from_string("403 Forbidden")
|
||||
.with_status_code(403)
|
||||
.with_header(content_type("text/plain"))
|
||||
}
|
||||
|
||||
pub fn internal_error() -> BoxedResponse {
|
||||
Response::from_string("500 Internal Server Error")
|
||||
.with_status_code(500)
|
||||
.with_header(content_type("text/plain"))
|
||||
}
|
||||
20
src/router.rs
Normal file
20
src/router.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub fn resolve(root: &Path, url_path: &str) -> Option<PathBuf> {
|
||||
let rel = url_path.trim_start_matches('/');
|
||||
let rel = if rel.is_empty() { "index.html" } else { rel }; // default to index.html in . dir
|
||||
|
||||
//let rel = rel.split('?').next().unwrap_or(rel);
|
||||
|
||||
let joined = root.join(rel);
|
||||
|
||||
// prevent escape essentially
|
||||
let canonical_root = root.canonicalize().ok()?;
|
||||
let canonical_file = joined.canonicalize().ok()?;
|
||||
|
||||
if canonical_file.starts_with(&canonical_root) {
|
||||
Some(canonical_file)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
43
src/server.rs
Normal file
43
src/server.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use logger_rust::*;
|
||||
use threadpool::ThreadPool;
|
||||
use tiny_http::Server;
|
||||
|
||||
use crate::router;
|
||||
use crate::response;
|
||||
|
||||
pub fn run(root: Arc<PathBuf>, port: u16) {
|
||||
let addr = format!("0.0.0.0:{}", port);
|
||||
let server = Arc::new(Server::http(&addr).expect("Failed to start server"));
|
||||
let pool = ThreadPool::new(num_cpus());
|
||||
|
||||
log_info!("Serving '{}' on http://{}", root.display(), addr);
|
||||
|
||||
for request in server.incoming_requests() {
|
||||
let root = Arc::clone(&root);
|
||||
|
||||
pool.execute(move || {
|
||||
let url = request.url().to_owned();
|
||||
log_info!("{} {}", request.method(), url);
|
||||
|
||||
let resp = match router::resolve(&root, &url) {
|
||||
Some(path) => response::serve_file(&path),
|
||||
None => {
|
||||
log_warn!("Rejected path traversal attempt: {}", url);
|
||||
response::forbidden()
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = request.respond(resp) {
|
||||
log_error!("Failed to send response: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn num_cpus() -> usize {
|
||||
std::thread::available_parallelism()
|
||||
.map(|n| n.get())
|
||||
.unwrap_or(4)
|
||||
}
|
||||
Reference in New Issue
Block a user