server (/fs/upload): rewrite upload functional to use multipart
This commit is contained in:
parent
7b05a6d407
commit
03af3e63b2
|
@ -107,6 +107,7 @@ dependencies = [
|
|||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"multer",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
|
@ -388,6 +389,15 @@ version = "0.3.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.2"
|
||||
|
@ -434,6 +444,21 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.21"
|
||||
|
@ -472,6 +497,12 @@ dependencies = [
|
|||
"parking_lot 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.21"
|
||||
|
@ -501,10 +532,13 @@ version = "0.3.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
|
@ -633,6 +667,7 @@ dependencies = [
|
|||
"axum-auth",
|
||||
"base64",
|
||||
"byte-unit",
|
||||
"futures",
|
||||
"homedisk-database",
|
||||
"homedisk-types",
|
||||
"hyper",
|
||||
|
@ -915,6 +950,24 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multer"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f8f35e687561d5c1667590911e6698a8cb714a134a7505718a182e7bc9d3836"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-util",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"memchr",
|
||||
"mime",
|
||||
"spin 0.9.3",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.1"
|
||||
|
|
|
@ -4,7 +4,7 @@ version = "0.0.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
axum = "0.5.4"
|
||||
axum = { version = "0.5.4", features = ["multipart"] }
|
||||
log = "0.4.17"
|
||||
thiserror = "1.0.31"
|
||||
serde = { version = "1.0.137", features = ["derive"] }
|
||||
|
@ -17,3 +17,4 @@ axum-auth = "0.2.0"
|
|||
jsonwebtoken = "8.1.0"
|
||||
base64 = "0.13.0"
|
||||
byte-unit = "4.0.14"
|
||||
futures = "0.3.21"
|
||||
|
|
|
@ -1,59 +1,76 @@
|
|||
use std::io::Write;
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use crate::fs::validate_path;
|
||||
use axum::{extract::rejection::JsonRejection, Extension, Json};
|
||||
use axum::extract::{Multipart, Query};
|
||||
use axum::{Extension, Json};
|
||||
use axum_auth::AuthBearer;
|
||||
use futures::TryStreamExt;
|
||||
use homedisk_database::Database;
|
||||
use homedisk_types::{
|
||||
config::types::Config,
|
||||
errors::{FsError, ServerError},
|
||||
fs::upload::{Request, Response},
|
||||
fs::upload::{Pagination, Response},
|
||||
};
|
||||
|
||||
use crate::middleware::{find_user, validate_json, validate_jwt};
|
||||
use crate::fs::validate_path;
|
||||
use crate::middleware::{find_user, validate_jwt};
|
||||
|
||||
pub async fn handle(
|
||||
Extension(db): Extension<Database>,
|
||||
Extension(config): Extension<Config>,
|
||||
AuthBearer(token): AuthBearer,
|
||||
request: Result<Json<Request>, JsonRejection>,
|
||||
mut multipart: Multipart,
|
||||
query: Query<Pagination>,
|
||||
) -> Result<Json<Response>, ServerError> {
|
||||
let Json(request) = validate_json::<Request>(request)?;
|
||||
let token = validate_jwt(config.jwt.secret.as_bytes(), &token)?;
|
||||
|
||||
// validate the `path` can be used
|
||||
validate_path(&request.path)?;
|
||||
validate_path(&query.path)?;
|
||||
|
||||
// search for a user by UUID from a token
|
||||
let user = find_user(db, token.claims.sub).await?;
|
||||
|
||||
// get file content
|
||||
let content = base64::decode(request.content)
|
||||
.map_err(|err| ServerError::FsError(FsError::Base64(err.to_string())))?;
|
||||
|
||||
// directory where the file will be placed
|
||||
let dir = format!(
|
||||
"{user_dir}/{req_dir}",
|
||||
// path to the file
|
||||
let file_path = format!(
|
||||
"{user_dir}/{request_path}",
|
||||
user_dir = user.user_dir(&config.storage.path),
|
||||
req_dir = request.path
|
||||
request_path = query.path
|
||||
);
|
||||
let path = Path::new(&dir);
|
||||
let file_path = Path::new(&file_path);
|
||||
|
||||
// check if the file currently exists to avoid overwriting it
|
||||
if path.exists() {
|
||||
if file_path.exists() {
|
||||
return Err(ServerError::FsError(FsError::FileAlreadyExists));
|
||||
}
|
||||
|
||||
// create a directory where the file will be placed
|
||||
// e.g. path ==> `/secret/files/images/screenshot.png`
|
||||
// directories up to `/home/homedisk/{username}/secret/files/images/` will be created
|
||||
match path.parent() {
|
||||
Some(prefix) => fs::create_dir_all(&prefix).unwrap(),
|
||||
match file_path.parent() {
|
||||
Some(prefix) => fs::create_dir_all(&prefix)
|
||||
.map_err(|err| ServerError::FsError(FsError::CreateFile(err.to_string())))?,
|
||||
None => (),
|
||||
}
|
||||
|
||||
// get multipart field
|
||||
let field = multipart
|
||||
.next_field()
|
||||
.await
|
||||
.map_err(|_| ServerError::FsError(FsError::MultipartError))?
|
||||
.ok_or(ServerError::FsError(FsError::MultipartError))?;
|
||||
|
||||
// create file
|
||||
let file = std::fs::File::create(&file_path)
|
||||
.map_err(|err| ServerError::FsError(FsError::CreateFile(err.to_string())))?;
|
||||
|
||||
// write file
|
||||
fs::write(&path, &content)
|
||||
field
|
||||
.try_fold((file, 0u64), |(mut file, written_len), bytes| async move {
|
||||
file.write_all(bytes.as_ref()).expect("write file error");
|
||||
|
||||
Ok((file, written_len + bytes.len() as u64))
|
||||
})
|
||||
.await
|
||||
.map_err(|err| ServerError::FsError(FsError::WriteFile(err.to_string())))?;
|
||||
|
||||
Ok(Json(Response { uploaded: true }))
|
||||
|
|
|
@ -5,13 +5,19 @@ pub enum Error {
|
|||
#[error("file already exists")]
|
||||
FileAlreadyExists,
|
||||
|
||||
#[error("write file error - {0}")]
|
||||
#[error("unexpected multipart error")]
|
||||
MultipartError,
|
||||
|
||||
#[error("create file - {0}")]
|
||||
CreateFile(String),
|
||||
|
||||
#[error("write file - {0}")]
|
||||
WriteFile(String),
|
||||
|
||||
#[error("base64 - {0}")]
|
||||
Base64(String),
|
||||
|
||||
#[error("read dir error - {0}")]
|
||||
#[error("read dir - {0}")]
|
||||
ReadDir(String),
|
||||
|
||||
#[error("unknow error")]
|
||||
|
|
|
@ -51,7 +51,9 @@ impl axum::response::IntoResponse for ServerError {
|
|||
AuthError::UnknownError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
},
|
||||
Self::FsError(ref err) => match err {
|
||||
FsError::MultipartError => StatusCode::BAD_REQUEST,
|
||||
FsError::FileAlreadyExists => StatusCode::BAD_REQUEST,
|
||||
FsError::CreateFile(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
FsError::WriteFile(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
FsError::Base64(_) => StatusCode::BAD_REQUEST,
|
||||
FsError::ReadDir(_) => StatusCode::BAD_REQUEST,
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Request {
|
||||
pub content: String,
|
||||
pub struct Pagination {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue