Skip to content

Commit

Permalink
refactor: formatting main.rs
Browse files Browse the repository at this point in the history
  • Loading branch information
Hoverth committed Nov 18, 2024
1 parent 259a908 commit 4819bb7
Showing 1 changed file with 134 additions and 67 deletions.
201 changes: 134 additions & 67 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::{
collections::HashMap,
fmt,
fs::{self, File},
io::{ Cursor, Write },
time::SystemTime
};
use serde::{ Deserialize, Serialize };
use random_string::generate;
use tiny_http::{ Header, Server, StatusCode, Response, Request };
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
fmt,
fs::{self, File},
io::{Cursor, Write},
time::SystemTime,
};
use tiny_http::{Header, Request, Response, Server, StatusCode};
use urlencoding::decode;

const LETTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
Expand All @@ -25,7 +25,15 @@ struct Config {

impl fmt::Display for Config {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Port: {}\nURL path: {:?}, Links URL path: {:?}\n{} entries, {}llowing new entries", self.port, self.url_path, self.link_path, self.entries_len, if self.allow_new {"A"} else {"Not a"} )
write!(
f,
"Port: {}\nURL path: {:?}, Links URL path: {:?}\n{} entries, {}llowing new entries",
self.port,
self.url_path,
self.link_path,
self.entries_len,
if self.allow_new { "A" } else { "Not a" }
)
}
}

Expand All @@ -37,18 +45,18 @@ struct Entry {
delete: Option<u64>,
}

