2021-09-02 18:13:37 +01:00
use std ::io ::{ Write , BufReader , BufRead } ;
2021-08-29 16:07:05 +01:00
use std ::net ::{ TcpListener , TcpStream } ;
2021-08-30 16:17:11 +01:00
use std ::fs ;
use std ::string ::{ String } ;
2021-12-22 20:02:09 +00:00
fn detect_media_type ( filename : String ) -> String {
// The Lynx terminal browser made me do this.
let ext_index = filename . to_string ( ) . find ( " . " ) . unwrap ( ) ;
let test = ext_index + 1 ;
let ext = & filename [ test .. ] ;
match ext {
" aac " = > " audio/aac " . to_string ( ) ,
" avi " = > " video/x-msvideo " . to_string ( ) ,
" bmp " = > " image/bmp " . to_string ( ) ,
" bz2 " = > " application/x-bzip2 " . to_string ( ) ,
" css " = > " text/css " . to_string ( ) ,
" gz " = > " application/gzip " . to_string ( ) ,
" gif " = > " image/gif " . to_string ( ) ,
" png " = > " image/png " . to_string ( ) ,
" pdf " = > " application/pdf " . to_string ( ) ,
" jpeg " = > " image/jpeg " . to_string ( ) ,
" jpg " = > " image/jpeg " . to_string ( ) ,
" js " = > " text/javascript " . to_string ( ) ,
" mid " = > " audio/midi " . to_string ( ) ,
" midi " = > " audio/midi " . to_string ( ) ,
" mp3 " = > " audio/mpeg " . to_string ( ) ,
" mp4 " = > " video/mp4 " . to_string ( ) ,
" mpeg " = > " video/mpeg " . to_string ( ) ,
" ogg " = > " audio/ogg " . to_string ( ) ,
" oga " = > " audio/ogg " . to_string ( ) ,
" ogv " = > " video/ogg " . to_string ( ) ,
" opus " = > " audio/opus " . to_string ( ) ,
" txt " = > " text/plain " . to_string ( ) ,
" html " = > " text/html " . to_string ( ) ,
" htm " = > " text/html " . to_string ( ) ,
_ = > " application/octet-stream " . to_string ( ) ,
}
}
2021-09-02 18:13:37 +01:00
fn check_if_path_exists ( path : String ) -> bool {
// This is probably not the best way of checking if a path
// exists but I don't care :)
let file = fs ::metadata ( path ) ;
match file {
Ok ( _ ) = > true ,
Err ( _ ) = > false ,
}
}
fn check_if_dir ( directory : String ) -> bool {
let path = directory + " /index.html " ;
let result = check_if_path_exists ( path ) ;
return result ;
}
2021-09-05 16:16:07 +01:00
fn generate_index ( directory : String ) -> String {
let mut index = format! ( " <!DOCTYPE HTML><html><body><h1>Directory of {} </h1> " , directory ) ;
for file in fs ::read_dir ( directory ) . unwrap ( ) {
index = format! ( " {} <br/><a href= {} > {:#?} </a> " , index , file . as_ref ( ) . unwrap ( ) . path ( ) . display ( ) , file . unwrap ( ) . file_name ( ) ) ;
}
return index . to_string ( ) ;
}
2021-09-02 17:25:53 +01:00
fn get_page ( filename : String ) -> String {
2021-09-02 16:16:38 +01:00
2021-08-30 16:17:11 +01:00
// The loaded page should be left immutable as it does
// not need to be modified by the server.
2021-09-05 16:16:07 +01:00
let path = filename . to_string ( ) ;
let checks = check_if_path_exists ( path ) ;
let path = filename . to_string ( ) ;
let index = check_if_dir ( path ) ;
let path = filename . to_string ( ) ;
println! ( " {} {} {} " , path , checks , index ) ;
if checks = = true & & index = = false {
if path . contains ( " . " ) ! = true {
let result = generate_index ( filename ) ;
return result . to_string ( ) ;
}
}
2021-09-02 18:13:37 +01:00
if filename = = " .error_server_404 " {
let result = " <!DOCTYPE HTML><html><body><h1>404 Not Found</h1><p>The resource you are trying to locate cannot be accessed!</p></body></html> " ;
return result . to_string ( ) ;
} else if filename = = " .error_server_503 " {
2021-09-02 17:25:53 +01:00
let result = " <!DOCTYPE HTML><html><body><h1>503 Not Implemented</h1><p>The request sent by your web browser cannot be handled by this server software.</p></body></html> " ;
return result . to_string ( ) ;
} else {
let result = fs ::read_to_string ( filename ) ;
match result {
Ok ( i ) = > i . to_string ( ) ,
2021-09-02 18:13:37 +01:00
Err ( _e ) = > " <!DOCTYPE HTML><html><body><h1>403 Forbidden</h1><p>The resource you are trying to access cannot be read by the server.</p></body></html> " . to_string ( ) ,
2021-09-02 17:25:53 +01:00
}
2021-08-30 17:32:44 +01:00
}
2021-08-30 16:17:11 +01:00
}
2021-08-29 16:07:05 +01:00
2021-09-02 16:16:38 +01:00
fn process_request ( request : Vec < u8 > ) -> String {
let mut input = String ::from_utf8_lossy ( & request ) . to_string ( ) ;
2021-09-02 18:13:37 +01:00
2021-12-22 20:02:09 +00:00
let debug = false ;
2021-09-02 18:13:37 +01:00
let mut index = String ::new ( ) ;
2021-09-02 16:18:23 +01:00
let output ;
2021-09-02 16:16:38 +01:00
if input . contains ( " GET " ) {
// To-do: find a less atrocious way to do this.
2021-09-02 17:28:07 +01:00
println! ( " Stream sent GET request. " ) ;
2021-12-22 20:02:09 +00:00
if debug { println! ( " {} " , input ) ; }
2021-09-02 16:16:38 +01:00
input = input . replace ( " GET " , " " ) ;
2021-09-02 17:25:53 +01:00
input = input . replace ( " HTTP/1.1 \r \n " , " " ) ;
2021-12-22 20:02:09 +00:00
// Lynx also made me do this
input = input . replace ( " HTTP/1.0 \r \n " , " " ) ;
2021-09-02 16:16:38 +01:00
// Theoretically by this point, the request
// will have been cut down to just the
// requested resource, but in my experience
// this code is gonna result in like 50k errors
// and I'm gonna have to rewrite it to get it
// to actually work the way I want it to.
2021-09-02 17:25:53 +01:00
if input ! = " / " {
2021-09-02 16:25:13 +01:00
let mut chars = input . chars ( ) ;
chars . next ( ) ;
2021-09-02 18:13:37 +01:00
let path = chars . as_str ( ) . to_string ( ) ;
let exists = check_if_path_exists ( path ) ;
if exists = = false {
output = " .error_server_404 " ;
} else {
let path = chars . as_str ( ) . to_string ( ) ;
let dir = check_if_dir ( path ) ;
if dir = = true {
let path = chars . as_str ( ) . to_string ( ) ;
index + = & path ;
index + = " /index.html " ;
output = index . as_str ( ) . clone ( ) ;
} else {
output = chars . as_str ( ) ;
}
}
2021-09-02 17:25:53 +01:00
} else {
output = " index.html " ;
2021-09-02 16:16:38 +01:00
}
2021-09-02 17:25:53 +01:00
2021-09-02 16:16:38 +01:00
} else {
// It's get_page()'s problem now.
2021-09-02 18:13:37 +01:00
println! ( " Stream sent unhandlable request. " ) ;
output = " .error_server_503 " ;
2021-09-02 16:16:38 +01:00
}
2021-09-02 16:25:13 +01:00
// Did you want to see chars.as_str().to_string()?
return output . to_string ( ) ;
2021-08-30 17:32:44 +01:00
2021-08-30 17:18:22 +01:00
}
2021-08-29 16:07:05 +01:00
fn serve ( mut stream : TcpStream ) {
2021-08-30 17:18:22 +01:00
let mut request = Vec ::new ( ) ;
let mut reader = BufReader ::new ( & mut stream ) ;
reader
. read_until ( b '\n' , & mut request )
2021-08-30 17:32:44 +01:00
. expect ( " Failed to read from stream! " ) ;
2021-08-30 17:18:22 +01:00
2021-09-02 16:16:38 +01:00
let resource = process_request ( request ) ;
2021-12-22 20:02:09 +00:00
// Haha, bodged my way around Rust's ownership paradigm!
let mime = detect_media_type ( resource . to_string ( ) ) ;
2021-09-02 16:16:38 +01:00
let contents = get_page ( resource ) ;
2021-12-22 20:02:09 +00:00
let header = " HTTP/1.1 200 OK \r \n " ;
let content_type = format! ( " Content-Type: {} \r \n " , mime ) ;
let server = " Server: Herb/0.1.3 \r \n " ;
let extra_fields = format! ( " {} \r \n " , server ) ;
let response = format! ( " {} {} {} {} " , header , content_type , extra_fields , contents ) ;
2021-08-30 16:17:11 +01:00
2021-08-29 16:07:05 +01:00
stream . write ( response . as_bytes ( ) ) . unwrap ( ) ;
stream . flush ( ) . unwrap ( ) ;
}
fn main ( ) -> std ::io ::Result < ( ) > {
2021-12-22 20:02:09 +00:00
let listen = TcpListener ::bind ( " 0.0.0.0:80 " ) ? ;
2021-08-29 16:07:05 +01:00
for stream in listen . incoming ( ) {
2021-08-30 17:18:22 +01:00
println! ( " Serving incoming stream. " ) ;
2021-08-29 16:07:05 +01:00
serve ( stream ? ) ;
}
Ok ( ( ) )
2021-08-29 15:18:20 +01:00
}