fix: properly catch empty contact, small refactor

This commit is contained in:
əlemi 2023-12-23 16:54:42 +01:00
parent c959b4e18a
commit 0ebf01ceb1
Signed by: alemi
GPG key ID: A4895B84D311642C
3 changed files with 44 additions and 17 deletions

View file

@ -3,18 +3,22 @@ use chrono::{DateTime, Utc};
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GuestBookPage { pub struct GuestBookPage {
pub author: Option<String>, pub author: String,
pub contact: Option<String>,
pub body: String, pub body: String,
pub date: DateTime<Utc>, pub date: DateTime<Utc>,
pub avatar: String, pub avatar: String,
pub url: Option<String>, pub url: Option<String>,
pub contact: Option<String>,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Insertion { pub struct Insertion {
#[serde(deserialize_with = "non_empty_str")]
pub author: Option<String>, pub author: Option<String>,
#[serde(deserialize_with = "non_empty_str")]
pub contact: Option<String>, pub contact: Option<String>,
pub body: String, pub body: String,
} }
@ -40,3 +44,9 @@ pub struct PageOptions {
pub limit: Option<usize>, pub limit: Option<usize>,
} }
fn non_empty_str<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Option<String>, D::Error> {
Ok(Option::deserialize(d)?.filter(|s: &String| !s.is_empty()))
}

View file

@ -17,6 +17,6 @@ pub struct ConsolePrettyNotifier {}
#[async_trait::async_trait] #[async_trait::async_trait]
impl NotificationProcessor<GuestBookPage> for ConsolePrettyNotifier { impl NotificationProcessor<GuestBookPage> for ConsolePrettyNotifier {
async fn process(&self, suggestion: &GuestBookPage) { async fn process(&self, suggestion: &GuestBookPage) {
println!("{} -- {} <{}>", suggestion.body, suggestion.author.as_deref().unwrap_or("anon"), suggestion.contact.as_deref().unwrap_or("")); println!("{} -- {} <{}>", suggestion.body, suggestion.author, suggestion.contact.as_deref().unwrap_or(""));
} }
} }

View file

@ -41,28 +41,25 @@ impl Context {
} }
async fn send_suggestion(unsafe_payload: Insertion, state: SafeContext) -> Result<Redirect, String> { async fn send_suggestion(unsafe_payload: Insertion, state: SafeContext) -> Result<Redirect, String> {
// sanitize all user input! we don't want XSS or html injections!
let payload = unsafe_payload.sanitize(); let payload = unsafe_payload.sanitize();
// limit author and contact fields to 25 and 50 characters, TODO don't hardcode limits
let contact_limited = payload.contact.clone().map(|x| limit_string(&x, 50));
let author_limited = payload.author.map(|x| limit_string(&x, 25));
// calculate contact hash for libravatar
let mut hasher = Md5::new(); let mut hasher = Md5::new();
let id = payload.contact.clone().unwrap_or(Uuid::new_v4().to_string()); hasher.update(contact_limited.as_deref().unwrap_or(&Uuid::new_v4().to_string()).as_bytes());
hasher.update(id.as_bytes());
let avatar = hasher.finalize(); let avatar = hasher.finalize();
// populate guestbook page struct
let page = GuestBookPage { let page = GuestBookPage {
avatar: format!("{:x}", avatar), avatar: format!("{:x}", avatar),
author: payload.author.map(|x| x.chars().take(25).collect()), // TODO don't hardcode char limits! author: author_limited.unwrap_or("anonymous".to_string()),
contact: payload.contact.clone().map(|x| x.chars().take(50).collect()), url: url_from_contact(contact_limited.clone()),
contact: contact_limited,
body: payload.body, body: payload.body,
date: Utc::now(), date: Utc::now(),
url: match payload.contact {
None => None,
Some(c) => if c.starts_with("http") {
Some(c)
} else if c.contains('@') {
Some(format!("mailto:{}", c))
} else {
None
}
},
}; };
// lock state, process and archive new page
let mut lock = state.write().await; let mut lock = state.write().await;
lock.process(&page).await; lock.process(&page).await;
match lock.storage.archive(page).await { match lock.storage.archive(page).await {
@ -84,3 +81,23 @@ async fn get_suggestion(State(state): State<SafeContext>, Query(page): Query<Pag
Err(e) => Err(e.to_string()), Err(e) => Err(e.to_string()),
} }
} }
fn url_from_contact(contact: Option<String>) -> Option<String> {
match contact {
None => None,
Some(c) => if c.starts_with("http") {
Some(c)
} else if c.contains('@') {
Some(format!("mailto:{}", c))
} else if c.contains('.') {
Some(format!("https://{}", c))
} else {
None
}
}
}
fn limit_string(s: &str, l: usize) -> String {
// TODO is there a better way? slicing doesn't work when l > s.len
s.chars().take(l).collect()
}