diff --git a/Cargo.lock b/Cargo.lock index 1a5cb11..2e8ac2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,12 +204,63 @@ dependencies = [ "syn", ] +[[package]] +name = "actix-web-flash-messages" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c89d051c33e7044e8a6e811ed06dda6ce65dfb51a1e379146f723116ca362a" +dependencies = [ + "actix-web", + "anyhow", + "percent-encoding", + "serde", + "serde_json", + "thiserror", + "time", + "tokio", +] + [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "aes-gcm" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.7.6" @@ -245,6 +296,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "anyhow" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9a8f622bcf6ff3df478e9deba3e03e4e04b300f8e6a139e192c05fa3490afc7" + [[package]] name = "askama_escape" version = "0.10.3" @@ -340,6 +397,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + [[package]] name = "clap" version = "3.2.17" @@ -391,7 +457,14 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" dependencies = [ + "aes-gcm", + "base64", + "hkdf", + "hmac", "percent-encoding", + "rand", + "sha2", + "subtle", "time", "version_check", ] @@ -424,6 +497,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -445,6 +527,7 @@ checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -552,6 +635,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "h2" version = "0.3.13" @@ -607,6 +700,24 @@ dependencies = [ "libc", ] +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "0.2.8" @@ -801,6 +912,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "os_str_bytes" version = "6.3.0" @@ -898,6 +1015,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -1103,6 +1232,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9db03534dff993187064c4e0c05a5708d2a9728ace9a8959b77bedf415dac5" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -1143,6 +1283,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + [[package]] name = "syn" version = "1.0.99" @@ -1228,6 +1374,7 @@ version = "0.1.0" dependencies = [ "actix-files", "actix-web", + "actix-web-flash-messages", "clap", "derive_more", "env_logger", @@ -1333,6 +1480,16 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "url" version = "2.2.2" diff --git a/Cargo.toml b/Cargo.toml index 1ad1857..cd34634 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] actix-files = "0.6.2" actix-web = "4.1.0" +actix-web-flash-messages = {version = "0.4", features = ["cookies"]} clap = { version = "3.2.17", features = ["derive"] } derive_more = "0.99.17" env_logger = "0.9.0" diff --git a/src/main.rs b/src/main.rs index 44bfca1..a4ca4ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,18 +2,19 @@ use std::net::SocketAddr; use std::path::PathBuf; -use actix_files::NamedFile; -use actix_web::http::{Method, StatusCode}; -use actix_web::middleware::{self, ErrorHandlers}; -use actix_web::{get, post, web, App, HttpRequest, HttpResponse, HttpServer, Responder}; +use actix_web::{ + http::{Method, StatusCode}, + middleware::{self, ErrorHandlers}, + web, App, HttpServer, +}; use clap::Parser; use handlebars::Handlebars; -use log::info; -use serde::Deserialize; mod errors; +mod main_pages; -use errors::{generic_error, UserError}; +use errors::generic_error; +use main_pages::{mail_post, static_file, template, template_index}; #[derive(Parser, Debug, Clone)] struct Config { @@ -36,94 +37,6 @@ struct SharedData<'a> { config: Config, } -#[get(r"/{filename:.*\.html}")] -async fn template( - shared: web::Data>, - req: HttpRequest, -) -> actix_web::Result { - let path = req - .match_info() - .query("filename") - .strip_suffix(".html") - .expect("only paths with this suffix should get here"); - - if shared.handlebars.has_template(path) { - let body = shared - .handlebars - .render(path, &()) - .map_err(|_| UserError::InternalError)?; - Ok(HttpResponse::Ok().body(body)) - } else { - Err(UserError::NotFound)? - } -} - -#[get(r"/")] -async fn template_index(shared: web::Data>) -> actix_web::Result { - if shared.handlebars.has_template("index") { - let body = shared - .handlebars - .render("index", &()) - .map_err(|_| UserError::InternalError)?; - Ok(HttpResponse::Ok().body(body)) - } else { - Err(UserError::NotFound)? - } -} - -#[get("/{filename:.*[^/]+}")] -async fn static_file( - shared: web::Data>, - req: HttpRequest, -) -> actix_web::Result { - let requested = req.match_info().query("filename"); - - match shared - .config - .template_directory - .join(requested) - .canonicalize() - { - // We only want to serve paths that are both valid *and* in - // the template directory. - // - // i.e., don't serve up /etc/passwd - Ok(path) if path.starts_with(&shared.config.template_directory) => { - let file = NamedFile::open_async(path) - .await - .map_err(|_| UserError::NotFound)?; - Ok(file.use_last_modified(false).respond_to(&req)) - } - // Any other cases should 404 - _ => Err(UserError::NotFound)?, - } -} - -#[derive(Clone, Debug, Deserialize)] -struct Mail { - mail: String, - subject: String, - message: String, -} - -#[post("/mail.html")] -async fn mail_post( - shared: web::Data>, - form: web::Form, -) -> actix_web::Result { - info!("{:?}", form); - - if shared.handlebars.has_template("mail") { - let body = shared - .handlebars - .render("mail", &()) - .map_err(|_| UserError::InternalError)?; - Ok(HttpResponse::Ok().body(body)) - } else { - Err(UserError::InternalError)? - } -} - #[actix_web::main] async fn main() -> Result<(), std::io::Error> { let mut config = Config::parse(); diff --git a/src/main_pages.rs b/src/main_pages.rs new file mode 100644 index 0000000..91cae03 --- /dev/null +++ b/src/main_pages.rs @@ -0,0 +1,97 @@ +use actix_files::NamedFile; +use actix_web::{get, post, web, HttpRequest, HttpResponse, Responder}; +use log::info; +use serde::Deserialize; + +use crate::errors::UserError; +use crate::SharedData; + +#[derive(Clone, Debug, Deserialize)] +pub(crate) struct Mail { + mail: String, + subject: String, + message: String, +} + +#[get(r"/")] +pub(crate) async fn template_index( + shared: web::Data>, +) -> actix_web::Result { + if shared.handlebars.has_template("index") { + let body = shared + .handlebars + .render("index", &()) + .map_err(|_| UserError::InternalError)?; + Ok(HttpResponse::Ok().body(body)) + } else { + Err(UserError::NotFound)? + } +} + +#[get(r"/{filename:.*\.html}")] +pub(crate) async fn template( + shared: web::Data>, + req: HttpRequest, +) -> actix_web::Result { + let path = req + .match_info() + .query("filename") + .strip_suffix(".html") + .expect("only paths with this suffix should get here"); + + if shared.handlebars.has_template(path) { + let body = shared + .handlebars + .render(path, &()) + .map_err(|_| UserError::InternalError)?; + Ok(HttpResponse::Ok().body(body)) + } else { + Err(UserError::NotFound)? + } +} + +#[get("/{filename:.*[^/]+}")] +pub(crate) async fn static_file( + shared: web::Data>, + req: HttpRequest, +) -> actix_web::Result { + let requested = req.match_info().query("filename"); + + match shared + .config + .template_directory + .join(requested) + .canonicalize() + { + // We only want to serve paths that are both valid *and* in + // the template directory. + // + // i.e., don't serve up /etc/passwd + Ok(path) if path.starts_with(&shared.config.template_directory) => { + let file = NamedFile::open_async(path) + .await + .map_err(|_| UserError::NotFound)?; + Ok(file.use_last_modified(false).respond_to(&req)) + } + // Any other cases should 404 + _ => Err(UserError::NotFound)?, + } +} + +#[post("/mail.html")] +pub(crate) async fn mail_post( + shared: web::Data>, + form: web::Form, +) -> actix_web::Result { + info!("{:?}", form); + + if shared.handlebars.has_template("mail") { + let body = shared + .handlebars + .render("mail", &()) + .map_err(|_| UserError::InternalError)?; + Ok(HttpResponse::Ok().body(body)) + } else { + Err(UserError::InternalError)? + } +}