aboutsummaryrefslogtreecommitdiffstats
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs165
1 files changed, 165 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..7a60081
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,165 @@
+//use std::io::Write;
+use dumb_cgi::{Request, EmptyResponse, Query};
+use postgres::{Client, NoTls};
+use std::process::exit;
+use regex::Regex;
+use dotenv;
+use url::Url;
+use rand::{thread_rng, Rng};
+use rand::distributions::Alphanumeric;
+
+
+// Get URL from database
+fn get_url(dburl:String, short: &str) -> Result<String, postgres::Error> {
+ // Connect to db
+ let mut client = Client::connect(&dburl, NoTls)?;
+
+ let row = client.query_one("SELECT url FROM shorts WHERE short = $1 LIMIT 1", &[&short])?;
+
+ if row.len() == 1 {
+ client.execute("UPDATE shorts SET count = count + 1, last_visited = now() WHERE short = $1;", &[&short])?;
+ }
+ client.close()?;
+
+ let url: String = row.get("url");
+
+ Ok(url)
+}
+
+fn insert_short(dburl:String, url: &str, short: &str, user: &str) -> Result<u64, postgres::Error> {
+ let mut client = Client::connect(&dburl, NoTls)?;
+
+ let n = client.execute("INSERT INTO shorts (url, short, created_by) VALUES ($1, $2, $3);", &[&url, &short, &user])?;
+ client.close()?;
+ Ok(n)
+}
+// Do the redirect
+// return std::io::Result<()> so we can use ? -
+// https://doc.rust-lang.org/std/result/#the-question-mark-operator-
+fn redirect(code:u16, url:&str) {
+ EmptyResponse::new(code)
+ .with_header("Location", url)
+ .with_content_type("text/plain")
+ .with_body(
+ format!("Redirecting to {}\n", url))
+ .respond().unwrap();
+ exit(0);
+}
+
+fn error(code:u16, msg:&str) {
+ EmptyResponse::new(code)
+ .with_content_type("text/plain")
+ .with_body(
+ format!("{} {}\n", code, msg))
+ .respond().unwrap();
+ exit(0);
+}
+
+// Print form
+fn print_form() {
+ let html = r#"<html>
+<head>
+ <title>PURL</title>
+</head>
+<body>
+ <form method="post" action="/purl.cgi" enctype="multipart/form-data" name="main">
+ Username: $user<br>
+ URL to shorten: <input type="text" name="url" size="50"><br>
+ Custom short: <input type="text" name="short"><br>
+ <input type="hidden" name="form" value="html">
+ <input type="submit" value="Submit">
+ </form>
+
+</body>
+</html>
+HTML
+"#;
+
+ EmptyResponse::new(200)
+ .with_content_type("text/html")
+ .with_body(html)
+ .respond().unwrap();
+ exit(0);
+}
+
+
+// Generate random short
+fn gen_short() -> String {
+ let rand_string: String = thread_rng()
+ .sample_iter(&Alphanumeric)
+ .take(thread_rng().gen_range(2..6))
+ .map(char::from)
+ .collect();
+
+ return rand_string;
+}
+
+
+
+// Do the dirty
+fn main() -> std::io::Result<()> {
+ // Get dburl from .env-file
+ let dburl = dotenv::var("DATABASE_URL").unwrap();
+ let prefix = dotenv::var("PREFIX").unwrap();
+
+ // Gather all request data from the environment and stdin.
+ let req = Request::new().unwrap();
+
+ // Find out what action we're performing
+ let action:&str = req.var("ACTION").unwrap_or("none");
+
+ match action {
+ "form" => print_form(),
+
+ "redirect" => {
+ // get DOCUMENT_URI
+ let docuri:&str = req.var("DOCUMENT_URI").unwrap_or("none");
+ // Trim / and + from start
+ let docuri = docuri.trim_start_matches(&['/', '+']);
+
+ // Make SURE docuri is a valid short
+ let shortregex = Regex::new(r"^[a-zA-Z0-9_-]+$").unwrap();
+ if !shortregex.is_match(docuri) {
+ error(400, "Bad Request");
+ }
+
+ // Fetch URL from postgres, and redirect or return 404
+ match get_url(dburl, docuri) {
+ Ok(url) => redirect(301, &url),
+ Err(_e) => error(404, "Not Found"),
+ };
+ },
+
+ "create" => {
+ match req.query() {
+ Query::None => redirect(303, "/purl.cgi"),
+ Query::Some(map) => {
+ if map.iter().count() != 1 {
+ error(400, "Bad Request\n\nIncorrect number of query items");
+ }
+ let url:&str = &map["url"];
+ if !Url::parse(url).is_ok() {
+ error(400, "Bad Request\n\nInvalid url");
+ }
+
+ let user:&str = req.var("REMOTE_USER").unwrap_or("none");
+ let short:&str = &gen_short();
+ let res:String = format!("{}{}", prefix, short);
+ if insert_short(dburl, url, short, user).unwrap() > 0 {
+ error(200, &res);
+ } else {
+ error(400, "Bad Request\n\nDunno wtf happened");
+ }
+ exit(0);
+
+ },
+ Query::Err(_e) => error(400,"Bad Request\n\nError reading query string"),
+ }
+ },
+
+ _ => error(400, "Bad Request"),
+ }
+
+
+ Ok(())
+}