-
Pierre Jarriges authoredPierre Jarriges authored
files.rs 11.79 KiB
use crate::static_files::upload::*;
use crate::website::WebSite;
use actix_files::NamedFile;
use actix_multipart::Multipart;
use actix_web::{
delete, get, post, web,
web::{Data, Path},
HttpResponse, Responder,
};
use futures::StreamExt;
use std::{
fs::{remove_file, File},
io::Write,
sync::RwLock,
};
#[get("/favicon.ico")]
pub async fn favicon(website: web::Data<RwLock<WebSite>>) -> impl Responder {
NamedFile::open(
&website
.read()
.unwrap()
.static_files_manager
.dir
.join("assets")
.join("default")
.join("favicon.ico"),
)
}
// TODO routes for robots.txt, sitemap.xml, dyn.sitemap.xml,...
fn upload_data_from_multipart_field(
field: &actix_multipart::Field,
) -> Result<UploadFileData, String> {
match field.content_disposition().get_filename() {
Some(fname) => match file_ext(&fname.to_string()) {
Ok(ext) => Ok(UploadFileData {
up_type: upload_type_from_file_ext(&ext),
filename: fname.to_owned(),
}),
Err(msg) => return Err(msg),
},
None => Err("Couldn't retrieve file extension".to_string()),
}
}
async fn write_uploaded_file(
website: &WebSite,
field: &mut actix_multipart::Field,
filename: &String,
upload_type: UploadFileType,
) -> Result<String, String> {
let root = &website.static_files_manager.assets_dir();
let sub_dir = dirname_from_type(&upload_type);
let filepath = root.join(sub_dir).join(&filename);
match File::create(&filepath) {
Err(e) => Err(format!("Error creating file {:?} : {:?}", filepath, e)),
Ok(mut f) => {
// Field in turn is stream of *Bytes* object
while let Some(chunk) = field.next().await {
match chunk {
Ok(chunk) => {
if f.write_all(&chunk).is_err() {
remove_file(&filepath).unwrap();
return Err("Error writing chunk".to_string());
}
}
Err(e) => {
return Err(format!("Error writing file {} : {:?}", filename, e));
}
}
}
Ok(filepath.into_os_string().into_string().unwrap())
}
}
}
#[post("/post-files")]
pub async fn post_files(website: Data<RwLock<WebSite>>, mut payload: Multipart) -> impl Responder {
let mut uploaded_filepathes = Vec::new();
let mut website = website
.write()
.expect("Couldn't acquire write lock for RwLock<WebSite");
while let Some(item) = payload.next().await {
match item {
Ok(mut field) => {
let up_data = upload_data_from_multipart_field(&field);
if let Err(msg) = up_data {
return HttpResponse::InternalServerError().body(msg);
}
let up_data = up_data.unwrap();
match write_uploaded_file(&website, &mut field, &up_data.filename, up_data.up_type)
.await
{
Err(msg) => return HttpResponse::InternalServerError().body(msg),
Ok(filepath) => uploaded_filepathes.extend(
website
.static_files_manager
.push_asset_path(std::path::Path::new(&filepath)),
),
}
}
Err(e) => {
return HttpResponse::InternalServerError().body(format!("FIELD ERR {:?}", e))
}
}
}
HttpResponse::Ok().json(uploaded_filepathes)
}
#[get("/static-files-index")]
async fn get_static_files_index(website: Data<RwLock<WebSite>>) -> impl Responder {
HttpResponse::Ok().json(
website
.read()
.unwrap()
.static_files_manager
.get_assets_index(),
)
}
#[delete("/delete-file/{category}/{filename}")]
async fn delete_static_file(
website: Data<RwLock<WebSite>>,
fileinfo: Path<(String, String)>,
) -> impl Responder {
let mut website = website
.write()
.expect("Couldn't acquire write lock for RwLock<WebSite");
let (cat, fname) = fileinfo.into_inner();
let fpath = std::path::PathBuf::from(cat).join(fname);
match remove_file(website.static_files_manager.dir.join(&fpath)) {
Ok(_) => {
website
.static_files_manager
.remove_path(fpath.to_string_lossy().into());
HttpResponse::Accepted().body("File was deleted")
}
Err(e) => HttpResponse::InternalServerError().body(format!("Error deleting file {:?}", e)),
}
}
#[cfg(test)]
mod test_static_files_services {
use super::*;
use crate::{app::AppArgs, cookie::*, website::*, AppState};
use actix_web::{
http::{Method, StatusCode},
test,
web::Bytes,
App,
};
use std::sync::RwLock;
fn create_simple_request() -> Bytes {
Bytes::from(
"--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
test\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"data.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
data\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n",
)
}
// fn clear_testing_static(dir: &std::path::PathBuf) {
// std::fs::remove_dir_all(dir).unwrap();
// }
#[actix_web::test]
async fn post_files_unauthenticated_should_be_unauthorized() {
let app_state = AppState::builder().use_args(AppArgs::testing()).build();
let static_dir = std::path::PathBuf::from("./test");
let ws = WebSiteBuilder::testing(&static_dir);
let mut app = test::init_service(
App::new()
.app_data(web::Data::new(RwLock::new(app_state.clone())))
.app_data(web::Data::new(RwLock::new(ws.clone())))
.wrap(crate::middleware::AuthService {})
.service(post_files),
)
.await;
let req = test::TestRequest::with_uri("/post-files")
.method(Method::POST)
.append_header((
"Content-Type",
"multipart/form-data; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
))
.cookie(SecureCookie::new(
&"wrong-cookie".to_string(),
&SecureCookie::generate_token(36),
))
.set_payload(create_simple_request())
.to_request();
let resp = test::call_service(&mut app, req).await;
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
// #[actix_web::test]
// async fn test_post_files() {
// let app_state = AppState::for_test().await;
// let admin_user = Administrator::authenticated(
// &app_state,
// AdminAuthCredentials {
// username: app_state.env.default_admin_username.to_owned(),
// password: app_state.env.default_admin_password.to_owned(),
// },
// )
// .await
// .unwrap();
// let static_files_index = create_files_index(&app_state);
// let mut app = test::init_service(
// App::new()
// .app_data(Data::new(app_state.clone()))
// .app_data(Data::clone(&static_files_index))
// .app_data(Data::new(AuthenticatedAdminMiddleware::new(
// "kuadrado-admin-auth",
// )))
// .service(post_files),
// )
// .await;
// let req = test::TestRequest::with_uri("/post-files")
// .method(Method::POST)
// .header(
// "Content-Type",
// "multipart/form-data; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
// )
// .cookie(get_auth_cookie(
// "kuadrado-admin-auth",
// app_state
// .encryption
// .decrypt(&admin_user.auth_token.unwrap())
// .to_owned(),
// ))
// .set_payload(create_simple_request())
// .to_request();
// let resp = test::call_service(&mut app, req).await;
// let status = resp.status();
// assert_eq!(status, StatusCode::OK);
// let pathes: Vec<String> = test::read_body_json(resp).await;
// let public_dir = StaticFilesIndex::get_public_dir(&app_state.env);
// let pathes_from_public = pathes
// .iter()
// .map(|p| {
// format!(
// "/{}",
// std::path::Path::new(p)
// .strip_prefix(&public_dir)
// .unwrap()
// .to_str()
// .unwrap()
// )
// })
// .collect::<Vec<String>>();
// let index = static_files_index.lock().unwrap();
// assert_eq!(pathes_from_public, index.0);
// let mut iter_pathes = pathes.iter();
// let f = std::fs::read_to_string(iter_pathes.next().unwrap()).unwrap();
// assert_eq!(f, "test");
// let f = std::fs::read_to_string(iter_pathes.next().unwrap()).unwrap();
// assert_eq!(f, "data");
// clear_testing_static();
// }
// #[actix_web::test]
// async fn test_delete_file() {
// let app_state = AppState::for_test().await;
// let admin_user = Administrator::authenticated(
// &app_state,
// AdminAuthCredentials {
// username: app_state.env.default_admin_username.to_owned(),
// password: app_state.env.default_admin_password.to_owned(),
// },
// )
// .await
// .unwrap();
// let static_files_index = create_files_index(&app_state);
// let mut app = test::init_service(
// App::new()
// .app_data(Data::new(app_state.clone()))
// .app_data(Data::clone(&static_files_index))
// .app_data(Data::new(AuthenticatedAdminMiddleware::new(
// "kuadrado-admin-auth",
// )))
// .service(post_files)
// .service(delete_static_file),
// )
// .await;
// let auth_token = admin_user.auth_token.unwrap();
// let req = test::TestRequest::with_uri("/post-files")
// .method(Method::POST)
// .header(
// "Content-Type",
// "multipart/form-data; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
// )
// .cookie(get_auth_cookie(
// "kuadrado-admin-auth",
// app_state.encryption.decrypt(&auth_token).to_owned(),
// ))
// .set_payload(create_simple_request())
// .to_request();
// let resp = test::call_service(&mut app, req).await;
// let status = resp.status();
// assert_eq!(status, StatusCode::OK);
// let req = test::TestRequest::with_uri("/delete-file/uploads/docs/test.txt")
// .method(Method::DELETE)
// .cookie(get_auth_cookie(
// "kuadrado-admin-auth",
// app_state.encryption.decrypt(&auth_token).to_owned(),
// ))
// .to_request();
// let resp = test::call_service(&mut app, req).await;
// let status = resp.status();
// assert_eq!(status, StatusCode::ACCEPTED);
// clear_testing_static();
// }
}