Browse Source

Implement upload filename sanitisation

Daniel Szabo 3 years ago
parent
commit
7b4cd7c26e
5 changed files with 57 additions and 22 deletions
  1. 21 9
      src/endpoints/create.rs
  2. 22 5
      src/pasta.rs
  3. 10 5
      src/util/misc.rs
  4. 3 2
      templates/pasta.html
  5. 1 1
      templates/pastalist.html

+ 21 - 9
src/endpoints/create.rs

@@ -8,6 +8,7 @@ use actix_web::{get, web, Error, HttpResponse, Responder};
 use askama::Template;
 use askama::Template;
 use bytesize::ByteSize;
 use bytesize::ByteSize;
 use futures::TryStreamExt;
 use futures::TryStreamExt;
+use log::warn;
 use rand::Rng;
 use rand::Rng;
 use std::io::Write;
 use std::io::Write;
 use std::time::{SystemTime, UNIX_EPOCH};
 use std::time::{SystemTime, UNIX_EPOCH};
@@ -105,30 +106,41 @@ pub async fn create(
                 continue;
                 continue;
             }
             }
             "file" => {
             "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("") => continue,
-                    Some(filename) => filename.replace(' ', "_").to_string(),
+                    Some(p) => p,
                     None => continue,
                     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()))
                 std::fs::create_dir_all(format!("./pasta_data/{}", &new_pasta.id_as_animals()))
                     .unwrap();
                     .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;
                 let mut size = 0;
                 while let Some(chunk) = field.try_next().await? {
                 while let Some(chunk) = field.try_next().await? {
                     size += chunk.len();
                     size += chunk.len();
                     f = web::block(move || f.write_all(&chunk).map(|_| f)).await??;
                     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");
                 new_pasta.pasta_type = String::from("text");
             }
             }
             _ => {}
             _ => {}

+ 22 - 5
src/pasta.rs

@@ -1,15 +1,32 @@
-use std::fmt;
-
-use chrono::{Datelike, Timelike, Local, TimeZone};
-use serde::{Deserialize, Serialize};
 use bytesize::ByteSize;
 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::animalnumbers::to_animal_names;
 use crate::util::syntaxhighlighter::html_highlight;
 use crate::util::syntaxhighlighter::html_highlight;
 
 
 #[derive(Serialize, Deserialize, PartialEq, Eq)]
 #[derive(Serialize, Deserialize, PartialEq, Eq)]
 pub struct PastaFile {
 pub struct PastaFile {
-    pub name: String, pub size: ByteSize ,
+    pub name: String,
+    pub size: ByteSize,
+}
+
+impl PastaFile {
+    pub fn from_unsanitized(path: &str) -> Result<Self, &'static str> {
+        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)]
 #[derive(Serialize, Deserialize)]

+ 10 - 5
src/util/misc.rs

@@ -3,7 +3,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
 use linkify::{LinkFinder, LinkKind};
 use linkify::{LinkFinder, LinkKind};
 use std::fs;
 use std::fs;
 
 
-use crate::{dbio, pasta::PastaFile, Pasta};
+use crate::{dbio, Pasta};
 
 
 pub fn remove_expired(pastas: &mut Vec<Pasta>) {
 pub fn remove_expired(pastas: &mut Vec<Pasta>) {
     // get current time - this will be needed to check which pastas have expired
     // get current time - this will be needed to check which pastas have expired
@@ -22,15 +22,20 @@ pub fn remove_expired(pastas: &mut Vec<Pasta>) {
             true
             true
         } else {
         } else {
             // remove the file itself
             // 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
                 // and remove the containing directory
                 if fs::remove_dir(format!("./pasta_data/{}/", p.id_as_animals())).is_err() {
                 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
             false

+ 3 - 2
templates/pasta.html

@@ -3,8 +3,9 @@
   <a style="margin-right: 0.5rem" href="/raw/{{pasta.id_as_animals()}}">Raw Text Content</a>
   <a style="margin-right: 0.5rem" href="/raw/{{pasta.id_as_animals()}}">Raw Text Content</a>
   {% if pasta.file.is_some() %}
   {% if pasta.file.is_some() %}
   <a style="margin-right: 0.5rem; margin-left: 0.5rem"
   <a style="margin-right: 0.5rem; margin-left: 0.5rem"
-    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}}]</a>
+    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}}]
+  </a>
   {%- endif %}
   {%- endif %}
   {% if pasta.editable %}
   {% if pasta.editable %}
   <a style="margin-right: 0.5rem; margin-left: 0.5rem" href="/edit/{{pasta.id_as_animals()}}">Edit</a>
   <a style="margin-right: 0.5rem; margin-left: 0.5rem" href="/edit/{{pasta.id_as_animals()}}">Edit</a>

+ 1 - 1
templates/pastalist.html

@@ -49,7 +49,7 @@
         <td>
         <td>
             <a style="margin-right:1rem" href="/raw/{{pasta.id_as_animals()}}">Raw</a>
             <a style="margin-right:1rem" href="/raw/{{pasta.id_as_animals()}}">Raw</a>
             {% if pasta.file.is_some() %}
             {% if pasta.file.is_some() %}
-            <a style="margin-right:1rem" href="/file/{{pasta.id_as_animals()}}/{{pasta.file.as_ref().unwrap().name}}">File</a>
+            <a style="margin-right:1rem" href="/file/{{pasta.id_as_animals()}}/{{pasta.file.as_ref().unwrap().name()}}">File</a>
             {%- endif %}
             {%- endif %}
             {% if pasta.editable %}
             {% if pasta.editable %}
             <a style="margin-right:1rem" href="/edit/{{pasta.id_as_animals()}}">Edit</a>
             <a style="margin-right:1rem" href="/edit/{{pasta.id_as_animals()}}">Edit</a>