diff --git a/src/counters.rs b/src/counters.rs index 01803ba..fca1552 100644 --- a/src/counters.rs +++ b/src/counters.rs @@ -1,10 +1,12 @@ use lazy_static::lazy_static; -use rocket_prometheus::prometheus; +// use rocket_prometheus::prometheus; use crate::anonymize_utils; use crate::database::types::Email; +// TODO this module is a stub at the moment for rocket 0.5 migration. reintroduce later on? + lazy_static! { static ref KEY_UPLOAD: LabelCounter = LabelCounter::new("hagrid_key_upload", "Uploaded keys", &["result"]); @@ -18,6 +20,7 @@ lazy_static! { LabelCounter::new("hagrid_key_address_unpublished", "Unpublished email addresses", &["domain"]); } +/* pub fn register_counters(registry: &prometheus::Registry) { KEY_UPLOAD.register(registry); @@ -26,6 +29,7 @@ pub fn register_counters(registry: &prometheus::Registry) { KEY_ADDRESS_PUBLISHED.register(registry); KEY_ADDRESS_UNPUBLISHED.register(registry); } +*/ pub fn inc_key_upload(upload_result: &str) { KEY_UPLOAD.inc(&[upload_result]); @@ -47,21 +51,24 @@ pub fn inc_address_unpublished(email: &Email) { } struct LabelCounter { - prometheus_counter: prometheus::IntCounterVec, + // prometheus_counter: prometheus::IntCounterVec, } impl LabelCounter { fn new(name: &str, help: &str, labels: &[&str]) -> Self { - let opts = prometheus::Opts::new(name, help); - let prometheus_counter = prometheus::IntCounterVec::new(opts, labels).unwrap(); - Self { prometheus_counter } + // let opts = prometheus::Opts::new(name, help); + // let prometheus_counter = prometheus::IntCounterVec::new(opts, labels).unwrap(); + // Self { prometheus_counter } + Self { } } + /* fn register(&self, registry: &prometheus::Registry) { registry.register(Box::new(self.prometheus_counter.clone())).unwrap(); } + */ fn inc(&self, values: &[&str]) { - self.prometheus_counter.with_label_values(values).inc(); + // self.prometheus_counter.with_label_values(values).inc(); } } diff --git a/src/main.rs b/src/main.rs index d160b69..20e0037 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -#![feature(proc_macro_hygiene, plugin, decl_macro)] #![recursion_limit = "1024"] #[macro_use] @@ -10,8 +9,6 @@ extern crate serde_derive; #[macro_use] extern crate rocket; -#[macro_use] -extern crate rocket_contrib; #[cfg(test)] extern crate regex; @@ -39,16 +36,7 @@ mod gettext_strings; mod web; mod template_helpers; -fn main() { - if let Err(e) = web::serve() { - eprint!("{}", e); - let mut cause = e.source(); - while let Some(c) = cause { - eprint!(":\n {}", c); - cause = c.source(); - } - eprintln!(); - - ::std::process::exit(2); - } +#[launch] +fn rocket() -> _ { + web::serve().expect("Rocket config must succeed") } diff --git a/src/web/debug_web.rs b/src/web/debug_web.rs index aa578f5..adc466d 100644 --- a/src/web/debug_web.rs +++ b/src/web/debug_web.rs @@ -10,7 +10,7 @@ use crate::database::{Database, KeyDatabase, Query}; #[get("/debug?")] pub fn debug_info( - db: rocket::State, + db: &rocket::State, i18n: I18n, q: String, ) -> MyResponse { diff --git a/src/web/hkp.rs b/src/web/hkp.rs index 7a4c1cc..b44006d 100644 --- a/src/web/hkp.rs +++ b/src/web/hkp.rs @@ -4,11 +4,12 @@ use std::time::SystemTime; use std::collections::HashMap; use rocket::Data; -use rocket::Outcome; +use rocket::form::{Form, ValueField}; +use rocket::outcome::Outcome; use rocket::http::{ContentType, Status}; use rocket::request::{self, Request, FromRequest}; -use rocket::http::uri::Uri; use rocket_i18n::I18n; +use url::percent_encoding::{DEFAULT_ENCODE_SET, utf8_percent_encode}; use crate::database::{Database, Query, KeyDatabase}; use crate::database::types::{Email, Fingerprint, KeyID}; @@ -45,21 +46,17 @@ impl fmt::Display for Hkp { } } -impl<'a, 'r> FromRequest<'a, 'r> for Hkp { +#[async_trait] +impl<'r> FromRequest<'r> for Hkp { type Error = (); - fn from_request(request: &'a Request<'r>) -> request::Outcome { + async fn from_request(request: &'r Request<'_>) -> request::Outcome { use std::str::FromStr; - use rocket::request::FormItems; - let query = request.uri().query().unwrap_or(""); - let fields = FormItems::from(query) - .map(|item| { - let (k, v) = item.key_value(); - - let key = k.url_decode().unwrap_or_default(); - let value = v.url_decode().unwrap_or_default(); - (key, value) + let query = request.uri().query().map(|q| q.as_str()).unwrap_or_default(); + let fields = Form::values(query) + .map(|ValueField { name, value }| { + (name.to_string(), value.to_string()) }) .collect::>(); @@ -78,30 +75,30 @@ impl<'a, 'r> FromRequest<'a, 'r> for Hkp { (search.starts_with("0x") && search.len() < 16 || search.len() == 8); if looks_like_short_key_id { Outcome::Success(Hkp::ShortKeyID { - query: search, - index: index, + query: search.to_string(), + index, }) } else if let Ok(fpr) = maybe_fpr { Outcome::Success(Hkp::Fingerprint { - fpr: fpr, - index: index, + fpr, + index, }) } else if let Ok(keyid) = maybe_keyid { Outcome::Success(Hkp::KeyID { - keyid: keyid, - index: index, + keyid, + index, }) } else { match Email::from_str(&search) { Ok(email) => { Outcome::Success(Hkp::Email { - email: email, - index: index, + email, + index, }) } Err(_) => { Outcome::Success(Hkp::Invalid{ - query: search + query: search.to_string(), }) } } @@ -119,9 +116,9 @@ impl<'a, 'r> FromRequest<'a, 'r> for Hkp { #[post("/pks/add", format = "multipart/form-data", data = "")] pub fn pks_add_form_data( - db: rocket::State, - tokens_stateless: rocket::State, - rate_limiter: rocket::State, + db: &rocket::State, + tokens_stateless: &rocket::State, + rate_limiter: &rocket::State, i18n: I18n, cont_type: &ContentType, data: Data, @@ -133,16 +130,16 @@ pub fn pks_add_form_data( } #[post("/pks/add", format = "application/x-www-form-urlencoded", data = "")] -pub fn pks_add_form( +pub async fn pks_add_form( request_origin: RequestOrigin, - db: rocket::State, - tokens_stateless: rocket::State, - rate_limiter: rocket::State, - mail_service: rocket::State, + db: &rocket::State, + tokens_stateless: &rocket::State, + rate_limiter: &rocket::State, + mail_service: &rocket::State, i18n: I18n, - data: Data, + data: Data<'_>, ) -> MyResponse { - match vks_web::process_post_form(&db, &tokens_stateless, &rate_limiter, &i18n, data) { + match vks_web::process_post_form(&db, &tokens_stateless, &rate_limiter, &i18n, data).await { Ok(UploadResponse::Ok { is_new_key, key_fpr, primary_uid, token, status, .. }) => { let msg = pks_add_ok(&request_origin, &mail_service, &rate_limiter, token, status, is_new_key, key_fpr, primary_uid); MyResponse::plain(msg) @@ -198,7 +195,7 @@ fn send_welcome_mail( #[get("/pks/lookup")] pub fn pks_lookup( - db: rocket::State, + db: &rocket::State, i18n: I18n, key: Hkp ) -> MyResponse { @@ -227,7 +224,7 @@ pub fn pks_lookup( #[get("/pks/internal/index/")] pub fn pks_internal_index( - db: rocket::State, + db: &rocket::State, i18n: I18n, query_string: String, ) -> MyResponse { @@ -238,7 +235,7 @@ pub fn pks_internal_index( } fn key_to_hkp_index( - db: rocket::State, + db: &rocket::State, i18n: I18n, query: Query, ) -> MyResponse { @@ -278,7 +275,7 @@ fn key_to_hkp_index( for uid in tpk.userids() { let uidstr = uid.userid().to_string(); - let u = Uri::percent_encode(&uidstr); + let u = utf8_percent_encode(&uidstr, DEFAULT_ENCODE_SET).to_string(); let ctime = uid .binding_signature(policy, None) .ok() @@ -344,7 +341,7 @@ mod tests { .header(ContentType::Form) .dispatch(); assert_eq!(response.status(), Status::Ok); - let body = response.body_string().unwrap(); + let body = response.into_string().unwrap(); eprintln!("response: {}", body); // Check that we get a welcome mail @@ -357,7 +354,7 @@ mod tests { .header(ContentType::Form) .dispatch(); assert_eq!(response.status(), Status::Ok); - let body = response.body_string().unwrap(); + let body = response.into_string().unwrap(); eprintln!("response: {}", body); // No second email right after the welcome one! diff --git a/src/web/maintenance.rs b/src/web/maintenance.rs index 7640927..69b4d9d 100644 --- a/src/web/maintenance.rs +++ b/src/web/maintenance.rs @@ -1,8 +1,9 @@ use rocket::{Request, Data}; use rocket::fairing::{Fairing, Info, Kind}; use rocket::http::Method; -use rocket_contrib::templates::Template; +use rocket_dyn_templates::Template; use rocket_i18n::I18n; +use serde_json::json; use std::fs; use std::path::PathBuf; @@ -23,6 +24,7 @@ mod templates { } } +#[async_trait] impl Fairing for MaintenanceMode { fn info(&self) -> Info { Info { @@ -31,21 +33,21 @@ impl Fairing for MaintenanceMode { } } - fn on_request(&self, request: &mut Request, _: &Data) { + async fn on_request(&self, request: &mut Request<'_>, _: &mut Data<'_>) { let message = match self.get_maintenance_message() { Some(message) => message, None => return, }; - let path = request.uri().path(); + let path = request.uri().path().as_str(); if self.is_request_json(path) { - request.set_uri(uri!(maintenance_error_json: message)); + request.set_uri(uri!(maintenance_error_json(message))); request.set_method(Method::Get); } else if self.is_request_plain(path, request.method()) { - request.set_uri(uri!(maintenance_error_plain: message)); + request.set_uri(uri!(maintenance_error_plain(message))); request.set_method(Method::Get); } else if self.is_request_web(path) { - request.set_uri(uri!(maintenance_error_web: message)); + request.set_uri(uri!(maintenance_error_web(message))); request.set_method(Method::Get); } } diff --git a/src/web/manage.rs b/src/web/manage.rs index 15c6b39..7a70fc7 100644 --- a/src/web/manage.rs +++ b/src/web/manage.rs @@ -1,6 +1,5 @@ use rocket; -use rocket::State; -use rocket::request::Form; +use rocket::form::Form; use rocket_i18n::I18n; use crate::Result; @@ -65,10 +64,10 @@ pub fn vks_manage() -> Result { #[get("/manage/")] pub fn vks_manage_key( request_origin: RequestOrigin, - db: State, + db: &rocket::State, i18n: I18n, token: String, - token_service: rocket::State, + token_service: &rocket::State, ) -> MyResponse { use crate::database::types::Fingerprint; use std::convert::TryFrom; @@ -88,7 +87,7 @@ pub fn vks_manage_key( published: true, } ).collect(); - let key_link = uri!(vks_web::search: fp.to_string()).to_string(); + let key_link = uri!(vks_web::search(q = fp.to_string())).to_string(); let context = templates::ManageKey { key_fpr: fp.to_string(), key_link, @@ -100,7 +99,10 @@ pub fn vks_manage_key( }, Ok(None) => MyResponse::not_found( Some("manage/manage"), - Some(i18n!(i18n.catalog, "This link is invalid or expired"))), + Some(i18n!(i18n.catalog, "This link is invalid or expired")), + i18n, + request_origin, + ), Err(e) => MyResponse::ise(e), } } else { @@ -112,13 +114,13 @@ pub fn vks_manage_key( #[post("/manage", data="")] pub fn vks_manage_post( - db: State, + db: &rocket::State, request_origin: RequestOrigin, - mail_service: rocket::State, - rate_limiter: rocket::State, + mail_service: &rocket::State, + rate_limiter: &rocket::State, i18n: I18n, request: Form, - token_service: rocket::State, + token_service: &rocket::State, ) -> MyResponse { use std::convert::TryInto; @@ -155,7 +157,7 @@ pub fn vks_manage_post( let fpr: Fingerprint = tpk.fingerprint().try_into().unwrap(); let fpr_text = fpr.to_string(); let token = token_service.create(&StatelessVerifyToken { fpr }); - let link_path = uri!(vks_manage_key: token).to_string(); + let link_path = uri!(vks_manage_key(token)).to_string(); let base_uri = request_origin.get_base_uri(); if let Err(e) = mail_service.send_manage_token(&i18n, base_uri, fpr_text, &email, &link_path) { @@ -171,9 +173,9 @@ pub fn vks_manage_post( #[post("/manage/unpublish", data="")] pub fn vks_manage_unpublish( request_origin: RequestOrigin, - db: rocket::State, + db: &rocket::State, i18n: I18n, - token_service: rocket::State, + token_service: &rocket::State, request: Form, ) -> MyResponse { match vks_manage_unpublish_or_fail(request_origin, db, token_service, i18n, request) { @@ -184,8 +186,8 @@ pub fn vks_manage_unpublish( pub fn vks_manage_unpublish_or_fail( request_origin: RequestOrigin, - db: rocket::State, - token_service: rocket::State, + db: &rocket::State, + token_service: &rocket::State, i18n: I18n, request: Form, ) -> Result { diff --git a/src/web/mod.rs b/src/web/mod.rs index 28b283d..c99e1d9 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -1,15 +1,14 @@ use rocket; -use rocket::http::Status; +use rocket::figment::Figment; +use rocket::fs::NamedFile; +use rocket::http::{Header, Status}; use rocket::request; use rocket::outcome::Outcome; -use rocket::response::{NamedFile, Responder, Response}; -use rocket::config::Config; -use rocket_contrib::templates::{Template, Engines}; -use rocket_contrib::json::JsonValue; +use rocket::response::{Responder, Response}; use rocket::response::status::Custom; +use rocket_dyn_templates::{Engines, Template}; use rocket_i18n::I18n; - -use rocket_prometheus::PrometheusMetrics; +use hyperx::header::{ContentDisposition, DispositionType, DispositionParam, Charset}; use gettext_macros::{compile_i18n, include_i18n}; @@ -19,7 +18,6 @@ use std::path::PathBuf; use crate::mail; use crate::tokens; -use crate::counters; use crate::i18n_helpers::describe_query_error; use crate::template_helpers::TemplateOverrides; use crate::i18n::I18NHelper; @@ -41,17 +39,14 @@ mod debug_web; use crate::web::maintenance::MaintenanceMode; -use rocket::http::hyper::header::ContentDisposition; +pub struct HagridTemplate(&'static str, serde_json::Value, I18n, RequestOrigin); -pub struct HagridTemplate(&'static str, serde_json::Value); +impl<'r> Responder<'r, 'static> for HagridTemplate { + fn respond_to(self, req: &'r rocket::Request) -> std::result::Result, Status> { + let HagridTemplate(tmpl, ctx, i18n, origin) = self; -impl Responder<'static> for HagridTemplate { - fn respond_to(self, req: &rocket::Request) -> std::result::Result, Status> { - let HagridTemplate(tmpl, ctx) = self; - let i18n: I18n = req.guard().expect("Error parsing language"); - let template_overrides: rocket::State = req.guard().expect("TemplateOverrides must be in managed state"); + let template_overrides: &TemplateOverrides = req.rocket().state().expect("TemplateOverrides must be in managed state"); let template_override = template_overrides.get_template_override(i18n.lang, tmpl); - let origin: RequestOrigin = req.guard().expect("Error determining request origin"); let layout_context = templates::HagridLayout::new(ctx, i18n, origin); if let Some(template_override) = template_override { @@ -71,7 +66,7 @@ pub enum MyResponse { #[response(status = 200, content_type = "xml")] Xml(HagridTemplate), #[response(status = 200, content_type = "application/pgp-keys")] - Key(String, ContentDisposition), + Key(String, Header<'static>), #[response(status = 500, content_type = "html")] ServerError(Template), #[response(status = 404, content_type = "html")] @@ -85,25 +80,25 @@ pub enum MyResponse { #[response(status = 503, content_type = "html")] Maintenance(Template), #[response(status = 503, content_type = "json")] - MaintenanceJson(JsonValue), + MaintenanceJson(serde_json::Value), #[response(status = 503, content_type = "plain")] MaintenancePlain(String), } impl MyResponse { - pub fn ok(tmpl: &'static str, ctx: impl Serialize) -> Self { + pub fn ok(tmpl: &'static str, ctx: impl Serialize, i18n: I18n, origin: RequestOrigin) -> Self { let context_json = serde_json::to_value(ctx).unwrap(); - MyResponse::Success(HagridTemplate(tmpl, context_json)) + MyResponse::Success(HagridTemplate(tmpl, context_json, i18n, origin)) } - pub fn ok_bare(tmpl: &'static str) -> Self { + pub fn ok_bare(tmpl: &'static str, i18n: I18n, origin: RequestOrigin) -> Self { let context_json = serde_json::to_value(templates::Bare { dummy: () }).unwrap(); - MyResponse::Success(HagridTemplate(tmpl, context_json)) + MyResponse::Success(HagridTemplate(tmpl, context_json, i18n, origin)) } - pub fn xml(tmpl: &'static str) -> Self { + pub fn xml(tmpl: &'static str, i18n: I18n, origin: RequestOrigin) -> Self { let context_json = serde_json::to_value(templates::Bare { dummy: () }).unwrap(); - MyResponse::Xml(HagridTemplate(tmpl, context_json)) + MyResponse::Xml(HagridTemplate(tmpl, context_json, i18n, origin)) } pub fn plain(s: String) -> Self { @@ -111,17 +106,15 @@ impl MyResponse { } pub fn key(armored_key: String, fp: &Fingerprint) -> Self { - use rocket::http::hyper::header::{DispositionType, DispositionParam, Charset}; - MyResponse::Key( - armored_key, - ContentDisposition { + let content_disposition = Header::new(CONTENT_DISPOSITION.as_str(), ContentDisposition { disposition: DispositionType::Attachment, parameters: vec![ DispositionParam::Filename( Charset::Us_Ascii, None, (fp.to_string() + ".asc").into_bytes()), ], - }) + }); + MyResponse::Key(armored_key, content_disposition) } pub fn ise(e: anyhow::Error) -> Self { @@ -135,10 +128,10 @@ impl MyResponse { MyResponse::ServerError(Template::render("500", ctx)) } - pub fn bad_request(template: &'static str, e: anyhow::Error) -> Self { + pub fn bad_request(template: &'static str, e: anyhow::Error, i18n: I18n, origin: RequestOrigin) -> Self { let ctx = templates::Error { error: format!("{}", e) }; let context_json = serde_json::to_value(ctx).unwrap(); - MyResponse::BadRequest(HagridTemplate(template, context_json)) + MyResponse::BadRequest(HagridTemplate(template, context_json, i18n, origin)) } pub fn bad_request_plain(message: impl Into) -> Self { @@ -152,11 +145,13 @@ impl MyResponse { pub fn not_found( tmpl: Option<&'static str>, message: impl Into>, + i18n: I18n, + origin: RequestOrigin, ) -> Self { let ctx = templates::Error { error: message.into() .unwrap_or_else(|| "Key not found".to_owned()) }; let context_json = serde_json::to_value(ctx).unwrap(); - MyResponse::NotFound(HagridTemplate(tmpl.unwrap_or("index"), context_json)) + MyResponse::NotFound(HagridTemplate(tmpl.unwrap_or("index"), context_json, i18n, origin)) } } @@ -202,7 +197,7 @@ mod templates { version: env!("CARGO_PKG_VERSION").to_string(), commit: env!("VERGEN_SHA_SHORT").to_string(), base_uri: origin.get_base_uri().to_string(), - page: page, + page, lang: i18n.lang.to_string(), htmldir: if is_rtl { "rtl".to_owned() } else { "ltr".to_owned() }, htmlclass: if is_rtl { "rtl".to_owned() } else { "".to_owned() }, @@ -226,11 +221,12 @@ pub enum RequestOrigin { OnionService(String), } -impl<'a, 'r> request::FromRequest<'a, 'r> for RequestOrigin { +#[async_trait] +impl<'r> request::FromRequest<'r> for RequestOrigin { type Error = (); - fn from_request(request: &'a request::Request<'r>) -> request::Outcome { - let hagrid_state = request.guard::>().unwrap(); + async fn from_request(request: &'r request::Request<'_>) -> request::Outcome { + let hagrid_state = request.rocket().state::().unwrap(); let result = match request.headers().get("x-is-onion").next() { Some(_) => RequestOrigin::OnionService(hagrid_state.base_uri_onion.clone()), None => RequestOrigin::Direct(hagrid_state.base_uri.clone()), @@ -249,7 +245,7 @@ impl RequestOrigin { } pub fn key_to_response_plain( - db: rocket::State, + db: &rocket::State, i18n: I18n, query: Query, ) -> MyResponse { @@ -270,8 +266,8 @@ pub fn key_to_response_plain( } #[get("/assets/")] -fn files(file: PathBuf, state: rocket::State) -> Option { - NamedFile::open(state.assets_dir.join(file)).ok() +async fn files(file: PathBuf, state: &rocket::State) -> Option { + NamedFile::open(state.assets_dir.join(file)).await.ok() } #[get("/")] @@ -338,13 +334,13 @@ fn errors( Ok(Custom(status_code, response_body)) } -pub fn serve() -> Result<()> { - Err(rocket_factory(rocket::ignite())?.launch().into()) +pub fn serve() -> Result> { + Ok(rocket_factory(rocket::build())?) } compile_i18n!(); -fn rocket_factory(mut rocket: rocket::Rocket) -> Result { +fn rocket_factory(mut rocket: rocket::Rocket) -> Result> { let routes = routes![ // infra root, @@ -384,7 +380,7 @@ fn rocket_factory(mut rocket: rocket::Rocket) -> Result { hkp::pks_add_form, hkp::pks_add_form_data, hkp::pks_internal_index, - // EManage + // Manage manage::vks_manage, manage::vks_manage_key, manage::vks_manage_post, @@ -395,17 +391,18 @@ fn rocket_factory(mut rocket: rocket::Rocket) -> Result { maintenance::maintenance_error_plain, ]; - let db_service = configure_db_service(rocket.config())?; - let hagrid_state = configure_hagrid_state(rocket.config())?; - let stateful_token_service = configure_stateful_token_service(rocket.config())?; - let stateless_token_service = configure_stateless_token_service(rocket.config())?; - let mail_service = configure_mail_service(rocket.config())?; - let rate_limiter = configure_rate_limiter(rocket.config())?; - let maintenance_mode = configure_maintenance_mode(rocket.config())?; - let localized_template_list = configure_localized_template_list(rocket.config())?; + let figment = rocket.figment(); + let db_service = configure_db_service(figment)?; + let hagrid_state = configure_hagrid_state(figment)?; + let stateful_token_service = configure_stateful_token_service(figment)?; + let stateless_token_service = configure_stateless_token_service(figment)?; + let mail_service = configure_mail_service(figment)?; + let rate_limiter = configure_rate_limiter(figment)?; + let maintenance_mode = configure_maintenance_mode(figment)?; + let localized_template_list = configure_localized_template_list(figment)?; println!("{:?}", localized_template_list); - let prometheus = configure_prometheus(rocket.config()); + // let prometheus = configure_prometheus(rocket.config()); rocket = rocket .attach(Template::custom(|engines: &mut Engines| { @@ -424,39 +421,43 @@ fn rocket_factory(mut rocket: rocket::Rocket) -> Result { .manage(localized_template_list) .mount("/", routes); + /* if let Some(prometheus) = prometheus { rocket = rocket .attach(prometheus.clone()) .mount("/metrics", prometheus); } + */ Ok(rocket) } -fn configure_prometheus(config: &Config) -> Option { - if !config.get_bool("enable_prometheus").unwrap_or(false) { +/* +fn configure_prometheus(config: &Figment) -> Option { + if !config.extract_inner("enable_prometheus").unwrap_or(false) { return None; } let prometheus = PrometheusMetrics::new(); counters::register_counters(&prometheus.registry()); return Some(prometheus); } +*/ -fn configure_db_service(config: &Config) -> Result { - let keys_internal_dir: PathBuf = config.get_str("keys_internal_dir")?.into(); - let keys_external_dir: PathBuf = config.get_str("keys_external_dir")?.into(); - let tmp_dir: PathBuf = config.get_str("tmp_dir")?.into(); +fn configure_db_service(config: &Figment) -> Result { + let keys_internal_dir: PathBuf = config.extract_inner("keys_internal_dir")?; + let keys_external_dir: PathBuf = config.extract_inner("keys_external_dir")?; + let tmp_dir: PathBuf = config.extract_inner("tmp_dir")?; let fs_db = KeyDatabase::new(keys_internal_dir, keys_external_dir, tmp_dir)?; Ok(fs_db) } -fn configure_hagrid_state(config: &Config) -> Result { - let assets_dir: PathBuf = config.get_str("assets_dir")?.into(); +fn configure_hagrid_state(config: &Figment) -> Result { + let assets_dir: PathBuf = config.extract_inner("assets_dir")?; // State - let base_uri = config.get_str("base-URI")?.to_string(); - let base_uri_onion = config.get_str("base-URI-Onion") + let base_uri: String = config.extract_inner("base-URI")?; + let base_uri_onion = config.extract_inner::("base-URI-Onion") .map(|c| c.to_string()) .unwrap_or(base_uri.clone()); Ok(HagridState { @@ -466,29 +467,25 @@ fn configure_hagrid_state(config: &Config) -> Result { }) } -fn configure_stateful_token_service(config: &Config) -> Result { - let token_dir: PathBuf = config.get_str("token_dir")?.into(); +fn configure_stateful_token_service(config: &Figment) -> Result { + let token_dir: PathBuf = config.extract_inner("token_dir")?; database::StatefulTokens::new(token_dir) } -fn configure_stateless_token_service(config: &Config) -> Result { - use std::convert::TryFrom; - - let secret = config.get_str("token_secret")?.to_string(); - let validity = config.get_int("token_validity")?; - let validity = u64::try_from(validity)?; +fn configure_stateless_token_service(config: &Figment) -> Result { + let secret: String = config.extract_inner("token_secret")?; + let validity: u64 = config.extract_inner("token_validity")?; Ok(tokens::Service::init(&secret, validity)) } -fn configure_mail_service(config: &Config) -> Result { +fn configure_mail_service(config: &Figment) -> Result { // Mail service - let email_template_dir: PathBuf = config.get_str("email_template_dir")?.into(); + let email_template_dir: PathBuf = config.extract_inner("email_template_dir")?; - let base_uri = config.get_str("base-URI")?; - let from = config.get_str("from")?; + let base_uri = config.extract_inner("base-URI")?; + let from = config.extract_inner("from")?; - let filemail_into = config.get_str("filemail_into") - .ok().map(|p| PathBuf::from(p)); + let filemail_into: Option = config.extract_inner::("filemail_into").ok(); if let Some(path) = filemail_into { mail::Service::filemail(from, base_uri, &email_template_dir, &path) @@ -497,33 +494,33 @@ fn configure_mail_service(config: &Config) -> Result { } } -fn configure_rate_limiter(config: &Config) -> Result { - let timeout_secs = config.get_int("mail_rate_limit").unwrap_or(60); +fn configure_rate_limiter(config: &Figment) -> Result { + let timeout_secs: i32 = config.extract_inner("mail_rate_limit").unwrap_or(60); let timeout_secs = timeout_secs.try_into()?; Ok(RateLimiter::new(timeout_secs)) } -fn configure_localized_template_list(config: &Config) -> Result { - let template_dir: PathBuf = config.get_str("template_dir")?.into(); +fn configure_localized_template_list(config: &Figment) -> Result { + let template_dir: PathBuf = config.extract_inner("template_dir")?; TemplateOverrides::load(&template_dir, "localized") } -fn configure_maintenance_mode(config: &Config) -> Result { - let maintenance_file: PathBuf = config.get_str("maintenance_file") - .unwrap_or("maintenance").into(); +fn configure_maintenance_mode(config: &Figment) -> Result { + let maintenance_file: PathBuf = config.extract_inner("maintenance_file") + .unwrap_or_else(|_| PathBuf::from("maintenance")); Ok(MaintenanceMode::new(maintenance_file)) } #[cfg(test)] pub mod tests { use regex; + use rocket::local::blocking::{Client, LocalResponse}; use std::fs; use std::fs::File; use std::io::Write; use std::path::Path; use tempfile::{tempdir, TempDir}; use super::rocket; - use rocket::local::{Client, LocalResponse}; use rocket::http::Status; use rocket::http::ContentType; use rocket::http::Header; @@ -559,49 +556,47 @@ pub mod tests { /// Note that you need to keep the returned TempDir alive for the /// duration of your test. To debug the test, mem::forget it to /// prevent cleanup. - pub fn configuration() -> Result<(TempDir, rocket::Config)> { - use rocket::config::Environment; - + pub fn configuration() -> Result<(TempDir, rocket::figment::Figment)> { let root = tempdir()?; let filemail = root.path().join("filemail"); ::std::fs::create_dir_all(&filemail)?; let base_dir: PathBuf = root.path().into(); - let config = Config::build(Environment::Staging) - .root(root.path().to_path_buf()) - .extra("template_dir", + let config = Figment::new() + .select("staging") + .merge(("root", root.path())) + .merge(("template_dir", ::std::env::current_dir().unwrap().join("dist/templates") - .to_str().unwrap()) - .extra("email_template_dir", + .to_str().unwrap())) + .merge(("email_template_dir", ::std::env::current_dir().unwrap().join("dist/email-templates") - .to_str().unwrap()) - .extra("assets_dir", + .to_str().unwrap())) + .merge(("assets_dir", ::std::env::current_dir().unwrap().join("dist/assets") - .to_str().unwrap()) - .extra("keys_internal_dir", base_dir.join("keys_internal").to_str().unwrap()) - .extra("keys_external_dir", base_dir.join("keys_external").to_str().unwrap()) - .extra("tmp_dir", base_dir.join("tmp").to_str().unwrap()) - .extra("token_dir", base_dir.join("tokens").to_str().unwrap()) - .extra("maintenance_file", base_dir.join("maintenance").to_str().unwrap()) - .extra("base-URI", BASE_URI) - .extra("base-URI-Onion", BASE_URI_ONION) - .extra("from", "from@example.com") - .extra("token_secret", "hagrid") - .extra("token_validity", 3600) - .extra("filemail_into", filemail.into_os_string().into_string() - .expect("path is valid UTF8")) - .finalize()?; + .to_str().unwrap())) + .merge(("keys_internal_dir", base_dir.join("keys_internal").to_str().unwrap())) + .merge(("keys_external_dir", base_dir.join("keys_external").to_str().unwrap())) + .merge(("tmp_dir", base_dir.join("tmp").to_str().unwrap())) + .merge(("token_dir", base_dir.join("tokens").to_str().unwrap())) + .merge(("maintenance_file", base_dir.join("maintenance").to_str().unwrap())) + .merge(("base-URI", BASE_URI)) + .merge(("base-URI-Onion", BASE_URI_ONION)) + .merge(("from", "from@example.com")) + .merge(("token_secret", "hagrid")) + .merge(("token_validity", 3600u64)) + .merge(("filemail_into", filemail.into_os_string().into_string() + .expect("path is valid UTF8"))); Ok((root, config)) } pub fn client() -> Result<(TempDir, Client)> { let (tmpdir, config) = configuration()?; let rocket = rocket_factory(rocket::custom(config))?; - Ok((tmpdir, Client::new(rocket)?)) + Ok((tmpdir, Client::untracked(rocket)?)) } - pub fn assert_consistency(rocket: &rocket::Rocket) { + pub fn assert_consistency(rocket: &rocket::Rocket) { let db = rocket.state::().unwrap(); db.check_consistency().unwrap(); } @@ -610,7 +605,7 @@ pub mod tests { fn about_translation() { let (_tmpdir, config) = configuration().unwrap(); let rocket = rocket_factory(rocket::custom(config)).unwrap(); - let client = Client::new(rocket).expect("valid rocket instance"); + let client = Client::untracked(rocket).expect("valid rocket instance"); // Check that we see the landing page. let mut response = client.get("/about") @@ -619,50 +614,50 @@ pub mod tests { assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); // TODO check translation - assert!(response.body_string().unwrap().contains("Hagrid")); + assert!(response.into_string().unwrap().contains("Hagrid")); } #[test] fn basics() { let (_tmpdir, config) = configuration().unwrap(); let rocket = rocket_factory(rocket::custom(config)).unwrap(); - let client = Client::new(rocket).expect("valid rocket instance"); + let client = Client::untracked(rocket).expect("valid rocket instance"); // Check that we see the landing page. let mut response = client.get("/").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); - assert!(response.body_string().unwrap().contains("Hagrid")); + assert!(response.into_string().unwrap().contains("Hagrid")); // Check that we see the privacy policy. let mut response = client.get("/about").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); - assert!(response.body_string().unwrap().contains("distribution and discovery")); + assert!(response.into_string().unwrap().contains("distribution and discovery")); // Check that we see the privacy policy. let mut response = client.get("/about/privacy").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); - assert!(response.body_string().unwrap().contains("Public Key Data")); + assert!(response.into_string().unwrap().contains("Public Key Data")); // Check that we see the API docs. let mut response = client.get("/about/api").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); - assert!(response.body_string().unwrap().contains("/vks/v1/by-keyid")); + assert!(response.into_string().unwrap().contains("/vks/v1/by-keyid")); // Check that we see the upload form. let mut response = client.get("/upload").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); - assert!(response.body_string().unwrap().contains("upload")); + assert!(response.into_string().unwrap().contains("upload")); // Check that we see the deletion form. let mut response = client.get("/manage").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); - assert!(response.body_string().unwrap().contains("any verified email address")); + assert!(response.into_string().unwrap().contains("any verified email address")); assert_consistency(client.rocket()); } @@ -687,21 +682,21 @@ pub mod tests { let mut response = client.put("/").dispatch(); assert_eq!(response.status(), Status::ServiceUnavailable); assert_eq!(response.content_type(), Some(ContentType::Plain)); - assert!(response.body_string().unwrap().contains("maintenance-message")); + assert!(response.into_string().unwrap().contains("maintenance-message")); fs::remove_file(&maintenance_path).unwrap(); // Check that we see the upload form. let mut response = client.get("/upload").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); - assert!(!response.body_string().unwrap().contains("maintenance-message")); + assert!(!response.into_string().unwrap().contains("maintenance-message")); } fn check_maintenance(client: &Client, uri: &str, content_type: ContentType) { let mut response = client.get(uri).dispatch(); assert_eq!(response.status(), Status::ServiceUnavailable); assert_eq!(response.content_type(), Some(content_type)); - assert!(response.body_string().unwrap().contains("maintenance-message")); + assert!(response.into_string().unwrap().contains("maintenance-message")); } #[test] @@ -781,7 +776,7 @@ pub mod tests { let (_tmpdir, config) = configuration().unwrap(); let rocket = rocket_factory(rocket::custom(config)).unwrap(); - let client = Client::new(rocket).expect("valid rocket instance"); + let client = Client::untracked(rocket).expect("valid rocket instance"); // Generate two keys and upload them. let tpk_0 = build_cert("foo@invalid.example.com"); @@ -813,7 +808,7 @@ pub mod tests { let filemail_into = tmpdir.path().join("filemail"); let rocket = rocket_factory(rocket::custom(config)).unwrap(); - let client = Client::new(rocket).expect("valid rocket instance"); + let client = Client::untracked(rocket).expect("valid rocket instance"); // Generate two keys and upload them. let tpk_1 = build_cert("foo@invalid.example.com"); @@ -997,7 +992,7 @@ pub mod tests { assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::new("application", "pgp-keys"))); - let body = response.body_string().unwrap(); + let body = response.into_string().unwrap(); assert!(body.contains("END PGP PUBLIC KEY BLOCK")); let tpk_ = Cert::from_bytes(body.as_bytes()).unwrap(); assert_eq!(tpk.fingerprint(), tpk_.fingerprint()); @@ -1016,7 +1011,7 @@ pub mod tests { assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::new("text", "plain"))); - let body = response.body_string().unwrap(); + let body = response.into_string().unwrap(); assert!(body.contains("info:1:1")); let primary_fpr = tpk.fingerprint().to_hex(); @@ -1069,7 +1064,7 @@ pub mod tests { pub fn check_response(client: &Client, uri: &str, status: Status, needle: &str) { let mut response = client.get(uri).dispatch(); assert_eq!(response.status(), status); - let body = response.body_string().unwrap(); + let body = response.into_string().unwrap(); println!("{}", body); assert!(body.contains(needle)); } @@ -1081,7 +1076,7 @@ pub mod tests { let mut response = client.get(uri).dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.body_string().unwrap(); + let body = response.into_string().unwrap(); assert!(body.contains("found")); assert!(body.contains(&tpk.fingerprint().to_hex())); @@ -1106,7 +1101,7 @@ pub mod tests { .header(Header::new("X-Is-Onion", "true")) .dispatch(); assert_eq!(response.status(), Status::Ok); - let body = response.body_string().unwrap(); + let body = response.into_string().unwrap(); assert!(body.contains("found")); assert!(body.contains(&tpk.fingerprint().to_hex())); @@ -1164,7 +1159,7 @@ pub mod tests { .body(json.as_bytes()) .dispatch(); assert_eq!(response.status(), Status::Ok); - assert!(response.body_string().unwrap().contains("pending")); + assert!(response.into_string().unwrap().contains("pending")); } fn check_mails_and_verify_email(client: &Client, filemail_path: &Path) { @@ -1176,7 +1171,7 @@ pub mod tests { let mut response_second = client.post(&confirm_uri).dispatch(); assert_eq!(response_second.status(), Status::BadRequest); - assert!(response_second.body_string().unwrap().contains("already been verified")); + assert!(response_second.into_string().unwrap().contains("already been verified")); } fn check_mails_and_confirm_deletion(client: &Client, filemail_path: &Path, address: &str) { @@ -1196,7 +1191,7 @@ pub mod tests { fn vks_publish_submit_multiple<'a>(client: &'a Client, data: &[u8]) { let mut response = vks_publish_submit_response(client, data); - let response_body = response.body_string().unwrap(); + let response_body = response.into_string().unwrap(); assert_eq!(response.status(), Status::Ok); assert!(response_body.contains("you must upload them individually")); @@ -1204,7 +1199,7 @@ pub mod tests { fn vks_publish_submit_get_token<'a>(client: &'a Client, data: &[u8]) -> String { let mut response = vks_publish_submit_response(client, data); - let response_body = response.body_string().unwrap(); + let response_body = response.into_string().unwrap(); let pattern = "name=\"token\" value=\"([^\"]*)\""; let capture_re = regex::bytes::Regex::new(pattern).unwrap(); @@ -1247,8 +1242,8 @@ pub mod tests { let mut response = client.put("/") .body(data) .dispatch(); - let response_body = response.body_string().unwrap(); assert_eq!(response.status(), Status::Ok); + let response_body = response.into_string().unwrap(); assert!(response_body.contains("Key successfully uploaded")); let pattern = format!("{}/upload/([^ \t\n]*)", BASE_URI); @@ -1263,7 +1258,7 @@ pub mod tests { .header(ContentType::JSON) .body(format!(r#"{{ "keytext": "{}" }}"#, base64::encode(data))) .dispatch(); - let response_body = response.body_string().unwrap(); + let response_body = response.into_string().unwrap(); let result: vks_api::json::UploadResult = serde_json::from_str(&response_body).unwrap(); assert_eq!(response.status(), Status::Ok); diff --git a/src/web/vks.rs b/src/web/vks.rs index b7429e1..d356f44 100644 --- a/src/web/vks.rs +++ b/src/web/vks.rs @@ -194,12 +194,12 @@ fn process_key_single( } pub fn request_verify( - db: rocket::State, + db: &rocket::State, request_origin: RequestOrigin, - token_stateful: rocket::State, - token_stateless: rocket::State, - mail_service: rocket::State, - rate_limiter: rocket::State, + token_stateful: &rocket::State, + token_stateless: &rocket::State, + mail_service: &rocket::State, + rate_limiter: &rocket::State, i18n: I18n, token: String, addresses: Vec, @@ -273,9 +273,9 @@ fn send_verify_email( } pub fn verify_confirm( - db: rocket::State, + db: &rocket::State, i18n: &I18n, - token_service: rocket::State, + token_service: &rocket::State, token: String, ) -> response::PublishResponse { let (fingerprint, email) = match check_publish_token(&db, &token_service, token) { diff --git a/src/web/vks_api.rs b/src/web/vks_api.rs index d6362a8..b7c49d0 100644 --- a/src/web/vks_api.rs +++ b/src/web/vks_api.rs @@ -1,9 +1,8 @@ -use rocket_contrib::json::{Json,JsonValue,JsonError}; -use rocket::request::Request; -use rocket::response::{self, Response, Responder}; +use rocket::request::Request; use rocket::response::{self, Response, Responder}; use rocket::http::{ContentType,Status}; -use rocket::State; +use rocket::serde::json::Json; use rocket_i18n::{I18n, Translations}; +use serde_json::json; use std::io::Cursor; use crate::database::{KeyDatabase, StatefulTokens, Query}; @@ -17,6 +16,8 @@ use crate::web::{RequestOrigin, MyResponse}; use crate::web::vks; use crate::web::vks::response::*; +use rocket::serde::json::Error as JsonError; + pub mod json { use crate::web::vks::response::EmailStatus; use std::collections::HashMap; @@ -41,17 +42,17 @@ pub mod json { } } -type JsonResult = Result; +type JsonResult = Result; #[derive(Debug)] pub struct JsonErrorResponse(Status,String); -impl<'r> Responder<'r> for JsonErrorResponse { - fn respond_to(self, _: &Request) -> response::Result<'r> { +impl<'r> Responder<'r, 'static> for JsonErrorResponse { + fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> { let error_json = json!({"error": self.1}); Response::build() .status(self.0) - .sized_body(Cursor::new(error_json.to_string())) + .sized_body(None, Cursor::new(error_json.to_string())) .header(ContentType::JSON) .ok() } @@ -65,7 +66,7 @@ fn json_or_error(data: Result, JsonError>) -> Result, JsonErr } } -fn upload_ok_json(response: UploadResponse) -> Result { +fn upload_ok_json(response: UploadResponse) -> Result { match response { UploadResponse::Ok { token, key_fpr, status, .. } => Ok(json!(json::UploadResult { token, key_fpr, status })), @@ -76,9 +77,9 @@ fn upload_ok_json(response: UploadResponse) -> Result, - tokens_stateless: rocket::State, - rate_limiter: rocket::State, + db: &rocket::State, + tokens_stateless: &rocket::State, + rate_limiter: &rocket::State, i18n: I18n, data: Result, JsonError>, ) -> JsonResult { @@ -98,7 +99,7 @@ pub fn upload_fallback( } fn get_locale( - langs: State, + langs: &rocket::State, locales: Vec, ) -> I18n { locales @@ -113,13 +114,13 @@ fn get_locale( #[post("/vks/v1/request-verify", format = "json", data="")] pub fn request_verify_json( - db: rocket::State, - langs: State, + db: &rocket::State, + langs: &rocket::State, request_origin: RequestOrigin, - token_stateful: rocket::State, - token_stateless: rocket::State, - mail_service: rocket::State, - rate_limiter: rocket::State, + token_stateful: &rocket::State, + token_stateless: &rocket::State, + mail_service: &rocket::State, + rate_limiter: &rocket::State, data: Result, JsonError>, ) -> JsonResult { let data = json_or_error(data)?; @@ -141,7 +142,7 @@ pub fn request_verify_fallback( #[get("/vks/v1/by-fingerprint/")] pub fn vks_v1_by_fingerprint( - db: rocket::State, + db: &rocket::State, i18n: I18n, fpr: String, ) -> MyResponse { @@ -155,7 +156,7 @@ pub fn vks_v1_by_fingerprint( #[get("/vks/v1/by-email/")] pub fn vks_v1_by_email( - db: rocket::State, + db: &rocket::State, i18n: I18n, email: String, ) -> MyResponse { @@ -170,7 +171,7 @@ pub fn vks_v1_by_email( #[get("/vks/v1/by-keyid/")] pub fn vks_v1_by_keyid( - db: rocket::State, + db: &rocket::State, i18n: I18n, kid: String, ) -> MyResponse { diff --git a/src/web/vks_web.rs b/src/web/vks_web.rs index 6581536..2422a9f 100644 --- a/src/web/vks_web.rs +++ b/src/web/vks_web.rs @@ -4,11 +4,14 @@ use multipart::server::save::Entries; use multipart::server::save::SaveResult::*; use multipart::server::Multipart; +use rocket::data::ByteUnit; +use rocket::form::Form; +use rocket::form::ValueField; use rocket::http::ContentType; -use rocket::request::Form; use rocket::Data; use rocket_i18n::I18n; use gettext_macros::i18n; +use url::percent_encoding::percent_decode; use crate::database::{KeyDatabase, StatefulTokens, Query, Database}; use crate::mail; @@ -17,13 +20,13 @@ use crate::web::{RequestOrigin, MyResponse}; use crate::rate_limiter::RateLimiter; use crate::i18n_helpers::describe_query_error; -use std::io::Read; use std::collections::HashMap; +use std::io::Cursor; use crate::web::vks; use crate::web::vks::response::*; -const UPLOAD_LIMIT: u64 = 1024 * 1024; // 1 MiB. +const UPLOAD_LIMIT: ByteUnit = ByteUnit::Mebibyte(1); mod forms { #[derive(FromForm,Deserialize)] @@ -94,7 +97,7 @@ impl MyResponse { fn upload_response_quick(response: UploadResponse, base_uri: &str) -> Self { match response { UploadResponse::Ok { token, .. } => { - let uri = uri!(quick_upload_proceed: token); + let uri = uri!(quick_upload_proceed(token)); let text = format!( "Key successfully uploaded. Proceed with verification here:\n{}{}\n", base_uri, @@ -127,7 +130,7 @@ impl MyResponse { count_unparsed: usize, uid_status: HashMap, ) -> Self { - let key_link = uri!(search: &key_fpr).to_string(); + let key_link = uri!(search(q = &key_fpr)).to_string(); let count_revoked = uid_status.iter() .filter(|(_,status)| **status == EmailStatus::Revoked) @@ -169,7 +172,7 @@ impl MyResponse { fn upload_ok_multi(key_fprs: Vec) -> Self { let keys = key_fprs.into_iter() .map(|fpr| { - let key_link = uri!(search: &fpr).to_string(); + let key_link = uri!(search(q = &fpr)).to_string(); template::UploadOkKey { key_fpr: fpr.to_owned(), key_link, @@ -192,9 +195,9 @@ pub fn upload() -> MyResponse { #[post("/upload/submit", format = "multipart/form-data", data = "")] pub fn upload_post_form_data( - db: rocket::State, - tokens_stateless: rocket::State, - rate_limiter: rocket::State, + db: &rocket::State, + tokens_stateless: &rocket::State, + rate_limiter: &rocket::State, i18n: I18n, cont_type: &ContentType, data: Data, @@ -206,9 +209,9 @@ pub fn upload_post_form_data( } pub fn process_post_form_data( - db: rocket::State, - tokens_stateless: rocket::State, - rate_limiter: rocket::State, + db: &rocket::State, + tokens_stateless: &rocket::State, + rate_limiter: &rocket::State, i18n: I18n, cont_type: &ContentType, data: Data, @@ -225,7 +228,7 @@ pub fn process_post_form_data( #[get("/search?")] pub fn search( - db: rocket::State, + db: &rocket::State, i18n: I18n, q: String, ) -> MyResponse { @@ -236,7 +239,7 @@ pub fn search( } fn key_to_response( - db: rocket::State, + db: &rocket::State, i18n: I18n, query_string: String, query: Query, @@ -259,20 +262,18 @@ fn key_to_response( #[put("/", data = "")] -pub fn quick_upload( - db: rocket::State, - tokens_stateless: rocket::State, - rate_limiter: rocket::State, +pub async fn quick_upload( + db: &rocket::State, + tokens_stateless: &rocket::State, + rate_limiter: &rocket::State, i18n: I18n, request_origin: RequestOrigin, - data: Data, + data: Data<'_>, ) -> MyResponse { - use std::io::Cursor; - - let mut buf = Vec::default(); - if let Err(error) = std::io::copy(&mut data.open().take(UPLOAD_LIMIT), &mut buf) { - return MyResponse::bad_request("400-plain", anyhow!(error)); - } + let buf = match data.open(UPLOAD_LIMIT).into_bytes().await { + Ok(buf) => buf.into_inner(), + Err(error) => return MyResponse::bad_request("400-plain", anyhow!(error)), + }; MyResponse::upload_response_quick( vks::process_key( @@ -286,12 +287,12 @@ pub fn quick_upload( #[get("/upload/", rank = 2)] pub fn quick_upload_proceed( - db: rocket::State, + db: &rocket::State, request_origin: RequestOrigin, - token_stateful: rocket::State, - token_stateless: rocket::State, - mail_service: rocket::State, - rate_limiter: rocket::State, + token_stateful: &rocket::State, + token_stateless: &rocket::State, + mail_service: &rocket::State, + rate_limiter: &rocket::State, i18n: I18n, token: String, ) -> MyResponse { @@ -303,43 +304,37 @@ pub fn quick_upload_proceed( #[post("/upload/submit", format = "application/x-www-form-urlencoded", data = "")] -pub fn upload_post_form( - db: rocket::State, - tokens_stateless: rocket::State, - rate_limiter: rocket::State, +pub async fn upload_post_form( + db: &rocket::State, + tokens_stateless: &rocket::State, + rate_limiter: &rocket::State, i18n: I18n, - data: Data, + data: Data<'_>, ) -> MyResponse { - match process_post_form(&db, &tokens_stateless, &rate_limiter, &i18n, data) { + match process_post_form(&db, &tokens_stateless, &rate_limiter, &i18n, data).await { Ok(response) => MyResponse::upload_response(response), Err(err) => MyResponse::bad_request("upload/upload", err), } } -pub fn process_post_form( +pub async fn process_post_form( db: &KeyDatabase, tokens_stateless: &tokens::Service, rate_limiter: &RateLimiter, i18n: &I18n, - data: Data, + data: Data<'_>, ) -> Result { - use rocket::request::FormItems; - use std::io::Cursor; - // application/x-www-form-urlencoded - let mut buf = Vec::default(); + let buf = data.open(UPLOAD_LIMIT).into_bytes().await?; - std::io::copy(&mut data.open().take(UPLOAD_LIMIT), &mut buf)?; - - for item in FormItems::from(&*String::from_utf8_lossy(&buf)) { - let (key, value) = item.key_value(); - let decoded_value = value.url_decode().or_else(|_| { + for ValueField { name, value } in Form::values(&*String::from_utf8_lossy(&buf)) { + let decoded_value = percent_decode(value.as_bytes()).decode_utf8().or_else(|_| { Err(anyhow!( "`Content-Type: application/x-www-form-urlencoded` \ not valid")) })?; - match key.as_str() { + match name.to_string().as_str() { "keytext" => { return Ok(vks::process_key( &db, @@ -368,7 +363,7 @@ fn process_upload( // saves all fields, any field longer than 10kB goes to a temporary directory // Entries could implement FromData though that would give zero control over // how the files are saved; Multipart would be a good impl candidate though - match Multipart::with_body(data.open().take(UPLOAD_LIMIT), boundary).save().temp() { + match Multipart::with_body(data.open(UPLOAD_LIMIT), boundary).save().temp() { Full(entries) => { process_multipart(db, tokens_stateless, rate_limiter, i18n, entries) } @@ -398,12 +393,12 @@ fn process_multipart( #[post("/upload/request-verify", format = "application/x-www-form-urlencoded", data="")] pub fn request_verify_form( - db: rocket::State, + db: &rocket::State, request_origin: RequestOrigin, - token_stateful: rocket::State, - token_stateless: rocket::State, - mail_service: rocket::State, - rate_limiter: rocket::State, + token_stateful: &rocket::State, + token_stateless: &rocket::State, + mail_service: &rocket::State, + rate_limiter: &rocket::State, i18n: I18n, request: Form, ) -> MyResponse { @@ -416,12 +411,12 @@ pub fn request_verify_form( #[post("/upload/request-verify", format = "multipart/form-data", data="")] pub fn request_verify_form_data( - db: rocket::State, + db: &rocket::State, request_origin: RequestOrigin, - token_stateful: rocket::State, - token_stateless: rocket::State, - mail_service: rocket::State, - rate_limiter: rocket::State, + token_stateful: &rocket::State, + token_stateless: &rocket::State, + mail_service: &rocket::State, + rate_limiter: &rocket::State, i18n: I18n, request: Form, ) -> MyResponse { @@ -434,9 +429,9 @@ pub fn request_verify_form_data( #[post("/verify/")] pub fn verify_confirm( - db: rocket::State, - token_service: rocket::State, - rate_limiter: rocket::State, + db: &rocket::State, + token_service: &rocket::State, + rate_limiter: &rocket::State, i18n: I18n, token: String, ) -> MyResponse { @@ -444,7 +439,7 @@ pub fn verify_confirm( match vks::verify_confirm(db, &i18n, token_service, token) { PublishResponse::Ok { fingerprint, email } => { rate_limiter.action_perform(rate_limit_id); - let userid_link = uri!(search: &email).to_string(); + let userid_link = uri!(search(q = &email)).to_string(); let context = template::Verify { userid: email, key_fpr: fingerprint,