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");
let server_config = config.server.clone();
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 || {
App::new()
.data(config.clone())

View file

@ -8,12 +8,16 @@ use std::str;
use url::Url;
/// Type of the data to store.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PasteType {
/// Any type of file.
File,
/// A file that allowed to be accessed once.
Oneshot,
/// A file that only contains an URL.
Url,
/// A file that is expired or deleted.
Trash,
}
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> {
if content_disposition.has_form_field("file") {
Ok(Self::File)
} else if content_disposition.has_form_field("oneshot") {
Ok(Self::Oneshot)
} else if content_disposition.has_form_field("url") {
Ok(Self::Url)
} else {
@ -34,13 +40,25 @@ impl PasteType {
pub fn get_dir(&self) -> String {
match self {
Self::File => String::new(),
Self::Oneshot => String::from("oneshot"),
Self::Url => String::from("url"),
Self::Trash => String::from("trash"),
}
}
/// Returns the given path with [`directory`](Self::get_dir) adjoined.
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(),
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() {
Some(extension) => {
if let Some(file_name) = config.paste.random_url.generate() {
@ -189,17 +210,22 @@ mod tests {
);
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;
let paste = Paste {
data: vec![116, 101, 115, 116],
type_: PasteType::File,
type_: PasteType::Oneshot,
};
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", fs::read_to_string(&file_name)?);
fs::remove_file(file_name)?;
fs::create_dir_all(PasteType::Url.get_path(&config.server.upload_path))?;
assert_eq!("test", fs::read_to_string(&file_path)?);
fs::remove_file(file_path)?;
config.paste.random_url.enabled = true;
let url = String::from("https://orhun.dev/");
@ -221,7 +247,9 @@ mod tests {
};
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(())
}

View file

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