feat: finalize v0.7.0 Docker deployment and image routing
This commit is contained in:
1 parent
2cd3c2d965
commit
25577b4b83
5 files changed
+520
-223
No files matched your search
@@ -0,0 +1,67 @@
|
||||
use axum::{
|
||||
extract::Path,
|
||||
http::{header, HeaderValue, StatusCode},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use std::path::{Component, PathBuf};
|
||||
|
||||
const IMAGES_DIR: &str = "/app/images";
|
||||
|
||||
pub async fn image_handler(Path(path): Path<String>) -> Response {
|
||||
// preview.png is reserved for social-media metadata.
|
||||
// It is served from /app/www/images/preview.png,
|
||||
// not from the persistent application image directory.
|
||||
if path == "preview.png" {
|
||||
return crate::web::pages::social_preview().await;
|
||||
}
|
||||
|
||||
let relative = PathBuf::from(&path);
|
||||
|
||||
// Prevent path traversal.
|
||||
if relative.components().any(|component| {
|
||||
matches!(
|
||||
component,
|
||||
Component::ParentDir | Component::RootDir | Component::Prefix(_)
|
||||
)
|
||||
}) {
|
||||
return (StatusCode::BAD_REQUEST, "Invalid image path").into_response();
|
||||
}
|
||||
|
||||
let image_path = PathBuf::from(IMAGES_DIR).join(relative);
|
||||
|
||||
let content = match tokio::fs::read(&image_path).await {
|
||||
Ok(content) => content,
|
||||
Err(_) => {
|
||||
return (StatusCode::NOT_FOUND, "Image not found").into_response();
|
||||
}
|
||||
};
|
||||
|
||||
let content_type = match image_path
|
||||
.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
.map(|ext| ext.to_ascii_lowercase())
|
||||
.as_deref()
|
||||
{
|
||||
Some("png") => "image/png",
|
||||
Some("jpg") | Some("jpeg") => "image/jpeg",
|
||||
Some("gif") => "image/gif",
|
||||
Some("webp") => "image/webp",
|
||||
Some("svg") => "image/svg+xml",
|
||||
Some("avif") => "image/avif",
|
||||
_ => "application/octet-stream",
|
||||
};
|
||||
|
||||
let content_type = HeaderValue::from_static(content_type);
|
||||
|
||||
(
|
||||
[
|
||||
(header::CONTENT_TYPE, content_type),
|
||||
(
|
||||
header::CACHE_CONTROL,
|
||||
HeaderValue::from_static("public, max-age=86400"),
|
||||
),
|
||||
],
|
||||
content,
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
@@ -11,3 +11,4 @@ pub mod routes;
|
||||
pub mod system;
|
||||
|
||||
pub use routes::create_router;
|
||||
pub mod images;
|
||||
+4
-2
@@ -1,5 +1,7 @@
|
||||
use crate::state::AppState;
|
||||
use crate::web::{admin, api, bulk, multi_user, pages, password_gate, qr, redirect, system};
|
||||
use crate::web::{
|
||||
admin, api, bulk, images, multi_user, pages, password_gate, qr, redirect, system,
|
||||
};
|
||||
use axum::{
|
||||
routing::{delete, get, post, put},
|
||||
Router,
|
||||
@@ -95,7 +97,7 @@ pub fn create_router(state: AppState) -> Router {
|
||||
.route("/admin/pages/delete/:id", post(admin::pages_delete))
|
||||
.route("/admin/analytics/url/:id", get(admin::url_analytics_get))
|
||||
.route("/deploy.sh", get(pages::deploy_script))
|
||||
.route("/images/preview.png", get(pages::social_preview))
|
||||
.route("/images/*path", get(images::image_handler))
|
||||
.route(
|
||||
"/admin/analytics/url/:id/export/csv",
|
||||
get(admin::url_analytics_csv_export),
|
||||
|
||||
Reference in new issue
Block a user