From 5832c02ff6f614e065a6fbc8901945816bfa6905 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Sun, 27 Mar 2022 14:13:44 +0200 Subject: [PATCH] Can update files --- Cargo.lock | 71 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 6 +++- src/main.rs | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 163 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c203390..4043ece 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,6 +89,24 @@ dependencies = [ "syn", ] +[[package]] +name = "actix-multipart" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9edfb0e7663d7fe18c8d5b668c9c1bcf79176b1dcc9d4da9592503209a6bfb0" +dependencies = [ + "actix-utils", + "actix-web", + "bytes", + "derive_more", + "futures-core", + "httparse", + "local-waker", + "log", + "mime", + "twoway", +] + [[package]] name = "actix-router" version = "0.5.0" @@ -460,6 +478,18 @@ dependencies = [ "termcolor", ] +[[package]] +name = "filetime" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi", +] + [[package]] name = "firestorm" version = "0.5.0" @@ -522,6 +552,7 @@ dependencies = [ "futures-task", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -819,10 +850,14 @@ name = "pages_server" version = "0.1.0" dependencies = [ "actix-files", + "actix-multipart", "actix-web", + "bytes", "clap", "env_logger", + "futures-util", "log", + "tar", ] [[package]] @@ -1091,6 +1126,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tar" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -1206,12 +1252,28 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "twoway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c57ffb460d7c24cd6eda43694110189030a3d1dfe418416d9468fd1c1d290b47" +dependencies = [ + "memchr", + "unchecked-index", +] + [[package]] name = "typenum" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +[[package]] +name = "unchecked-index" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" + [[package]] name = "unicase" version = "2.6.0" @@ -1346,6 +1408,15 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +[[package]] +name = "xattr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" +dependencies = [ + "libc", +] + [[package]] name = "zstd" version = "0.10.0+zstd.1.5.2" diff --git a/Cargo.toml b/Cargo.toml index 2856318..dcf037b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,5 +9,9 @@ edition = "2021" clap = { version = "3.1.6", features = ["derive", "env"] } actix-web = "4" actix-files = "0.6" +actix-multipart = "0.4" env_logger = "0.9.0" -log = "0.4" \ No newline at end of file +log = "0.4" +bytes = "1.1.0" +futures-util = { version = "0.3.7", default-features = false, features = ["std"] } +tar = "0.4.38" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 96a375f..424a11d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,15 @@ +use std::io::Read; use std::path::{Path, PathBuf}; use actix_files::{Files, NamedFile}; -use actix_web::{App, HttpResponse, HttpServer}; +use actix_multipart::Multipart; +use actix_web::{App, Error, HttpRequest, HttpResponse, HttpServer, web}; use actix_web::dev::{fn_service, ServiceRequest, ServiceResponse}; +use actix_web::error::{ErrorInternalServerError, ErrorUnauthorized}; use actix_web::middleware::Logger; +use bytes::BufMut; use clap::Parser; +use futures_util::TryStreamExt; /// Simple pages server #[derive(Parser, Debug, Clone)] @@ -50,6 +55,82 @@ impl Args { static mut ARGS: Option = None; +struct NewFile { + path: PathBuf, + bytes: Vec, +} + +/// Replace all the files of the website +async fn replace_files(args: web::Data, req: HttpRequest, mut payload: Multipart) -> Result { + + // Check token + let token = match req.headers().get("Token") { + None => { + return Err(ErrorUnauthorized("Token required!")); + } + Some(t) => t.to_str() + .map_err(|_| ErrorInternalServerError("Failed to parse token!"))? + }; + + if !token.eq(&args.update_token) || args.update_token.is_empty() { + return Err(ErrorUnauthorized("Invalid update token!")); + } + + let mut new_files = Vec::new(); + + // iterate over multipart stream + if let Some(mut field) = payload.try_next().await? { + let mut b = bytes::BytesMut::new(); + + // Field in turn is stream of *Bytes* object + while let Some(chunk) = field.try_next().await? { + b.put(chunk); + } + + let mut archive = tar::Archive::new(b.as_ref()); + for entry in archive.entries() + .map_err(|_| ErrorInternalServerError("Failed to parse TAR archive!"))? { + let mut file = entry?; + let inner_path = file.header().path()?; + + if inner_path.is_absolute() { + return Err(ErrorInternalServerError("Cowardly refusing to unpack absolute file!")); + } + + if inner_path.is_dir() || inner_path.to_string_lossy().ends_with("/") { + continue; + } + + let dest_file = args.storage_path().join(inner_path); + let mut buf = Vec::with_capacity(file.size() as usize); + file.read_to_end(&mut buf)?; + + new_files.push(NewFile { + path: dest_file, + bytes: buf, + }); + } + } + + // Delete all current files in storage + for entry in std::fs::read_dir(args.storage_path())? { + let entry = entry?; + if entry.path().is_dir() { + std::fs::remove_dir_all(entry.path())?; + } else { + std::fs::remove_file(entry.path())?; + } + } + + // Apply new files + for file in new_files { + std::fs::create_dir_all(file.path.parent().unwrap())?; + std::fs::write(file.path, file.bytes)?; + } + + Ok(HttpResponse::Ok().into()) +} + #[actix_web::main] async fn main() -> std::io::Result<()> { let args: Args = Args::parse(); @@ -67,6 +148,10 @@ async fn main() -> std::io::Result<()> { HttpServer::new(move || { App::new() + // Update service + .service(web::resource("/_mgmt/replace_files") + .route(web::post().to(replace_files))) + // Serve a tree of static files at the web root and specify the index file. // Note that the root path should always be defined as the last item. The paths are // resolved in the order they are defined. If this would be placed before the `/images` @@ -90,6 +175,7 @@ async fn main() -> std::io::Result<()> { // Enable the logger. .wrap(Logger::default()) + .app_data(web::Data::new(args.clone())) }) .bind(listen_address)? .run()