http_mini_lib/utils/
http_server.rs

1use crate::traits::stream_trait::StreamTrait;
2use crate::utils::fs::get_dir_contents_as_html;
3use crate::utils::mimes::get_mime_type;
4use crate::utils::{fs, http_response};
5use std::ffi::OsStr;
6use std::io::Error;
7use std::net::{IpAddr, SocketAddr, TcpListener, TcpStream};
8use std::path::Path;
9
10/// # Setup and start TcpListener
11pub fn run(address: &str, mut port: i32) -> Result<TcpListener, Error> {
12    if !(1..=65535).contains(&port) {
13        println!("Invalid port number {}. Using port 8080", port);
14        port = 8080;
15    }
16
17    let ip: IpAddr = address.parse().unwrap();
18    let addr = SocketAddr::new(ip, port as u16);
19    let result_tcp_listener = TcpListener::bind(addr);
20
21    if result_tcp_listener.is_err() {
22        return Err(result_tcp_listener.err().unwrap());
23    }
24
25    let listener = result_tcp_listener?;
26
27    Ok(listener)
28}
29
30/// # Main connections handler
31pub fn handle_connection(
32    stream: TcpStream,
33    source_dir: &Path,
34    executable_name: &OsStr,
35    address: &str,
36) {
37    let http_request = stream.parse();
38    if http_request.is_err() {
39        http_response::send(
40            &stream,
41            "HTTP/1.1 400 Bad Request",
42            None,
43            Option::from(Vec::from(http_request.err().unwrap().to_string())),
44        );
45        return;
46    }
47
48    let request = http_request.unwrap();
49
50    // get file contents
51    let request_path = request.path;
52    if request_path.is_none() {
53        http_response::send(&stream, "HTTP/1.1 400 Bad Request", None, None);
54        return;
55    }
56
57    let file_path = source_dir.join(request_path.unwrap().trim_start_matches('/'));
58
59    // list directory contents with usable links
60    if file_path.is_dir() {
61        let dir_contents_as_html = get_dir_contents_as_html(&file_path, source_dir, address);
62        if dir_contents_as_html.is_err() {
63            http_response::send(&stream, "HTTP/1.1 500 Internal Server Error", None, None);
64            return;
65        }
66
67        http_response::send(
68            &stream,
69            "HTTP/1.1 200 OK",
70            Option::from(vec![("Content-Type".to_string(), "text/html".to_string())]),
71            Option::from(Vec::from(dir_contents_as_html.ok().unwrap())),
72        );
73        return;
74    }
75
76    if !fs::validate_path(file_path.as_ref()) {
77        http_response::send(&stream, "HTTP/1.1 404 Not Found", None, None);
78        return;
79    }
80
81    let file_contents = fs::get_file_contents(file_path.to_str().unwrap());
82    if file_contents.is_err() {
83        http_response::send(
84            &stream,
85            "HTTP/1.1 400 Bad Request",
86            None,
87            Option::from(Vec::from(file_contents.err().unwrap().to_string())),
88        );
89        return;
90    }
91
92    // Extra protection. Prevent calling own executable i.e. http://localhost:8080/mini-http !!!
93    if file_path.file_name().is_none() || file_path.file_name().unwrap() == executable_name {
94        http_response::send(&stream, "HTTP/1.1 400 Bad Request", None, None);
95        return;
96    }
97
98    // All OK. Show the file.
99
100    let mut response_headers: Vec<(String, String)> = vec![];
101    let mut has_content_type = false;
102    if file_path.extension().is_some() {
103        let mt = get_mime_type(file_path.extension().unwrap().to_str().unwrap());
104        has_content_type = true;
105        response_headers.push(("Content-Type".to_string(), mt));
106    }
107    if !has_content_type {
108        response_headers.push(("Content-Type".to_string(), "text/plain".to_string()));
109    }
110
111    let status_line = format!("{} 200 OK", request.protocol.unwrap());
112    http_response::send(
113        &stream,
114        status_line.as_str(),
115        Option::from(response_headers),
116        Option::from(file_contents.unwrap()),
117    );
118}