From 7b4cd7c26e6e0d66c50f1b35bbf4d60bdde4dc5b Mon Sep 17 00:00:00 2001 From: Daniel Szabo Date: Sun, 31 Jul 2022 21:31:35 +0100 Subject: [PATCH] Implement upload filename sanitisation --- src/endpoints/create.rs | 30 +++++++++++++++++++++--------- src/pasta.rs | 27 ++++++++++++++++++++++----- src/util/misc.rs | 15 ++++++++++----- templates/pasta.html | 5 +++-- templates/pastalist.html | 2 +- 5 files changed, 57 insertions(+), 22 deletions(-) diff --git a/src/endpoints/create.rs b/src/endpoints/create.rs index a3c4fe4..2b67782 100644 --- a/src/endpoints/create.rs +++ b/src/endpoints/create.rs @@ -8,6 +8,7 @@ use actix_web::{get, web, Error, HttpResponse, Responder}; use askama::Template; use bytesize::ByteSize; use futures::TryStreamExt; +use log::warn; use rand::Rng; use std::io::Write; use std::time::{SystemTime, UNIX_EPOCH}; @@ -105,30 +106,41 @@ pub async fn create( continue; } "file" => { - let content_disposition = field.content_disposition(); + let path = field.content_disposition().get_filename(); - let filename = match content_disposition.get_filename() { + let path = match path { Some("") => continue, - Some(filename) => filename.replace(' ', "_").to_string(), + Some(p) => p, None => continue, }; + let mut file = match PastaFile::from_unsanitized(&path) { + Ok(f) => f, + Err(e) => { + warn!("Unsafe file name: {e:?}"); + continue; + } + }; + std::fs::create_dir_all(format!("./pasta_data/{}", &new_pasta.id_as_animals())) .unwrap(); - let filepath = format!("./pasta_data/{}/{}", &new_pasta.id_as_animals(), &filename); - let mut f = web::block(|| std::fs::File::create(filepath)).await??; + let filepath = format!( + "./pasta_data/{}/{}", + &new_pasta.id_as_animals(), + &file.name() + ); + let mut f = web::block(|| std::fs::File::create(filepath)).await??; let mut size = 0; while let Some(chunk) = field.try_next().await? { size += chunk.len(); f = web::block(move || f.write_all(&chunk).map(|_| f)).await??; } - new_pasta.file = Some(PastaFile { - name: filename, - size: ByteSize::b(size as u64), - }); + file.size = ByteSize::b(size as u64); + + new_pasta.file = Some(file); new_pasta.pasta_type = String::from("text"); } _ => {} diff --git a/src/pasta.rs b/src/pasta.rs index c51766e..3a54070 100644 --- a/src/pasta.rs +++ b/src/pasta.rs @@ -1,15 +1,32 @@ -use std::fmt; - -use chrono::{Datelike, Timelike, Local, TimeZone}; -use serde::{Deserialize, Serialize}; use bytesize::ByteSize; +use chrono::{Datelike, Local, TimeZone, Timelike}; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::path::Path; use crate::util::animalnumbers::to_animal_names; use crate::util::syntaxhighlighter::html_highlight; #[derive(Serialize, Deserialize, PartialEq, Eq)] pub struct PastaFile { - pub name: String, pub size: ByteSize , + pub name: String, + pub size: ByteSize, +} + +impl PastaFile { + pub fn from_unsanitized(path: &str) -> Result { + let path = Path::new(path); + let name = path.file_name().ok_or("Path did not contain a file name")?; + let name = name.to_string_lossy().replace(' ', "_"); + Ok(Self { + name, + size: ByteSize::b(0), + }) + } + + pub fn name(&self) -> &str { + &self.name + } } #[derive(Serialize, Deserialize)] diff --git a/src/util/misc.rs b/src/util/misc.rs index 1d326ba..5ea3a80 100644 --- a/src/util/misc.rs +++ b/src/util/misc.rs @@ -3,7 +3,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use linkify::{LinkFinder, LinkKind}; use std::fs; -use crate::{dbio, pasta::PastaFile, Pasta}; +use crate::{dbio, Pasta}; pub fn remove_expired(pastas: &mut Vec) { // get current time - this will be needed to check which pastas have expired @@ -22,15 +22,20 @@ pub fn remove_expired(pastas: &mut Vec) { true } else { // remove the file itself - if let Some(PastaFile { name, .. }) = &p.file { - if fs::remove_file(format!("./pasta_data/{}/{}", p.id_as_animals(), name)).is_err() + if let Some(file) = &p.file { + if fs::remove_file(format!( + "./pasta_data/{}/{}", + p.id_as_animals(), + file.name() + )) + .is_err() { - log::error!("Failed to delete file {}!", name) + log::error!("Failed to delete file {}!", file.name()) } // and remove the containing directory if fs::remove_dir(format!("./pasta_data/{}/", p.id_as_animals())).is_err() { - log::error!("Failed to delete directory {}!", name) + log::error!("Failed to delete directory {}!", file.name()) } } false diff --git a/templates/pasta.html b/templates/pasta.html index 67794be..879c948 100644 --- a/templates/pasta.html +++ b/templates/pasta.html @@ -3,8 +3,9 @@ Raw Text Content {% if pasta.file.is_some() %} Attached file - '{{pasta.file.as_ref().unwrap().name}}' [{{pasta.file.as_ref().unwrap().size}}] + href="/file/{{pasta.id_as_animals()}}/{{pasta.file.as_ref().unwrap().name()}}"> + Attached file'{{pasta.file.as_ref().unwrap().name()}}' [{{pasta.file.as_ref().unwrap().size}}] + {%- endif %} {% if pasta.editable %} Edit diff --git a/templates/pastalist.html b/templates/pastalist.html index 6f6dd37..4de2d2b 100644 --- a/templates/pastalist.html +++ b/templates/pastalist.html @@ -49,7 +49,7 @@ Raw {% if pasta.file.is_some() %} - File + File {%- endif %} {% if pasta.editable %} Edit