2024-01-14 19:38:38 +00:00
use std ::io ::{ Write , BufReader , BufRead , Read } ;
2021-12-24 21:30:39 +00:00
use std ::thread ;
2021-08-29 16:07:05 +01:00
use std ::net ::{ TcpListener , TcpStream } ;
2021-08-30 16:17:11 +01:00
use std ::fs ;
2024-01-14 19:38:38 +00:00
use std ::fs ::File ;
2021-08-30 16:17:11 +01:00
use std ::string ::{ String } ;
2021-12-22 20:30:53 +00:00
use std ::process ::Command ;
2023-10-19 11:24:36 +01:00
use packeteer ::http1 ::* ;
2024-01-15 16:36:11 +00:00
use packeteer ::{ generate_kvheader , unwrap_url_into_segments } ;
2023-10-19 11:24:36 +01:00
struct Resource {
2024-01-14 19:38:38 +00:00
contents : Vec < u8 > ,
2023-10-19 11:24:36 +01:00
status_code : i32 ,
mime : String ,
2024-01-15 14:47:25 +00:00
iscgi : bool ,
2023-10-19 11:24:36 +01:00
}
2021-08-30 16:17:11 +01:00
2024-01-14 19:38:38 +00:00
struct GetPageResult {
is500 : bool ,
2024-01-15 14:47:25 +00:00
is502 : bool ,
2024-01-14 19:38:38 +00:00
contents : Vec < u8 > ,
2024-01-15 14:47:25 +00:00
iscgi : bool ,
2024-01-14 19:38:38 +00:00
}
2024-01-15 14:47:25 +00:00
fn process_cgi ( filename : String ) -> Option < Vec < u8 > > {
2021-12-26 17:29:34 +00:00
// This is gonna be the boggiest implementation of CGI that anyone
// has ever seen in the history of the fucking world
let query ;
let script ;
if filename . contains ( " ? " ) {
let vars = filename . to_string ( ) . find ( " ? " ) . unwrap ( ) ;
let vars1 = vars + 1 ;
query = & filename [ vars1 .. ] ;
script = & filename [ .. vars ] ;
} else {
query = " " ;
script = & filename ;
}
2022-09-17 09:37:42 +01:00
let output ;
if query ! = " " {
output = Command ::new ( format! ( " ./ {} " , script ) )
. env ( " GATEWAY_INTERFACE " , " CGI/1.1 " )
2023-10-19 11:24:36 +01:00
. env ( " SERVER_SOFTWARE " , format! ( " herb/ {} " , env! ( " CARGO_PKG_VERSION " ) ) )
2022-09-17 09:37:42 +01:00
. env ( " REQUEST_METHOD " , " GET " )
. env ( " QUERY_STRING " , query )
. env ( " SCRIPT_NAME " , script )
2024-01-15 14:47:25 +00:00
. output ( ) ;
2022-09-17 09:37:42 +01:00
} else {
output = Command ::new ( format! ( " ./ {} " , script ) )
. env ( " GATEWAY_INTERFACE " , " CGI/1.1 " )
2023-10-19 11:24:36 +01:00
. env ( " SERVER_SOFTWARE " , format! ( " herb/ {} " , env! ( " CARGO_PKG_VERSION " ) ) )
2022-09-17 09:37:42 +01:00
. env ( " REQUEST_METHOD " , " GET " )
. env ( " SCRIPT_NAME " , script )
2024-01-15 14:47:25 +00:00
. output ( ) ;
}
if output . is_ok ( ) {
return Some ( output . unwrap ( ) . stdout ) ;
} else {
return None ;
2022-09-17 09:37:42 +01:00
}
2021-12-26 17:29:34 +00:00
}
2024-01-15 16:03:28 +00:00
fn process_cgi_with_path ( filename : String , segments : Vec < String > , location : i32 ) -> Option < Vec < u8 > > {
2024-01-15 16:36:11 +00:00
let mut query : String = " " . to_string ( ) ;
2024-01-15 16:03:28 +00:00
let script = filename ;
let mut i = 0 ;
let mut path = " " . to_string ( ) ;
for seg in segments {
2024-01-15 16:36:11 +00:00
if ! ( i < = location ) & & ! ( seg . contains ( " ? " ) ) {
2024-01-15 16:03:28 +00:00
if path = = " " . to_string ( ) {
path = format! ( " / {} " , seg ) ;
} else {
path = format! ( " {} / {} " , path , seg ) ;
}
}
2024-01-15 16:36:11 +00:00
if ! ( i < = location ) & & seg . contains ( " ? " ) {
let split : Vec < & str > = seg . as_str ( ) . split ( " ? " ) . collect ( ) ;
query = split [ split . len ( ) - 1 ] . to_string ( ) ;
path = format! ( " {} / {} " , path , split [ 0 ] . to_string ( ) ) ;
}
2024-01-15 16:03:28 +00:00
i = i + 1 ;
}
path = format! ( " {} / " , path ) ;
2024-01-15 16:36:11 +00:00
println! ( " {} " , path ) ;
2024-01-15 16:03:28 +00:00
let output ;
if query ! = " " . to_string ( ) {
output = Command ::new ( format! ( " ./ {} " , script ) )
. env ( " GATEWAY_INTERFACE " , " CGI/1.1 " )
. env ( " SERVER_SOFTWARE " , format! ( " herb/ {} " , env! ( " CARGO_PKG_VERSION " ) ) )
. env ( " REQUEST_METHOD " , " GET " )
. env ( " QUERY_STRING " , query )
. env ( " PATH_INFO " , path )
. env ( " SCRIPT_NAME " , script )
. output ( ) ;
} else {
output = Command ::new ( format! ( " ./ {} " , script ) )
. env ( " GATEWAY_INTERFACE " , " CGI/1.1 " )
. env ( " SERVER_SOFTWARE " , format! ( " herb/ {} " , env! ( " CARGO_PKG_VERSION " ) ) )
. env ( " REQUEST_METHOD " , " GET " )
. env ( " PATH_INFO " , path )
. env ( " SCRIPT_NAME " , script )
. output ( ) ;
}
if output . is_ok ( ) {
return Some ( output . unwrap ( ) . stdout ) ;
} else {
return None ;
}
}
2021-12-22 20:02:09 +00:00
fn detect_media_type ( filename : String ) -> String {
// The Lynx terminal browser made me do this.
2021-12-26 17:29:34 +00:00
let path = filename . to_string ( ) ;
let paths = check_if_path_exists ( path ) ;
let path = filename . to_string ( ) ;
let dir = check_if_dir ( path ) ;
if paths = = true & & dir = = false & & filename . contains ( " . " ) = = false {
return " text/html " . to_string ( ) ;
} else {
2024-01-15 10:20:23 +00:00
let ext_index = filename . as_str ( ) . split ( " . " ) . collect ::< Vec < & str > > ( ) ;
let ext = ext_index [ ext_index . len ( ) - 1 ] ;
2021-12-26 17:29:34 +00:00
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 ( ) ,
" cgi " = > " text/html " . to_string ( ) ,
" .error_server_404 " = > " text/html " . to_string ( ) ,
2023-10-19 11:24:36 +01:00
" .error_server_501 " = > " text/html " . to_string ( ) ,
2021-12-26 17:29:34 +00:00
_ = > " application/octet-stream " . to_string ( ) ,
}
2021-12-22 20:02:09 +00:00
}
}
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 " ;
2023-10-20 12:32:41 +01:00
let result = check_if_path_exists ( path ) ;
2021-09-02 18:13:37 +01:00
return result ;
}
2021-09-05 16:16:07 +01:00
fn generate_index ( directory : String ) -> String {
2023-10-20 12:32:41 +01:00
let mut index = format! ( " <!DOCTYPE HTML><html><body><h1>Directory of {} </h1><hr/><a href= \" / {} /../ \" >Parent Directory</a><br/> " , directory , directory ) ;
2021-09-05 16:16:07 +01:00
for file in fs ::read_dir ( directory ) . unwrap ( ) {
2023-10-20 12:32:41 +01:00
index = format! ( " {} <a href= {} > {} </a><br/> " , index , format! ( " \" / {} \" " , file . as_ref ( ) . unwrap ( ) . path ( ) . display ( ) . to_string ( ) ) , file . unwrap ( ) . file_name ( ) . into_string ( ) . unwrap_or ( " !! " . to_string ( ) ) ) ;
2021-09-05 16:16:07 +01:00
}
2024-01-15 10:20:23 +00:00
return format! ( " {} <hr/><i>Generated by herb/ {} </i> " , index , env! ( " CARGO_PKG_VERSION " ) ) . to_string ( ) ;
2021-09-05 16:16:07 +01:00
}
2024-01-14 19:38:38 +00:00
fn get_page ( filename : String ) -> GetPageResult {
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 ( ) ;
2023-10-19 11:24:36 +01:00
let checks = check_if_path_exists ( path . clone ( ) ) ;
let index = check_if_dir ( path . clone ( ) ) ;
2021-09-05 16:16:07 +01:00
2024-01-15 14:47:25 +00:00
let mut resultstruct = GetPageResult { is500 : false , is502 : false , contents : vec ! [ ] , iscgi : false } ;
2024-01-14 19:38:38 +00:00
2021-09-05 16:16:07 +01:00
println! ( " {} {} {} " , path , checks , index ) ;
if checks = = true & & index = = false {
2021-12-26 17:29:34 +00:00
if path . contains ( " . " ) ! = true & & path . contains ( " .cgi? " ) ! = true {
2021-09-05 16:16:07 +01:00
let result = generate_index ( filename ) ;
2024-01-14 19:38:38 +00:00
resultstruct . contents = result . to_string ( ) . into_bytes ( ) ;
return resultstruct ;
2021-12-26 17:29:34 +00:00
} else if path . contains ( " .cgi? " ) {
let result = process_cgi ( filename ) ;
2024-01-15 14:47:25 +00:00
if result . is_some ( ) {
resultstruct . contents = result . unwrap ( ) ;
resultstruct . iscgi = true ;
return resultstruct ;
} else {
resultstruct . contents = " <!DOCTYPE HTML><html><body><h1>500 Internal Server Error</h1><p>The resource you are trying to access cannot be read by the server.</p></body></html> " . to_string ( ) . into_bytes ( ) ;
resultstruct . is500 = true ;
return resultstruct ;
}
2021-09-05 16:16:07 +01:00
}
}
2023-10-19 11:24:36 +01:00
if filename . contains ( " .cgi " ) {
2021-12-26 17:29:34 +00:00
let result = process_cgi ( filename ) ;
2024-01-15 14:47:25 +00:00
if result . is_some ( ) {
resultstruct . contents = result . unwrap ( ) ;
resultstruct . iscgi = true ;
return resultstruct ;
} else {
resultstruct . contents = " <!DOCTYPE HTML><html><body><h1>502 Bad Gateway</h1><p>The server is unable to complete your request due to an error.</p></body></html> " . to_string ( ) . into_bytes ( ) ;
resultstruct . is502 = true ;
return resultstruct ;
}
2021-09-02 17:25:53 +01:00
} else {
2024-01-15 10:39:14 +00:00
let f = File ::open ( filename . clone ( ) ) ;
2024-01-14 19:38:38 +00:00
if f . is_ok ( ) {
let mut buf : Vec < u8 > = vec! [ ] ;
let result = f . unwrap ( ) . read_to_end ( & mut buf ) ;
if result . is_ok ( ) {
resultstruct . contents = buf ;
return resultstruct ;
} else {
2024-01-15 14:47:25 +00:00
resultstruct . contents = " <!DOCTYPE HTML><html><body><h1>502 Bad Gateway</h1><p>The server is unable to complete your request due to an error.</p></body></html> " . to_string ( ) . into_bytes ( ) ;
resultstruct . is502 = true ;
2024-01-14 19:38:38 +00:00
return resultstruct ;
}
} else {
2024-01-15 10:39:14 +00:00
if filename = = " index.html " {
let newresult = generate_index ( " . " . to_string ( ) ) ;
resultstruct . contents = newresult . to_string ( ) . into_bytes ( ) ;
return resultstruct ;
}
2024-01-14 19:38:38 +00:00
resultstruct . contents = " <!DOCTYPE HTML><html><body><h1>500 Internal Server Error</h1><p>The resource you are trying to access cannot be read by the server.</p></body></html> " . to_string ( ) . into_bytes ( ) ;
resultstruct . is500 = true ;
return resultstruct ;
2021-09-02 17:25:53 +01:00
}
2021-08-30 17:32:44 +01:00
}
2023-10-20 12:32:41 +01:00
}
2021-08-29 16:07:05 +01:00
2021-12-22 20:30:53 +00:00
fn grab_time ( ) -> String {
let output = Command ::new ( " date " )
. arg ( " +'%a, %d %b %Y %H:%m:%S %Z' " )
. output ( )
. expect ( " failed to execute process " ) ;
2023-10-19 11:24:36 +01:00
return String ::from_utf8_lossy ( & output . stdout ) . to_string ( ) . replace ( " ' " , " " ) . replace ( " \n " , " " ) ;
2021-12-22 20:30:53 +00:00
}
2023-10-20 12:32:41 +01:00
fn process_request ( request : Vec < u8 > ) -> Resource {
let input = String ::from_utf8_lossy ( & request ) . to_string ( ) ;
2024-01-15 15:13:02 +00:00
let prerequest = construct_request ( & input ) ;
let request ;
if prerequest . is_some ( ) {
request = prerequest . unwrap ( ) ;
} else {
let resource = Resource { contents : " <!DOCTYPE HTML><html><body><h1>400 Bad Request</h1><p>The request you sent appears to be malformed.</p></body></html> " . to_string ( ) . into_bytes ( ) , status_code : 400 , mime : " text/html " . to_string ( ) , iscgi : false } ;
return resource ;
}
2023-10-20 12:32:41 +01:00
let mut path : String ;
2021-12-22 20:02:09 +00:00
2021-09-02 18:13:37 +01:00
let mut index = String ::new ( ) ;
2021-09-02 16:18:23 +01:00
let output ;
2023-10-20 12:32:41 +01:00
if & request . method = = " GET " {
2021-09-02 17:28:07 +01:00
println! ( " Stream sent GET request. " ) ;
2023-10-20 12:32:41 +01:00
if request . location . segments . len ( ) ! = 0 {
2024-01-15 16:03:28 +00:00
let segclone = request . location . segments . clone ( ) ;
2023-10-20 12:32:41 +01:00
path = unwrap_url_into_segments ( request . location ) ;
2024-01-15 16:03:28 +00:00
if path . contains ( " .cgi/ " ) {
let mut cgiscript = 0 ;
let mut i = 0 ;
let mut cgipath : Vec < String > = vec! [ ] ;
for segment in & segclone {
if segment . contains ( " .cgi " ) {
cgipath . push ( segment . to_string ( ) ) ;
cgiscript = i ;
break ;
} else {
cgipath . push ( segment . to_string ( ) ) ;
i = i + 1 ;
}
}
let mut cgipathraw = " " . to_string ( ) ;
for seg in cgipath {
if cgipathraw = = " " . to_string ( ) {
cgipathraw = seg ;
} else {
cgipathraw = format! ( " {} / {} " , cgipathraw , seg ) ;
}
}
let contents = process_cgi_with_path ( cgipathraw , segclone , cgiscript ) ;
if contents . is_some ( ) {
2024-01-15 16:36:11 +00:00
let resource = Resource { contents : contents . unwrap ( ) , status_code : 200 , mime : " text/html " . to_string ( ) , iscgi : true } ;
2024-01-15 16:03:28 +00:00
return resource ;
} else {
let newcontents = " <!DOCTYPE HTML><html><body><h1>502 Bad Gateway</h1><p>The server is unable to complete your request due to an error.</p></body></html> " . to_string ( ) . into_bytes ( ) ;
2024-01-15 16:36:11 +00:00
let resource = Resource { contents : newcontents , status_code : 502 , mime : " text/html " . to_string ( ) , iscgi : false } ;
2024-01-15 16:03:28 +00:00
return resource ;
}
}
2023-10-20 12:32:41 +01:00
path . remove ( 0 ) ;
let exists = check_if_path_exists ( path . clone ( ) ) ;
2021-12-26 17:29:34 +00:00
if exists = = false & & path . contains ( " .cgi? " ) = = false {
2024-01-15 14:47:25 +00:00
let resource = Resource { contents : " <!DOCTYPE HTML><html><body><h1>404 Not Found</h1><p>The resource you are trying to locate cannot be accessed!</p></body></html> " . to_string ( ) . into_bytes ( ) , status_code : 404 , mime : " text/html " . to_string ( ) , iscgi : false } ;
2023-10-19 11:24:36 +01:00
return resource ;
2021-09-02 18:13:37 +01:00
} else {
2023-10-20 12:32:41 +01:00
let dir = check_if_dir ( path . clone ( ) ) ;
2021-09-02 18:13:37 +01:00
if dir = = true {
index + = & path ;
index + = " /index.html " ;
2024-01-15 16:36:11 +00:00
output = index . as_str ( ) ;
2021-09-02 18:13:37 +01:00
} else {
2024-01-15 16:36:11 +00:00
output = path . as_str ( ) ;
2021-09-02 18:13:37 +01:00
}
}
2021-09-02 17:25:53 +01:00
} else {
output = " index.html " ;
2021-09-02 16:16:38 +01:00
}
} else {
2024-01-15 14:47:25 +00:00
let resource = Resource { contents : " <!DOCTYPE HTML><html><body><h1>501 Not Implemented</h1><p>The request sent by your web browser cannot be handled by this server software.</p></body></html> " . to_string ( ) . into_bytes ( ) , status_code : 501 , mime : " text/html " . to_string ( ) , iscgi : false } ;
2021-09-02 18:13:37 +01:00
println! ( " Stream sent unhandlable request. " ) ;
2023-10-19 11:24:36 +01:00
return resource ;
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()?
2024-01-14 19:38:38 +00:00
let rescontents = get_page ( output . to_string ( ) ) ;
2024-01-15 14:47:25 +00:00
let mut resource = Resource { contents : rescontents . contents , status_code : 200 , mime : detect_media_type ( output . split ( " ? " ) . collect ::< Vec < & str > > ( ) [ 0 ] . to_string ( ) ) , iscgi : false } ;
2024-01-14 19:38:38 +00:00
if rescontents . is500 {
2023-10-19 11:24:36 +01:00
resource . status_code = 500 ;
resource . mime = " text/html " . to_string ( ) ;
}
2024-01-15 14:47:25 +00:00
if rescontents . is502 {
resource . status_code = 502 ;
resource . mime = " text/html " . to_string ( ) ;
}
if rescontents . iscgi {
resource . iscgi = true ;
}
2023-10-19 11:24:36 +01:00
return resource ;
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-12-24 21:30:39 +00:00
thread ::spawn ( move | | {
println! ( " Stream thread created. " ) ;
let mut request = Vec ::new ( ) ;
let mut reader = BufReader ::new ( & mut stream ) ;
reader
. read_until ( b '\n' , & mut request )
. expect ( " Failed to read from stream! " ) ;
2021-12-22 20:02:09 +00:00
2023-10-20 12:32:41 +01:00
let resource = process_request ( request ) ;
2021-12-22 20:02:09 +00:00
2024-01-14 19:38:38 +00:00
let mut response_constructed = generate_response ( resource . status_code , " " ) ;
2023-10-19 11:24:36 +01:00
response_constructed . headers . push ( generate_kvheader ( " Content-Type " , & resource . mime ) ) ;
response_constructed . headers . push ( generate_kvheader ( " Server " , & format! ( " herb/ {} " , env! ( " CARGO_PKG_VERSION " ) ) ) ) ;
2021-12-22 20:38:36 +00:00
2021-12-24 21:30:39 +00:00
if cfg! ( unix ) {
2023-10-19 11:24:36 +01:00
response_constructed . headers . push ( generate_kvheader ( " Date " , & grab_time ( ) ) )
2021-12-24 21:30:39 +00:00
}
2023-10-20 12:32:41 +01:00
2024-01-15 14:47:25 +00:00
if resource . iscgi {
2024-01-15 16:36:11 +00:00
let header = " HTTP/1.1 200 OK \r \n " . to_string ( ) . into_bytes ( ) ;
2024-01-15 14:47:25 +00:00
stream . write ( header . as_slice ( ) ) . unwrap ( ) ;
stream . write ( & resource . contents ) . unwrap ( ) ;
stream . flush ( ) . unwrap ( ) ;
return ;
}
2024-01-14 19:38:38 +00:00
let mut contents_clone = resource . contents . clone ( ) ;
let mut unpacked_response = unpack_response ( response_constructed ) . into_bytes ( ) ;
unpacked_response . append ( & mut contents_clone ) ;
stream . write ( unpacked_response . as_slice ( ) ) . unwrap ( ) ;
2021-12-24 21:30:39 +00:00
stream . flush ( ) . unwrap ( ) ;
} ) ;
2021-08-29 16:07:05 +01:00
}
fn main ( ) -> std ::io ::Result < ( ) > {
2021-12-22 20:30:53 +00:00
let listen = TcpListener ::bind ( " 0.0.0.0:8080 " ) ? ;
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
}