mirror of https://github.com/raftario/filite.git
Always use multipart for files
This commit is contained in:
parent
d6bc31d8ca
commit
f1c40f4748
|
@ -2,7 +2,6 @@ PORT=8080
|
||||||
DATABASE_URL=target/database.db
|
DATABASE_URL=target/database.db
|
||||||
POOL_SIZE=4
|
POOL_SIZE=4
|
||||||
FILES_DIR=target/static/
|
FILES_DIR=target/static/
|
||||||
MAX_FILESIZE=50000000
|
|
||||||
|
|
||||||
PASSWD=a1b2c3d4
|
PASSWD=a1b2c3d4
|
||||||
|
|
||||||
|
|
|
@ -55,8 +55,6 @@ database_url = "database.db"
|
||||||
pool_size = 4
|
pool_size = 4
|
||||||
# Path to the directory where files will be stored, relative or absolute
|
# Path to the directory where files will be stored, relative or absolute
|
||||||
files_dir = "files"
|
files_dir = "files"
|
||||||
# Max allowed size for file uploads, in bytes
|
|
||||||
max_filesize = 10000000
|
|
||||||
|
|
||||||
# Highlight.js configuration
|
# Highlight.js configuration
|
||||||
[highlight]
|
[highlight]
|
||||||
|
|
|
@ -359,18 +359,15 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let fileReader = new FileReader();
|
const fd = new FormData();
|
||||||
fileReader.onload = () => {
|
fd.append("file", file);
|
||||||
const id = urlInput.value;
|
const id = urlInput.value;
|
||||||
const url = `${baseUrl}f/${id}`;
|
const url = `${baseUrl}f/${id}`;
|
||||||
|
|
||||||
const base64 = btoa(fileReader.result);
|
|
||||||
const filename = file.name;
|
|
||||||
let status;
|
let status;
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
body: fd,
|
||||||
body: JSON.stringify({ base64, filename }),
|
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
status = response.status;
|
status = response.status;
|
||||||
|
@ -386,8 +383,6 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => alert(error));
|
.catch((error) => alert(error));
|
||||||
};
|
|
||||||
fileReader.readAsBinaryString(file);
|
|
||||||
});
|
});
|
||||||
} else if (group === "links") {
|
} else if (group === "links") {
|
||||||
submitButton.addEventListener("click", () => {
|
submitButton.addEventListener("click", () => {
|
||||||
|
@ -398,7 +393,6 @@
|
||||||
let status;
|
let status;
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ forward }),
|
body: JSON.stringify({ forward }),
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
@ -426,7 +420,6 @@
|
||||||
let status;
|
let status;
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ contents, highlight }),
|
body: JSON.stringify({ contents, highlight }),
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -9,7 +9,7 @@ extern crate serde;
|
||||||
extern crate diesel_migrations;
|
extern crate diesel_migrations;
|
||||||
|
|
||||||
use actix_identity::{CookieIdentityPolicy, IdentityService};
|
use actix_identity::{CookieIdentityPolicy, IdentityService};
|
||||||
use actix_web::{web, App, FromRequest, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
use diesel::{
|
use diesel::{
|
||||||
r2d2::{self, ConnectionManager},
|
r2d2::{self, ConnectionManager},
|
||||||
sqlite::SqliteConnection,
|
sqlite::SqliteConnection,
|
||||||
|
@ -53,7 +53,7 @@ async fn main() {
|
||||||
#[cfg(not(feature = "dev"))]
|
#[cfg(not(feature = "dev"))]
|
||||||
{
|
{
|
||||||
embedded_migrations::run(&pool.get().unwrap()).unwrap_or_else(|e| {
|
embedded_migrations::run(&pool.get().unwrap()).unwrap_or_else(|e| {
|
||||||
eprintln!("Can't prepare database: {}.", e);
|
eprintln!("Can't prepare database: {}", e);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -76,8 +76,6 @@ async fn main() {
|
||||||
};
|
};
|
||||||
|
|
||||||
let port = config.port;
|
let port = config.port;
|
||||||
let max_filesize_json = (config.max_filesize as f64 * 1.37) as usize;
|
|
||||||
|
|
||||||
println!("Listening on port {}", port);
|
println!("Listening on port {}", port);
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
|
@ -111,9 +109,6 @@ async fn main() {
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/f/{id}")
|
web::resource("/f/{id}")
|
||||||
.data(web::Json::<routes::files::PutFile>::configure(|cfg| {
|
|
||||||
cfg.limit(max_filesize_json)
|
|
||||||
}))
|
|
||||||
.route(web::get().to(routes::files::get))
|
.route(web::get().to(routes::files::get))
|
||||||
.route(web::put().to(routes::files::put))
|
.route(web::put().to(routes::files::put))
|
||||||
.route(web::delete().to(routes::files::delete)),
|
.route(web::delete().to(routes::files::delete)),
|
||||||
|
@ -133,13 +128,13 @@ async fn main() {
|
||||||
})
|
})
|
||||||
.bind(&format!("localhost:{}", port))
|
.bind(&format!("localhost:{}", port))
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
eprintln!("Can't bind webserver to specified port: {}.", e);
|
eprintln!("Can't bind webserver to specified port: {}", e);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
})
|
})
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
eprintln!("Can't start webserver: {}.", e);
|
eprintln!("Can't start webserver: {}", e);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
175
src/routes.rs
175
src/routes.rs
|
@ -83,20 +83,8 @@ async fn auth(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Match result from REPLACE queries for PUT routes
|
/// Match result from REPLACE queries
|
||||||
fn match_replace_result_put<T: Serialize>(
|
fn match_replace_result<T: Serialize>(
|
||||||
result: Result<T, BlockingError<diesel::result::Error>>,
|
|
||||||
) -> Result<HttpResponse, Error> {
|
|
||||||
match result {
|
|
||||||
Ok(x) => Ok(HttpResponse::Created().json(x)),
|
|
||||||
Err(_) => Err(HttpResponse::InternalServerError()
|
|
||||||
.body("Internal server error")
|
|
||||||
.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Match result from REPLACE queries for POST routes
|
|
||||||
fn match_replace_result_post<T: Serialize>(
|
|
||||||
result: Result<T, BlockingError<diesel::result::Error>>,
|
result: Result<T, BlockingError<diesel::result::Error>>,
|
||||||
id: i32,
|
id: i32,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
|
@ -286,6 +274,7 @@ pub async fn logout(identity: Identity) -> impl Responder {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod files {
|
pub mod files {
|
||||||
|
use crate::routes::match_replace_result;
|
||||||
use crate::{
|
use crate::{
|
||||||
queries::{self, SelectQuery},
|
queries::{self, SelectQuery},
|
||||||
routes::{auth, match_find_error, parse_id},
|
routes::{auth, match_find_error, parse_id},
|
||||||
|
@ -328,17 +317,15 @@ pub mod files {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request body when PUTting files
|
/// Common code for PUT and POST routes
|
||||||
#[derive(Deserialize)]
|
async fn put_post(
|
||||||
pub struct PutFile {
|
id: i32,
|
||||||
pub base64: String,
|
mut body: Multipart,
|
||||||
pub filename: String,
|
pool: web::Data<Pool>,
|
||||||
}
|
config: web::Data<Config>,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
/// Common setup for both PUT and POST
|
let mut path = config.files_dir.clone();
|
||||||
async fn setup(config: &Config) -> Result<(PathBuf, PathBuf), Error> {
|
let mut relative_path = PathBuf::new();
|
||||||
let path = config.files_dir.clone();
|
|
||||||
let relative_path = PathBuf::new();
|
|
||||||
let dir_path = path.clone();
|
let dir_path = path.clone();
|
||||||
if web::block(move || fs::create_dir_all(dir_path))
|
if web::block(move || fs::create_dir_all(dir_path))
|
||||||
.await
|
.await
|
||||||
|
@ -349,78 +336,6 @@ pub mod files {
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((path, relative_path))
|
|
||||||
}
|
|
||||||
/// Common conversion for both PUT and POST
|
|
||||||
fn pts(path: &PathBuf) -> Result<String, Error> {
|
|
||||||
match path.to_str() {
|
|
||||||
Some(rp) => Ok(rp.to_owned()),
|
|
||||||
None => Err(HttpResponse::InternalServerError()
|
|
||||||
.body("Internal server error")
|
|
||||||
.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// PUT a new file entry
|
|
||||||
pub async fn put(
|
|
||||||
request: HttpRequest,
|
|
||||||
path: web::Path<String>,
|
|
||||||
body: web::Json<PutFile>,
|
|
||||||
pool: web::Data<Pool>,
|
|
||||||
config: web::Data<Config>,
|
|
||||||
identity: Identity,
|
|
||||||
password_hash: web::Data<Vec<u8>>,
|
|
||||||
) -> Result<HttpResponse, Error> {
|
|
||||||
auth(identity, request, &password_hash).await?;
|
|
||||||
|
|
||||||
let id = parse_id(&path)?;
|
|
||||||
let (mut path, mut relative_path) = setup(&config).await?;
|
|
||||||
|
|
||||||
let mut filename = body.filename.clone();
|
|
||||||
filename = format!("{:x}.{}", Utc::now().timestamp(), filename);
|
|
||||||
path.push(&filename);
|
|
||||||
relative_path.push(&filename);
|
|
||||||
let relative_path = pts(&relative_path)?;
|
|
||||||
|
|
||||||
let contents = match web::block(move || base64::decode(&body.base64)).await {
|
|
||||||
Ok(contents) => contents,
|
|
||||||
Err(_) => {
|
|
||||||
return Err(HttpResponse::BadRequest()
|
|
||||||
.body("Invalid base64 encoded file")
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if web::block(move || fs::write(&path, contents))
|
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
return Err(HttpResponse::InternalServerError()
|
|
||||||
.body("Internal server error")
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
match web::block(move || queries::files::replace(id, &relative_path, pool)).await {
|
|
||||||
Ok(file) => Ok(HttpResponse::Created().json(file)),
|
|
||||||
Err(_) => Err(HttpResponse::InternalServerError()
|
|
||||||
.body("Internal server error")
|
|
||||||
.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// POST a new file entry using a multipart body
|
|
||||||
pub async fn post(
|
|
||||||
request: HttpRequest,
|
|
||||||
mut body: Multipart,
|
|
||||||
pool: web::Data<Pool>,
|
|
||||||
config: web::Data<Config>,
|
|
||||||
identity: Identity,
|
|
||||||
password_hash: web::Data<Vec<u8>>,
|
|
||||||
) -> Result<HttpResponse, Error> {
|
|
||||||
auth(identity, request, &password_hash).await?;
|
|
||||||
|
|
||||||
let id = random_id(&pool).await?;
|
|
||||||
let (mut path, mut relative_path) = setup(&config).await?;
|
|
||||||
|
|
||||||
let mut field = match body.next().await {
|
let mut field = match body.next().await {
|
||||||
Some(f) => f?,
|
Some(f) => f?,
|
||||||
None => {
|
None => {
|
||||||
|
@ -444,7 +359,14 @@ pub mod files {
|
||||||
let filename = format!("{:x}.{}", Utc::now().timestamp(), filename);
|
let filename = format!("{:x}.{}", Utc::now().timestamp(), filename);
|
||||||
path.push(&filename);
|
path.push(&filename);
|
||||||
relative_path.push(&filename);
|
relative_path.push(&filename);
|
||||||
let relative_path = pts(&relative_path)?;
|
let relative_path = match path.to_str() {
|
||||||
|
Some(rp) => rp.to_owned(),
|
||||||
|
None => {
|
||||||
|
return Err(HttpResponse::InternalServerError()
|
||||||
|
.body("Internal server error")
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut f = match web::block(move || File::create(&path)).await {
|
let mut f = match web::block(move || File::create(&path)).await {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
|
@ -479,12 +401,39 @@ pub mod files {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
match web::block(move || queries::files::replace(id, &relative_path, pool)).await {
|
match_replace_result(
|
||||||
Ok(_) => Ok(HttpResponse::Created().body(format!("{}", radix_fmt::radix_36(id)))),
|
web::block(move || queries::files::replace(id, &relative_path, pool)).await,
|
||||||
Err(_) => Err(HttpResponse::InternalServerError()
|
id,
|
||||||
.body("Internal server error")
|
)
|
||||||
.into()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// PUT a new file entry
|
||||||
|
pub async fn put(
|
||||||
|
request: HttpRequest,
|
||||||
|
path: web::Path<String>,
|
||||||
|
body: Multipart,
|
||||||
|
pool: web::Data<Pool>,
|
||||||
|
config: web::Data<Config>,
|
||||||
|
identity: Identity,
|
||||||
|
password_hash: web::Data<Vec<u8>>,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
|
auth(identity, request, &password_hash).await?;
|
||||||
|
let id = parse_id(&path)?;
|
||||||
|
put_post(id, body, pool, config).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// POST a new file entry using a multipart body
|
||||||
|
pub async fn post(
|
||||||
|
request: HttpRequest,
|
||||||
|
body: Multipart,
|
||||||
|
pool: web::Data<Pool>,
|
||||||
|
config: web::Data<Config>,
|
||||||
|
identity: Identity,
|
||||||
|
password_hash: web::Data<Vec<u8>>,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
|
auth(identity, request, &password_hash).await?;
|
||||||
|
let id = random_id(&pool).await?;
|
||||||
|
put_post(id, body, pool, config).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,8 +441,7 @@ pub mod links {
|
||||||
use crate::{
|
use crate::{
|
||||||
queries::{self, SelectQuery},
|
queries::{self, SelectQuery},
|
||||||
routes::{
|
routes::{
|
||||||
auth, match_find_error, match_replace_result_post, match_replace_result_put, parse_id,
|
auth, match_find_error, match_replace_result, parse_id, timestamp_to_last_modified,
|
||||||
timestamp_to_last_modified,
|
|
||||||
},
|
},
|
||||||
Pool,
|
Pool,
|
||||||
};
|
};
|
||||||
|
@ -535,10 +483,10 @@ pub mod links {
|
||||||
password_hash: web::Data<Vec<u8>>,
|
password_hash: web::Data<Vec<u8>>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
auth(identity, request, &password_hash).await?;
|
auth(identity, request, &password_hash).await?;
|
||||||
|
|
||||||
let id = parse_id(&path)?;
|
let id = parse_id(&path)?;
|
||||||
match_replace_result_put(
|
match_replace_result(
|
||||||
web::block(move || queries::links::replace(id, &body.forward, pool)).await,
|
web::block(move || queries::links::replace(id, &body.forward, pool)).await,
|
||||||
|
id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,9 +499,8 @@ pub mod links {
|
||||||
password_hash: web::Data<Vec<u8>>,
|
password_hash: web::Data<Vec<u8>>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
auth(identity, request, &password_hash).await?;
|
auth(identity, request, &password_hash).await?;
|
||||||
|
|
||||||
let id = random_id(&pool).await?;
|
let id = random_id(&pool).await?;
|
||||||
match_replace_result_post(
|
match_replace_result(
|
||||||
web::block(move || queries::links::replace(id, &body.forward, pool)).await,
|
web::block(move || queries::links::replace(id, &body.forward, pool)).await,
|
||||||
id,
|
id,
|
||||||
)
|
)
|
||||||
|
@ -565,8 +512,7 @@ pub mod texts {
|
||||||
use crate::{
|
use crate::{
|
||||||
queries::{self, SelectQuery},
|
queries::{self, SelectQuery},
|
||||||
routes::{
|
routes::{
|
||||||
auth, match_find_error, match_replace_result_post, match_replace_result_put, parse_id,
|
auth, match_find_error, match_replace_result, parse_id, timestamp_to_last_modified,
|
||||||
timestamp_to_last_modified,
|
|
||||||
},
|
},
|
||||||
Pool,
|
Pool,
|
||||||
};
|
};
|
||||||
|
@ -636,11 +582,11 @@ pub mod texts {
|
||||||
password_hash: web::Data<Vec<u8>>,
|
password_hash: web::Data<Vec<u8>>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
auth(identity, request, &password_hash).await?;
|
auth(identity, request, &password_hash).await?;
|
||||||
|
|
||||||
let id = parse_id(&path)?;
|
let id = parse_id(&path)?;
|
||||||
match_replace_result_put(
|
match_replace_result(
|
||||||
web::block(move || queries::texts::replace(id, &body.contents, body.highlight, pool))
|
web::block(move || queries::texts::replace(id, &body.contents, body.highlight, pool))
|
||||||
.await,
|
.await,
|
||||||
|
id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -653,9 +599,8 @@ pub mod texts {
|
||||||
password_hash: web::Data<Vec<u8>>,
|
password_hash: web::Data<Vec<u8>>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
auth(identity, request, &password_hash).await?;
|
auth(identity, request, &password_hash).await?;
|
||||||
|
|
||||||
let id = random_id(&pool).await?;
|
let id = random_id(&pool).await?;
|
||||||
match_replace_result_post(
|
match_replace_result(
|
||||||
web::block(move || queries::texts::replace(id, &body.contents, body.highlight, pool))
|
web::block(move || queries::texts::replace(id, &body.contents, body.highlight, pool))
|
||||||
.await,
|
.await,
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -86,8 +86,6 @@ pub struct Config {
|
||||||
pub pool_size: u32,
|
pub pool_size: u32,
|
||||||
/// Directory where to store static files
|
/// Directory where to store static files
|
||||||
pub files_dir: PathBuf,
|
pub files_dir: PathBuf,
|
||||||
/// Maximum allowed file size
|
|
||||||
pub max_filesize: usize,
|
|
||||||
/// Highlight.js configuration
|
/// Highlight.js configuration
|
||||||
pub highlight: HighlightConfig,
|
pub highlight: HighlightConfig,
|
||||||
}
|
}
|
||||||
|
@ -113,14 +111,12 @@ impl Default for Config {
|
||||||
};
|
};
|
||||||
let pool_size = std::cmp::max(1, num_cpus::get() as u32 / 2);
|
let pool_size = std::cmp::max(1, num_cpus::get() as u32 / 2);
|
||||||
let files_dir = get_data_dir().join("files");
|
let files_dir = get_data_dir().join("files");
|
||||||
let max_filesize = 10_000_000;
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
port,
|
port,
|
||||||
database_url,
|
database_url,
|
||||||
pool_size,
|
pool_size,
|
||||||
files_dir,
|
files_dir,
|
||||||
max_filesize,
|
|
||||||
highlight: HighlightConfig::default(),
|
highlight: HighlightConfig::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,14 +207,12 @@ impl Config {
|
||||||
.expect("Invalid FILES_DIR")
|
.expect("Invalid FILES_DIR")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let max_filesize = parse_env!("MAX_FILESIZE");
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
port,
|
port,
|
||||||
database_url,
|
database_url,
|
||||||
pool_size,
|
pool_size,
|
||||||
files_dir,
|
files_dir,
|
||||||
max_filesize,
|
|
||||||
highlight: HighlightConfig::default(),
|
highlight: HighlightConfig::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue