aboutsummaryrefslogtreecommitdiffstats
path: root/src/main.rs
blob: 11758188a043945266398f1acd61311d519ba33b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
use std::io::{Write, BufReader, BufRead};
use std::thread;
use std::net::{TcpListener, TcpStream};
use std::fs;
use std::string::{String};
use std::process::Command;

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(),
	}

}

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;
}

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();

}

fn get_page(filename: String) -> String {

	// The loaded page should be left immutable as it does
	// not need to be modified by the server.

	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();
		}
	}

	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" {
		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(),
			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(),
		}
	
	}
} 

fn grab_time() -> String{
	let output = Command::new("date")
				.arg("+'%a, %d %b %Y %H:%m:%S %Z'")
				.output()
				.expect("failed to execute process");

	return String::from_utf8_lossy(&output.stdout).to_string();
}


fn process_request(request: Vec<u8>) -> String { 
	let mut input = String::from_utf8_lossy(&request).to_string();

	let debug = false;

	let mut index = String::new();
	let output;
	if input.contains("GET") {		
		// To-do: find a less atrocious way to do this.
 		println!("Stream sent GET request.");
		
		if debug { println!("{}", input); }
		input = input.replace("GET ", "");
		input = input.replace(" HTTP/1.1\r\n", "");

		// Lynx also made me do this
		input = input.replace(" HTTP/1.0\r\n", "");

		// 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.

		if input != "/" {
			let mut chars = input.chars();
			chars.next();
			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();
				}
			}
		} else {
			output = "index.html";
		}


	} else {
		// It's get_page()'s problem now.
		println!("Stream sent unhandlable request.");
		output = ".error_server_503";
	}

	// Did you want to see chars.as_str().to_string()?
	return output.to_string();

}

fn serve(mut stream: TcpStream) {
	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!");
	

		let resource = process_request(request);

		// Haha, bodged my way around Rust's ownership paradigm!
		let mime = detect_media_type(resource.to_string());

		let contents = get_page(resource);
		let header = "HTTP/1.1 200 OK\r\n";
		let content_type = format!("Content-Type: {}\r\n", mime);
		let server = "Server: Herb/0.2.0\r\n";	
		let extra_fields;

		if cfg!(unix) {
			let mut time = format!("Date: {}\r\n", grab_time());
			time = time.replace("'","");
			extra_fields = format!("{}{}\r\n", server, time);
		} else {
			// I don't have a Windows or macOS box to test anything on
			// which means others are gonna have to deal with it :/

			extra_fields = format!("{}\r\n", server);
		}

		let response = format!("{}{}{}{}", header, content_type, extra_fields, contents);
	
		stream.write(response.as_bytes()).unwrap();
		stream.flush().unwrap();
	});
}


fn main() -> std::io::Result<()> {
	let listen = TcpListener::bind("0.0.0.0:8080")?;

	for stream in listen.incoming() {
		println!("Serving incoming stream.");
		serve(stream?);
	}
	Ok(())
}