use std::{io, mem};
use std::path::{PathBuf, Path};
use crate::Request;
use crate::http::{ContentType, Status};
use crate::data::{self, FromData, Data, Capped, N, Limits};
use crate::form::{FromFormField, ValueField, DataField, error::Errors};
use crate::outcome::IntoOutcome;
use crate::fs::FileName;
use tokio::task;
use tokio::fs::{self, File};
use tokio::io::{AsyncWriteExt, AsyncBufRead, BufReader};
use tempfile::{NamedTempFile, TempPath};
use either::Either;
#[derive(Debug)]
pub enum TempFile<'v> {
#[doc(hidden)]
File {
file_name: Option<&'v FileName>,
content_type: Option<ContentType>,
path: Either<TempPath, PathBuf>,
len: u64,
},
#[doc(hidden)]
Buffered {
content: &'v [u8],
}
}
impl<'v> TempFile<'v> {
pub async fn persist_to<P>(&mut self, path: P) -> io::Result<()>
where P: AsRef<Path>
{
let new_path = path.as_ref().to_path_buf();
match self {
TempFile::File { path: either, .. } => {
let path = mem::replace(either, Either::Right(new_path.clone()));
match path {
Either::Left(temp) => {
let result = task::spawn_blocking(move || temp.persist(new_path)).await
.map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "spawn_block"))?;
if let Err(e) = result {
*either = Either::Left(e.path);
return Err(e.error);
}
},
Either::Right(prev) => {
if let Err(e) = fs::rename(&prev, new_path).await {
*either = Either::Right(prev);
return Err(e);
}
}
}
}
TempFile::Buffered { content } => {
let mut file = File::create(&new_path).await?;
file.write_all(content).await?;
*self = TempFile::File {
file_name: None,
content_type: None,
path: Either::Right(new_path),
len: content.len() as u64
};
}
}
Ok(())
}
pub async fn copy_to<P>(&mut self, path: P) -> io::Result<()>
where P: AsRef<Path>
{
match self {
TempFile::File { path: either, .. } => {
let old_path = mem::replace(either, Either::Right(either.to_path_buf()));
match old_path {
Either::Left(temp) => {
let result = task::spawn_blocking(move || temp.keep()).await
.map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "spawn_block"))?;
if let Err(e) = result {
*either = Either::Left(e.path);
return Err(e.error);
}
},
Either::Right(_) => { }
};
tokio::fs::copy(&either, path).await?;
}
TempFile::Buffered { content } => {
let path = path.as_ref();
let mut file = File::create(path).await?;
file.write_all(content).await?;
*self = TempFile::File {
file_name: None,
content_type: None,
path: Either::Right(path.to_path_buf()),
len: content.len() as u64
};
}
}
Ok(())
}
pub async fn move_copy_to<P>(&mut self, path: P) -> io::Result<()>
where P: AsRef<Path>
{
let dest = path.as_ref();
self.copy_to(dest).await?;
if let TempFile::File { path, .. } = self {
fs::remove_file(&path).await?;
*path = Either::Right(dest.to_path_buf());
}
Ok(())
}
pub async fn open(&self) -> io::Result<impl AsyncBufRead + '_> {
use tokio_util::either::Either;
match self {
TempFile::File { path, .. } => {
let path = match path {
either::Either::Left(p) => p.as_ref(),
either::Either::Right(p) => p.as_path(),
};
let reader = BufReader::new(File::open(path).await?);
Ok(Either::Left(reader))
},
TempFile::Buffered { content } => {
Ok(Either::Right(*content))
},
}
}
pub fn len(&self) -> u64 {
match self {
TempFile::File { len, .. } => *len,
TempFile::Buffered { content } => content.len() as u64,
}
}
pub fn path(&self) -> Option<&Path> {
match self {
TempFile::File { path: Either::Left(p), .. } => Some(p.as_ref()),
TempFile::File { path: Either::Right(p), .. } => Some(p.as_path()),
TempFile::Buffered { .. } => None,
}
}
pub fn name(&self) -> Option<&str> {
self.raw_name().and_then(|f| f.as_str())
}
pub fn raw_name(&self) -> Option<&FileName> {
match *self {
TempFile::File { file_name, .. } => file_name,
TempFile::Buffered { .. } => None
}
}
pub fn content_type(&self) -> Option<&ContentType> {
match self {
TempFile::File { content_type, .. } => content_type.as_ref(),
TempFile::Buffered { .. } => None
}
}
async fn from<'a>(
req: &Request<'_>,
data: Data<'_>,
file_name: Option<&'a FileName>,
content_type: Option<ContentType>,
) -> io::Result<Capped<TempFile<'a>>> {
let limit = content_type.as_ref()
.and_then(|ct| ct.extension())
.and_then(|ext| req.limits().find(&["file", ext.as_str()]))
.or_else(|| req.limits().get("file"))
.unwrap_or(Limits::FILE);
let temp_dir = req.rocket().config().temp_dir.relative();
let file = task::spawn_blocking(move || NamedTempFile::new_in(temp_dir));
let file = file.await;
let file = file.map_err(|_| io::Error::new(io::ErrorKind::Other, "spawn_block panic"))??;
let (file, temp_path) = file.into_parts();
let mut file = File::from_std(file);
let fut = data.open(limit).stream_to(tokio::io::BufWriter::new(&mut file));
let n = fut.await;
let n = n?;
let temp_file = TempFile::File {
content_type, file_name,
path: Either::Left(temp_path),
len: n.written,
};
Ok(Capped::new(temp_file, n))
}
}
#[crate::async_trait]
impl<'v> FromFormField<'v> for Capped<TempFile<'v>> {
fn from_value(field: ValueField<'v>) -> Result<Self, Errors<'v>> {
let n = N { written: field.value.len() as u64, complete: true };
Ok(Capped::new(TempFile::Buffered { content: field.value.as_bytes() }, n))
}
async fn from_data(
f: DataField<'v, '_>
) -> Result<Self, Errors<'v>> {
Ok(TempFile::from(f.request, f.data, f.file_name, Some(f.content_type)).await?)
}
}
#[crate::async_trait]
impl<'r> FromData<'r> for Capped<TempFile<'_>> {
type Error = io::Error;
async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> data::Outcome<'r, Self> {
use yansi::Paint;
let has_form = |ty: &ContentType| ty.is_form_data() || ty.is_form();
if req.content_type().map_or(false, has_form) {
let (tf, form) = ("TempFile<'_>".primary(), "Form<TempFile<'_>>".primary());
warn_!("Request contains a form that will not be processed.");
info_!("Bare `{}` data guard writes raw, unprocessed streams to disk.", tf);
info_!("Did you mean to use `{}` instead?", form);
}
TempFile::from(req, data, None, req.content_type().cloned()).await
.or_error(Status::BadRequest)
}
}
impl_strict_from_form_field_from_capped!(TempFile<'v>);
impl_strict_from_data_from_capped!(TempFile<'_>);