mod config; mod monolith; mod render; use std::env; use std::collections::hash_map::{HashMap, RandomState}; enum CGIStatusCode { S200, C400, C404, C405 } type CGIHTTPHeaders = Vec<(String, String)>; struct CGIResponse { status: CGIStatusCode, headers: CGIHTTPHeaders, body: String } fn cgi_handle_request(conf: &config::Config) -> Result { let headers = vec![ (String::from("Allow"), String::from("GET")), (String::from("Content-type"), String::from("text/html")) ]; let mkerr = |status| CGIResponse { status, headers: headers.clone(), body: String::from("") }; let request_method = env::var("REQUEST_METHOD").map_err(|_| mkerr(CGIStatusCode::C400))?; if request_method != "GET" { return Err(mkerr(CGIStatusCode::C405)); } let query: HashMap<_, _, RandomState> = if let Ok(query_string) = env::var("QUERY_STRING") { if query_string.len() > 0 { HashMap::from_iter(query_string.split('&').map(|qi| { let (k, v) = config::split_at_first(qi, "="); (String::from(k), String::from(v)) })) } else { HashMap::from([(String::from("page"), String::from("1"))]) } } else { HashMap::from([(String::from("page"), String::from("1"))]) }; let m = monolith::Monolith::new(String::from("posts.monolith")); if let Some(ps) = query.get("page") { let p = usize::from_str_radix(&ps, 10).map_err(|_| mkerr(CGIStatusCode::C400))?.checked_sub(1).ok_or(mkerr(CGIStatusCode::C404))?; let ps = m.get_page_posts(p).ok_or(mkerr(CGIStatusCode::C404))?; let r = render::Renderer::load("./template"); return Ok(CGIResponse { status: CGIStatusCode::S200, headers, body: r.render_page(ps, p, m.get_page_count(), conf) }); } else if let Some(ds) = query.get("post") { let d = i64::from_str_radix(&ds, 10).map_err(|_| mkerr(CGIStatusCode::C400))?; let p = m.get_post_2(d).ok_or(mkerr(CGIStatusCode::C404))?; let r = render::Renderer::load("./template"); return Ok(CGIResponse { status: CGIStatusCode::S200, headers, body: r.render_single_post(p, conf) }); } Err(mkerr(CGIStatusCode::C400)) } fn cgimain(conf: config::Config) -> Result<(), &'static str> { let r = match cgi_handle_request(&conf) { Ok(r) => r, Err(r) => r }; let (status, status_str) = match r.status { CGIStatusCode::S200 => (200, "OK"), CGIStatusCode::C400 => (400, "Bad Request"), CGIStatusCode::C404 => (404, "Not Found"), CGIStatusCode::C405 => (405, "Method Not Allowed") }; print!("Status: {} {}\r\n", status, status_str); r.headers.iter().for_each(|(f, v)| print!("{}: {}\r\n", f, v)); print!("\r\n"); if status < 400 { print!("{}", r.body); } else { let rdr = render::Renderer::load("./template"); print!("{}", rdr.render_error(status, String::from(status_str), &conf)); } Ok(()) } fn dbgmain(conf: config::Config) -> Result<(), &'static str> { eprintln!("in debug mode"); eprintln!("notekins version {}", conf.get("VERSION_STRING")); let mut m = monolith::Monolith::new(String::from("posts.monolith")); let mut args = env::args(); args.next(); if let Some(dbgop) = args.next() { match dbgop.as_str() { "get_post" => { let tss = args.next().ok_or("missing timestamp")?; let ts = i64::from_str_radix(&tss, 10).map_err(|_| "invalid timestamp")?; m.load_index(); let p = m.get_post(ts).ok_or("post not found")?; monolith::test_print_post(&p); Ok(()) }, "get_post2" => { let tss = args.next().ok_or("missing timestamp")?; let ts = i64::from_str_radix(&tss, 10).map_err(|_| "invalid timestamp")?; let p = m.get_post_2(ts).ok_or("post not found")?; monolith::test_print_post(&p); Ok(()) }, "get_page" => { let pgs = args.next().ok_or("missing page")?; let pg = usize::from_str_radix(&pgs, 10).map_err(|_| "invalid page")?; let ps = m.get_page_posts(pg).ok_or("page out of range")?; for p in ps { monolith::test_print_post(&p); } Ok(()) }, "render_page" => { let pgs = args.next().ok_or("missing page")?; let pg = usize::from_str_radix(&pgs, 10).map_err(|_| "invalid page")?; let ps = m.get_page_posts(pg).ok_or("page out of range")?; let r = render::Renderer::load("./template"); println!("{}", r.render_page(ps, pg, m.get_page_count(), &conf)); Ok(()) }, "render_post" => { let tss = args.next().ok_or("missing timestamp")?; let ts = i64::from_str_radix(&tss, 10).map_err(|_| "invalid timestamp")?; let p = m.get_post_2(ts).ok_or("post not found")?; let r = render::Renderer::load("./template"); println!("{}", r.render_single_post(p, &conf)); Ok(()) }, _ => Err("unsupported debug option") } } else { m.load_index(); let dates = m.get_all_dates(); for d in dates { let p = m.get_post(d); println!("{:?}", p) } Ok(()) } } fn main() -> Result<(), &'static str> { let conf = config::Config::parse_config("notekins.conf"); if let Ok(_) = env::var("SERVER_SOFTWARE") { cgimain(conf) } else { dbgmain(conf) } }