feat(paste): support disappearing (oneshot) files

This commit is contained in:
orhun 2021-08-26 22:57:46 +03:00
parent 3223c6379c
commit 73359f3534
No known key found for this signature in database
GPG key ID: F83424824B3E4B90
3 changed files with 74 additions and 26 deletions

View file

@ -15,7 +15,9 @@ async fn main() -> IoResult<()> {
.expect("failed to parse config"); .expect("failed to parse config");
let server_config = config.server.clone(); let server_config = config.server.clone();
fs::create_dir_all(&server_config.upload_path)?; fs::create_dir_all(&server_config.upload_path)?;
fs::create_dir_all(PasteType::Url.get_path(&server_config.upload_path))?; for paste_type in &[PasteType::Url, PasteType::Oneshot, PasteType::Trash] {
fs::create_dir_all(paste_type.get_path(&server_config.upload_path))?;
}
let mut http_server = HttpServer::new(move || { let mut http_server = HttpServer::new(move || {
App::new() App::new()
.data(config.clone()) .data(config.clone())

View file

@ -8,12 +8,16 @@ use std::str;
use url::Url; use url::Url;
/// Type of the data to store. /// Type of the data to store.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum PasteType { pub enum PasteType {
/// Any type of file. /// Any type of file.
File, File,
/// A file that allowed to be accessed once.
Oneshot,
/// A file that only contains an URL. /// A file that only contains an URL.
Url, Url,
/// A file that is expired or deleted.
Trash,
} }
impl<'a> TryFrom<&'a ContentDisposition> for PasteType { impl<'a> TryFrom<&'a ContentDisposition> for PasteType {
@ -21,6 +25,8 @@ impl<'a> TryFrom<&'a ContentDisposition> for PasteType {
fn try_from(content_disposition: &'a ContentDisposition) -> Result<Self, Self::Error> { fn try_from(content_disposition: &'a ContentDisposition) -> Result<Self, Self::Error> {
if content_disposition.has_form_field("file") { if content_disposition.has_form_field("file") {
Ok(Self::File) Ok(Self::File)
} else if content_disposition.has_form_field("oneshot") {
Ok(Self::Oneshot)
} else if content_disposition.has_form_field("url") { } else if content_disposition.has_form_field("url") {
Ok(Self::Url) Ok(Self::Url)
} else { } else {
@ -34,13 +40,25 @@ impl PasteType {
pub fn get_dir(&self) -> String { pub fn get_dir(&self) -> String {
match self { match self {
Self::File => String::new(), Self::File => String::new(),
Self::Oneshot => String::from("oneshot"),
Self::Url => String::from("url"), Self::Url => String::from("url"),
Self::Trash => String::from("trash"),
} }
} }
/// Returns the given path with [`directory`](Self::get_dir) adjoined. /// Returns the given path with [`directory`](Self::get_dir) adjoined.
pub fn get_path(&self, path: &Path) -> PathBuf { pub fn get_path(&self, path: &Path) -> PathBuf {
path.join(self.get_dir()) let dir = self.get_dir();
if dir.is_empty() {
path.to_path_buf()
} else {
path.join(dir)
}
}
/// Returns `true` if the variant is [`Oneshot`](Self::Oneshot).
pub fn is_oneshot(&self) -> bool {
self == &Self::Oneshot
} }
} }
@ -83,7 +101,10 @@ impl Paste {
Some(v) => v.to_string(), Some(v) => v.to_string(),
None => String::from("file"), None => String::from("file"),
}; };
let mut path = config.server.upload_path.join(file_name); let mut path = self
.type_
.get_path(&config.server.upload_path)
.join(file_name);
match path.clone().extension() { match path.clone().extension() {
Some(extension) => { Some(extension) => {
if let Some(file_name) = config.paste.random_url.generate() { if let Some(file_name) = config.paste.random_url.generate() {
@ -189,17 +210,22 @@ mod tests {
); );
fs::remove_file(file_name)?; fs::remove_file(file_name)?;
for paste_type in &[PasteType::Url, PasteType::Oneshot] {
fs::create_dir_all(paste_type.get_path(&config.server.upload_path))?;
}
config.paste.random_url.enabled = false; config.paste.random_url.enabled = false;
let paste = Paste { let paste = Paste {
data: vec![116, 101, 115, 116], data: vec![116, 101, 115, 116],
type_: PasteType::File, type_: PasteType::Oneshot,
}; };
let file_name = paste.store_file("test.file", &config)?; let file_name = paste.store_file("test.file", &config)?;
let file_path = PasteType::Oneshot
.get_path(&config.server.upload_path)
.join(&file_name);
assert_eq!("test.file", &file_name); assert_eq!("test.file", &file_name);
assert_eq!("test", fs::read_to_string(&file_name)?); assert_eq!("test", fs::read_to_string(&file_path)?);
fs::remove_file(file_name)?; fs::remove_file(file_path)?;
fs::create_dir_all(PasteType::Url.get_path(&config.server.upload_path))?;
config.paste.random_url.enabled = true; config.paste.random_url.enabled = true;
let url = String::from("https://orhun.dev/"); let url = String::from("https://orhun.dev/");
@ -221,7 +247,9 @@ mod tests {
}; };
assert!(paste.store_url(&config).is_err()); assert!(paste.store_url(&config).is_err());
fs::remove_dir(PasteType::Url.get_path(&config.server.upload_path))?; for paste_type in &[PasteType::Url, PasteType::Oneshot] {
fs::remove_dir(paste_type.get_path(&config.server.upload_path))?;
}
Ok(()) Ok(())
} }

View file

@ -29,27 +29,42 @@ async fn serve(
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let mut path = config.server.upload_path.join(&*file); let mut path = config.server.upload_path.join(&*file);
let mut paste_type = PasteType::File; let mut paste_type = PasteType::File;
for type_ in &[PasteType::Url] { if !path.exists() || path.is_dir() {
if !path.exists() for type_ in &[PasteType::Url, PasteType::Oneshot] {
|| path.file_name().map(|v| v.to_str()).flatten() == Some(&type_.get_dir()) let alt_path = type_.get_path(&config.server.upload_path).join(&*file);
{ if alt_path.exists()
path = config.server.upload_path.join(type_.get_dir()).join(&*file); || path.file_name().map(|v| v.to_str()).flatten() == Some(&type_.get_dir())
paste_type = *type_; {
break; path = alt_path;
paste_type = *type_;
break;
}
} }
} }
match paste_type { match paste_type {
PasteType::File => Ok(NamedFile::open(&path)? PasteType::File | PasteType::Oneshot => {
.disable_content_disposition() let response = NamedFile::open(&path)?
.set_content_type( .disable_content_disposition()
mime::get_mime_type(&config.paste.mime_override, file.to_string()) .set_content_type(
.map_err(error::ErrorInternalServerError)?, mime::get_mime_type(&config.paste.mime_override, file.to_string())
) .map_err(error::ErrorInternalServerError)?,
.prefer_utf8(true) )
.into_response(&request)?), .prefer_utf8(true)
.into_response(&request)?;
if paste_type.is_oneshot() {
fs::rename(
path,
PasteType::Trash
.get_path(&config.server.upload_path)
.join(&*file),
)?;
}
Ok(response)
}
PasteType::Url => Ok(HttpResponse::Found() PasteType::Url => Ok(HttpResponse::Found()
.header("Location", fs::read_to_string(&path)?) .header("Location", fs::read_to_string(&path)?)
.finish()), .finish()),
PasteType::Trash => unreachable!(),
} }
} }
@ -86,8 +101,11 @@ async fn upload(
type_: paste_type, type_: paste_type,
}; };
let file_name = match paste_type { let file_name = match paste_type {
PasteType::File => paste.store_file(content.get_file_name()?, &config)?, PasteType::File | PasteType::Oneshot => {
paste.store_file(content.get_file_name()?, &config)?
}
PasteType::Url => paste.store_url(&config)?, PasteType::Url => paste.store_url(&config)?,
PasteType::Trash => unreachable!(),
}; };
log::info!("{} ({}) is uploaded from {}", file_name, bytes_unit, host); log::info!("{} ({}) is uploaded from {}", file_name, bytes_unit, host);
urls.push(format!( urls.push(format!(