129 lines
3.6 KiB
Rust
129 lines
3.6 KiB
Rust
mod matrix;
|
|
|
|
use axum::{
|
|
body::Body,
|
|
extract::{self, State},
|
|
response::Redirect,
|
|
routing::{any, post},
|
|
Router,
|
|
};
|
|
use flate2::read::GzDecoder;
|
|
use futures_util::TryStreamExt;
|
|
use tar::Archive;
|
|
use tokio::fs::File;
|
|
use tokio_util::io::StreamReader;
|
|
use tower_http::{
|
|
services::ServeDir, trace::TraceLayer, validate_request::ValidateRequestHeaderLayer,
|
|
};
|
|
|
|
use std::{
|
|
io,
|
|
path::{Path, PathBuf},
|
|
};
|
|
|
|
use crate::STATIC_ROOT_NAME;
|
|
|
|
pub fn app(
|
|
ctx: crate::Context,
|
|
api_key: &str,
|
|
redirects: &[(&'static str, &'static str)],
|
|
) -> Router {
|
|
// We first try to route the request according to the contents of the root directory. If the
|
|
// file doesn't exist, then we look for it in the other directories.
|
|
let serve_dir = ServeDir::new(ctx.static_dir.join(STATIC_ROOT_NAME))
|
|
.append_index_html_on_directories(true)
|
|
.not_found_service(
|
|
ServeDir::new(ctx.static_dir.clone()).append_index_html_on_directories(true),
|
|
);
|
|
|
|
let mut app = Router::new()
|
|
.route_service("/", serve_dir.clone())
|
|
.route(
|
|
"/{*path}",
|
|
post(post_static_archive)
|
|
.route_layer(ValidateRequestHeaderLayer::bearer(api_key))
|
|
.get_service(serve_dir),
|
|
)
|
|
.with_state(ctx.clone())
|
|
.merge(matrix::router());
|
|
|
|
for (path, url) in redirects.iter() {
|
|
app = app.route(path, any(|| async { Redirect::permanent(url) }))
|
|
}
|
|
|
|
app.layer(TraceLayer::new_for_http())
|
|
}
|
|
|
|
pub async fn post_static_archive(
|
|
State(ctx): State<crate::Context>,
|
|
extract::Path(path): extract::Path<String>,
|
|
body: Body,
|
|
) {
|
|
// Copy tarball data to file for parsing
|
|
let stream = body.into_data_stream();
|
|
let mut reader = StreamReader::new(stream.map_err(io::Error::other));
|
|
|
|
let uuid = uuid::Uuid::new_v4();
|
|
let ar_path = ctx.tmp_dir.join(uuid.to_string());
|
|
let mut f = File::create(&ar_path).await;
|
|
|
|
tokio::io::copy(&mut reader, &mut f.unwrap()).await;
|
|
|
|
// Root is stored in its own specifc directory, as otherwise it would wipe all other uploaded
|
|
// directories every time it's updated
|
|
let dest_dir = if path.is_empty() {
|
|
String::from(crate::STATIC_ROOT_NAME)
|
|
} else {
|
|
path
|
|
};
|
|
let dest_dir = ctx.static_dir.join(dest_dir);
|
|
|
|
tokio::task::spawn_blocking(move || process_archive(&ar_path, &dest_dir))
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
fn process_archive(ar_path: &Path, dest_dir: &Path) -> io::Result<()> {
|
|
let f = std::fs::File::open(ar_path)?;
|
|
let tar = GzDecoder::new(f);
|
|
let mut ar = Archive::new(tar);
|
|
|
|
// trim possible trailing slash from path
|
|
let dest_dir = PathBuf::from(dest_dir.to_string_lossy().trim_end_matches('/'));
|
|
|
|
// extract extension and append '.new' to form new extension
|
|
let ext = dest_dir
|
|
.extension()
|
|
.map(|ext| ext.to_string_lossy().to_string())
|
|
.unwrap_or(String::from(""));
|
|
let new_dir = dest_dir.with_extension(format!("{ext}.new"));
|
|
|
|
// Unpack archive into new directory
|
|
std::fs::create_dir(&new_dir)?;
|
|
ar.unpack(&new_dir)?;
|
|
|
|
// Replace original directory with new one
|
|
if dest_dir.try_exists()? {
|
|
std::fs::remove_dir_all(&dest_dir)?;
|
|
}
|
|
|
|
std::fs::rename(new_dir, dest_dir)?;
|
|
std::fs::remove_file(ar_path)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn delete_dir(
|
|
State(ctx): State<crate::Context>,
|
|
extract::Path(path): extract::Path<String>,
|
|
) {
|
|
let dest_dir = if path.is_empty() {
|
|
String::from(crate::STATIC_ROOT_NAME)
|
|
} else {
|
|
path
|
|
};
|
|
let dest_dir = ctx.static_dir.join(dest_dir);
|
|
|
|
tokio::fs::remove_dir_all(dest_dir).await;
|
|
}
|