fn main() {
// Default Config
let mut config: Config = Config {
fn main() {
// Default Config
let mut config: Config = Config {
host: String::from("http://localhost:8000"),
url_path: String::from(""),
url_path: String::from(""),
link_path: String::from("links"),
port: 8000,
entries: HashMap::new(), // parse in from toml or something,
entries_len: 0,
allow_new: true,
};

// Read in toml file (if exists)
println!("Reading config toml file...");
if let Ok(file_contents) = fs::read_to_string("ssurlss.toml") {
Expand All @@ -64,12 +72,12 @@ fn main() {
println!("Reading environment variables...");
for (key, value) in std::env::vars() {
match key.as_str() {
"HOST" => { config.host = value },
"URLPATH" => { config.url_path = value },
"LINKPATH" => { config.link_path = value.trim_matches(['/']).to_string() },
"PORT" => { config.port = value.parse().unwrap_or(config.port) },
"ALLOWNEW" => { config.allow_new = true },
"DISALLOWNEW" => { config.allow_new = false },
"HOST" => config.host = value,
"URLPATH" => config.url_path = value,
"LINKPATH" => config.link_path = value.trim_matches(['/']).to_string(),
"PORT" => config.port = value.parse().unwrap_or(config.port),
"ALLOWNEW" => config.allow_new = true,
"DISALLOWNEW" => config.allow_new = false,
_ => {}
}
}
Expand All @@ -88,26 +96,27 @@ fn main() {
} else {
let server = server.unwrap();
println!("Started server on port {}", config.port);

for request in server.incoming_requests() {
print!("{}: {:?}, {:?} // ",
print!(
"{}: {:?}, {:?} // ",
get_now(),
request.method(),
request.url(),
);

let full_url = request.url();
let prefix_no_trail = String::from("/") + &config.url_path;
let prefix = String::from("/") + &config.url_path + "/";

if full_url == prefix || full_url == prefix_no_trail {
println!("200 Index {full_url:?}");
let r = html_resp_incl(include_str!("index.html"));
let _ = request.respond(r);
} else if full_url.starts_with(&prefix) {
let url = full_url.strip_prefix(&prefix).unwrap();
let link_prefix = config.link_path.clone() + "/";

if url.contains("favicon.ico") {
file_resp(include_bytes!("../assets/favicon.ico"), request);
} else if url.starts_with(&link_prefix) {
Expand All @@ -130,7 +139,8 @@ fn main() {
show_404(request);
}
} else if url.starts_with("add") && config.allow_new {
if url.contains('?') { // receiving data to add
if url.contains('?') {
// receiving data to add
let data = url.strip_prefix("add?").unwrap();
let entry = process_entry(&config, data);

Expand All @@ -140,22 +150,29 @@ fn main() {
let _ = request.respond(r);
continue;
}

let name = entry.id.clone();
let _ = config.entries.insert(name.clone(), entry);
config.entries_len += 1;
println!("200 Add (data) {full_url:?}");

write_config(&config);
let template = include_str!("added.html");
let complete_url = if !config.url_path.is_empty() {
format!("{}/{}/{}/{}",config.host.clone(), &config.url_path, &config.link_path, &name)
format!(
"{}/{}/{}/{}",
config.host.clone(),
&config.url_path,
&config.link_path,
&name
)
} else {
format!("{}/{}/{}",config.host.clone(), &config.link_path, &name)
format!("{}/{}/{}", config.host.clone(), &config.link_path, &name)
};
let r = html_resp_incl(&template.replace("{url}", &complete_url));
let _ = request.respond(r);
} else { // give the normal add page
} else {
// give the normal add page
println!("200 Add (page) {full_url:?}");
let r = html_resp_incl(include_str!("add.html"));
let _ = request.respond(r);
Expand All @@ -170,30 +187,42 @@ fn main() {

fn process_entry(config: &Config, data: &str) -> Entry {
let mut entry = Entry {
url: String::new(),
id: generate(<u32 as TryInto<usize>>::try_into((config.entries_len + 1).ilog(52)).unwrap() + 1, LETTERS),
created: get_now(),
delete: None
url: String::new(),
id: generate(
<u32 as TryInto<usize>>::try_into((config.entries_len + 1).ilog(52)).unwrap() + 1,
LETTERS,
),
created: get_now(),
delete: None,
};

let data = data.split("&").map(|x| {
let y = x.split("=").collect::<Vec<_>>();
if y.len() >= 2 {
(y[0], y[1])
} else {("", "")}
}).collect::<Vec<_>>();
let data = data
.split("&")
.map(|x| {
let y = x.split("=").collect::<Vec<_>>();
if y.len() >= 2 {
(y[0], y[1])
} else {
("", "")
}
})
.collect::<Vec<_>>();

for (key, val) in &data {
match *key {
"url" => { if !val.is_empty() {
if let Ok(url) = decode(val) { entry.url = url.into_owned() }
}},
"id" => {
"url" => {
if !val.is_empty() {
if let Ok(url) = decode(val) {
entry.url = url.into_owned()
}
}
}
"id" => {
let val = val.to_string();
if !config.entries.contains_key(&val) && !val.is_empty() {
entry.id = val;
}
},
}
"time" => {
let time = val.to_string();
if !time.is_empty() {
Expand All @@ -202,44 +231,38 @@ fn process_entry(config: &Config, data: &str) -> Entry {
entry.delete = Some(timestamp);
}
}
},
}
_ => {}
}
}

entry
}

fn timestamp_from_str(string: String) -> u64 {
fn timestamp_from_str(string: String) -> u64 {
// in an ideal world, the string should just be the timestamp itself.
let year: u64 = (parse_timeslice(&string, 0, 4) - 1970u64) * 31_557_600u64;
let month: u64 = parse_timeslice(&string, 5, 7) * 2_629_800u64;
let day: u64 = parse_timeslice(&string, 8, 10) * 24u64 * 360u64;
let month: u64 = (parse_timeslice(&string, 5, 7) - 1) * 2_629_800u64;
let day: u64 = (parse_timeslice(&string, 8, 10) - 1) * 24u64 * 360u64;
let hour: u64 = parse_timeslice(&string, 11, 13) * 360u64;
let min: u64 = parse_timeslice(&string, 16, 18) * 60u64;
year + month + day + min + hour
}

/// only works for png images
fn file_resp(file: &[u8], request: Request) {
let response = Response::new(
StatusCode(200),
Vec::new(),
file,
Some(file.len()),
None
);
let response = Response::new(StatusCode(200), Vec::new(), file, Some(file.len()), None);
let _ = request.respond(response);
}

fn redir_resp(to: &str, request: Request) {
let from = request.url();
let response = Response::new(
StatusCode(302),
vec![Header::from_bytes(
&b"Location"[..], to.as_bytes()
).unwrap()],
"redirecting...".as_bytes(), None, None
StatusCode(302),
vec![Header::from_bytes(&b"Location"[..], to.as_bytes()).unwrap()],
"redirecting...".as_bytes(),
None,
None,
);
println!("302 {from:?} -> {to:?}");
let _ = request.respond(response);
Expand All @@ -249,19 +272,21 @@ fn html_resp_incl(content: &str) -> Response<Cursor<Vec<u8>>> {
let mut r = Response::from_string(content);
if let Ok(header) = Header::from_bytes(&b"Content-Type"[..], &b"text/html; charset=UTF-8"[..]) {
r = r.with_header(header);
};
};
r
}

fn parse_timeslice(string: &str, start: usize, end: usize) -> u64 {
if let Ok(timestamp) = string[start..end].parse() {
timestamp
} else { 0u64 }
} else {
0u64
}
}

fn get_now() -> u64 {
match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
Ok(n) => { n.as_secs() },
Ok(n) => n.as_secs(),
Err(e) => panic!("Err: this program doesn't work on time-travelling systems\n{e}"),
}
}
Expand All @@ -278,6 +303,48 @@ fn write_config(config: &Config) {
if let Err(e) = write!(file, "{}", toml::to_string(&config).unwrap()) {
println!("Failed to write config!\n{e}");
};
} else { println!("Failed to open toml config file!"); }
} else {
println!("Failed to open toml config file!");
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_parsing_timeslice_valid() {
let str = "123456789";
let e = parse_timeslice(str, 0, 4);
assert_eq!(1234, e);
}

#[test]
fn test_parsing_timeslice_invalid() {
let str = "e123456789";
let e = parse_timeslice(str, 0, 4);
assert_eq!(0, e);
}

#[test]
fn test_parsing_timeslice_valid_zeroprefix() {
let str = "0123456789";
let e = parse_timeslice(str, 0, 4);
assert_eq!(123, e);
}

#[test]
fn test_timestamp_from_str_zero() {
let str = String::from("1970-01-01T00:00:00");
let e = timestamp_from_str(str);
assert_eq!(0, e)
}

/// Fails, due to the wacky way the timestamp is calculated (specifically months)
#[test]
fn test_timestamp_from_str_2000() {
let str = String::from("2000-05-23T09:15:44");
let e = timestamp_from_str(str);
assert_eq!(959073344, e)
}
}

0 comments on commit 4819bb7

Please sign in to comment.