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
  • Learn to ignore specific revisions
  • 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::*;
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
        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() {
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
            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())))
    
    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();
        // }
    }