mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-06-14 03:32:00 +08:00
feat: 允许通过环境变量手动指定前端资源路径,删除 debug 分支默认资源路径
This commit is contained in:
@@ -49,7 +49,7 @@ reqwest = { version = "0.13", features = ["stream", "rustls", "json"], default-f
|
||||
urlencoding = "2"
|
||||
|
||||
# Static file embedding
|
||||
rust-embed = { version = "8", features = ["compression"] }
|
||||
rust-embed = { version = "8", features = ["compression", "debug-embed"] }
|
||||
mime_guess = "2"
|
||||
|
||||
# TLS/HTTPS
|
||||
|
||||
@@ -4,31 +4,40 @@ use axum::{
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
use std::path::PathBuf;
|
||||
#[cfg(debug_assertions)]
|
||||
use rust_embed::Embed;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
use rust_embed::Embed;
|
||||
const FRONTEND_DIR_ENV: &str = "ONE_KVM_FRONTEND_DIR";
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[derive(Embed)]
|
||||
#[folder = "web/dist"]
|
||||
#[prefix = ""]
|
||||
pub struct StaticAssets;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn get_static_base_dir() -> PathBuf {
|
||||
static BASE_DIR: OnceLock<PathBuf> = OnceLock::new();
|
||||
BASE_DIR
|
||||
fn frontend_dir_override() -> Option<PathBuf> {
|
||||
static FRONTEND_DIR: OnceLock<Option<PathBuf>> = OnceLock::new();
|
||||
FRONTEND_DIR
|
||||
.get_or_init(|| {
|
||||
if let Ok(exe_path) = std::env::current_exe() {
|
||||
if let Some(exe_dir) = exe_path.parent() {
|
||||
return exe_dir.join("web").join("dist");
|
||||
let value = std::env::var_os(FRONTEND_DIR_ENV)?;
|
||||
let path = PathBuf::from(value);
|
||||
|
||||
if path.as_os_str().is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match path.canonicalize() {
|
||||
Ok(path) => Some(path),
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
"{}='{}' is not accessible: {}",
|
||||
FRONTEND_DIR_ENV,
|
||||
path.display(),
|
||||
e
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
PathBuf::from("web/dist")
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
@@ -84,69 +93,60 @@ fn serve_file(path: &str) -> Response<Body> {
|
||||
}
|
||||
|
||||
fn try_serve_file(path: &str) -> Option<Response<Body>> {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let base_dir = get_static_base_dir();
|
||||
if let Some(base_dir) = frontend_dir_override() {
|
||||
return try_serve_file_from_dir(&base_dir, path);
|
||||
}
|
||||
|
||||
let asset = StaticAssets::get(path)?;
|
||||
Some(static_response(path, asset.data.to_vec()))
|
||||
}
|
||||
|
||||
fn try_serve_file_from_dir(base_dir: &Path, path: &str) -> Option<Response<Body>> {
|
||||
let file_path = base_dir.join(path);
|
||||
|
||||
if !file_path.starts_with(&base_dir) {
|
||||
let normalized_path = match file_path.canonicalize() {
|
||||
Ok(path) => path,
|
||||
Err(e) => {
|
||||
tracing::debug!(
|
||||
"Failed to resolve static file '{}' from '{}': {}",
|
||||
path,
|
||||
file_path.display(),
|
||||
e
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
if !normalized_path.starts_with(base_dir) {
|
||||
tracing::warn!("Path traversal attempt blocked: {}", path);
|
||||
return None;
|
||||
}
|
||||
|
||||
if let (Ok(normalized_path), Ok(normalized_base)) =
|
||||
(file_path.canonicalize(), base_dir.canonicalize())
|
||||
{
|
||||
if !normalized_path.starts_with(&normalized_base) {
|
||||
tracing::warn!("Path traversal attempt blocked (canonicalized): {}", path);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
match std::fs::read(&file_path) {
|
||||
Ok(data) => {
|
||||
let mime = mime_guess::from_path(path)
|
||||
.first_or_octet_stream()
|
||||
.to_string();
|
||||
|
||||
Some(
|
||||
Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header(header::CONTENT_TYPE, mime)
|
||||
.header(header::CACHE_CONTROL, "public, max-age=86400")
|
||||
.body(Body::from(data))
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
match std::fs::read(&normalized_path) {
|
||||
Ok(data) => Some(static_response(path, data)),
|
||||
Err(e) => {
|
||||
tracing::debug!(
|
||||
"Failed to read static file '{}' from '{}': {}",
|
||||
path,
|
||||
file_path.display(),
|
||||
normalized_path.display(),
|
||||
e
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
let asset = StaticAssets::get(path)?;
|
||||
}
|
||||
|
||||
fn static_response(path: &str, data: Vec<u8>) -> Response<Body> {
|
||||
let mime = mime_guess::from_path(path)
|
||||
.first_or_octet_stream()
|
||||
.to_string();
|
||||
|
||||
Some(
|
||||
Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header(header::CONTENT_TYPE, mime)
|
||||
.header(header::CACHE_CONTROL, "public, max-age=86400")
|
||||
.body(Body::from(asset.data.to_vec()))
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
.body(Body::from(data))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn placeholder_html() -> &'static str {
|
||||
|
||||
Reference in New Issue
Block a user