Pour tout problème contactez-nous par mail : support@froggit.fr | La FAQ :grey_question: | Rejoignez-nous sur le Chat :speech_balloon:

Skip to content
Snippets Groups Projects
files.rs 11.6 KiB
Newer Older
Pierre Jarriges's avatar
Pierre Jarriges committed
use crate::static_files::upload::*;
use crate::website::WebSite;
peterrabbit's avatar
peterrabbit committed
use actix_files::NamedFile;
Pierre Jarriges's avatar
Pierre Jarriges committed
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,
Pierre Jarriges's avatar
Pierre Jarriges committed
};
peterrabbit's avatar
peterrabbit committed

#[get("/favicon.ico")]
pub async fn favicon(website: web::Data<RwLock<WebSite>>) -> impl Responder {
    NamedFile::open(
        &website
            .read()
            .unwrap()
            .static_files_manager
            .dir
            .join("default")
            .join("favicon.ico"),
    )
peterrabbit's avatar
peterrabbit committed
}
Pierre Jarriges's avatar
Pierre Jarriges committed

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.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 {
Pierre Jarriges's avatar
Pierre Jarriges committed
    let mut uploaded_filepathes = Vec::new();
    let mut website = website
        .write()
        .expect("Couldn't acquire write lock for RwLock<WebSite");
Pierre Jarriges's avatar
Pierre Jarriges committed

    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_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_index())
Pierre Jarriges's avatar
Pierre Jarriges committed
}

#[delete("/delete-file/{category}/{filename}")]
async fn delete_static_file(
    website: Data<RwLock<WebSite>>,
Pierre Jarriges's avatar
Pierre Jarriges committed
    fileinfo: Path<(String, String)>,
) -> impl Responder {
    let mut website = website
        .write()
        .expect("Couldn't acquire write lock for RwLock<WebSite");
Pierre Jarriges's avatar
Pierre Jarriges committed
    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::{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() {
Pierre Jarriges's avatar
Pierre Jarriges committed
        let app_state = AppState::testing();
        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())))
Pierre Jarriges's avatar
Pierre Jarriges committed
                .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();
    // }
}