initial implementation of mlua for loading plugins, see todo.md in examples
This commit is contained in:
@@ -2,3 +2,4 @@
|
|||||||
Cargo.lock
|
Cargo.lock
|
||||||
example
|
example
|
||||||
*.diff
|
*.diff
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -1,13 +1,14 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "http-rs"
|
name = "http-rs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
author="rattatwinko"
|
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = {version = "4.5.55", features = ["derive"]}
|
clap = {version = "4.5.55", features = ["derive"]}
|
||||||
logger-rust = "0.2.12"
|
logger-rust = "0.2.12"
|
||||||
mime_guess = "2.0.5"
|
mime_guess = "2.0.5"
|
||||||
|
mlua = {version="0.11.6", features = ["lua55", "vendored"]}
|
||||||
|
once_cell = "1.21.4"
|
||||||
serde = { version = "1.0.228", features = ["derive"]}
|
serde = { version = "1.0.228", features = ["derive"]}
|
||||||
threadpool = "1.8.1"
|
threadpool = "1.8.1"
|
||||||
tiny_http = "0.12.0"
|
tiny_http = "0.12.0"
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
-- example plugin for http-rs ; using current api
|
||||||
|
|
||||||
|
print("type(router) = " .. type(router))
|
||||||
|
print("router = ", router)
|
||||||
|
|
||||||
|
router:add_file_server("/uploads", "./uploaded_files")
|
||||||
|
|
||||||
|
router:add_static("/api/status", 200, '{"status":"ok","version":"1.0"}', "application/json")
|
||||||
|
|
||||||
|
print("Lua plugin loaded with the following arguments:")
|
||||||
|
for i, v in ipairs(args) do
|
||||||
|
print(" arg[" .. i .. "] = " .. tostring(v))
|
||||||
|
end
|
||||||
|
|
||||||
|
if args[1] == "debug" then
|
||||||
|
router:add_static("/debug", 200, "Debug mode active", "text/plain")
|
||||||
|
print("Debug endpoint added at /debug")
|
||||||
|
end
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# mlua-plugin-addition todo list
|
||||||
|
|
||||||
|
## implemented ("working")
|
||||||
|
|
||||||
|
- [x] **cli args**
|
||||||
|
`--lua-plugin` and `--lua-arg` (multiple) added with `clap`
|
||||||
|
|
||||||
|
- [x] **lua scripting**
|
||||||
|
`lua_plugin.rs` with `LuaRouter` userdata
|
||||||
|
|
||||||
|
- [x] **file server mounting from lua**
|
||||||
|
`router:add_file_server(prefix, root)` – paths relative to plugin file
|
||||||
|
|
||||||
|
- [x] **route registration from lua**
|
||||||
|
`router:add_static(path, status, body, content_type)`
|
||||||
|
|
||||||
|
- [x] **fallback static file server**
|
||||||
|
Original `resolve_fs` logic still works for non lua routes
|
||||||
|
|
||||||
|
- [x] **pass cli args to lua**
|
||||||
|
Accessible as `args` table inside Lua scripts.
|
||||||
|
|
||||||
|
## fixing implementing needed
|
||||||
|
|
||||||
|
- [ ] **better error messages from Lua**
|
||||||
|
log something at all. write a custom logger ; and use logger_rs for it
|
||||||
|
|
||||||
|
- [ ] **hot reload of lua plugins**
|
||||||
|
i want this
|
||||||
|
|
||||||
|
- [ ] **more lua api functions**
|
||||||
|
very bare bones right now
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>TestDoc</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>If this is there the server worked!</h1>
|
||||||
|
<script>
|
||||||
|
var msg = "This is js!";
|
||||||
|
console.log(msg);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use logger_rust::*;
|
||||||
|
use mlua::{Lua, Table, UserData, UserDataMethods};
|
||||||
|
use crate::router::Router;
|
||||||
|
|
||||||
|
// just use userdata
|
||||||
|
pub struct LuaRouter(pub Arc<Router>);
|
||||||
|
|
||||||
|
impl UserData for LuaRouter {
|
||||||
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
// lua example -> router:add_file_server(prefix, root)
|
||||||
|
methods.add_method("add_file_server", |lua, this, (prefix, root): (String, String)| {
|
||||||
|
// get lua dir ; dont panic and fuck up.
|
||||||
|
let plugin_dir: String = lua.globals().get("_PLUGIN_DIR")?;
|
||||||
|
let plugin_dir = PathBuf::from(plugin_dir);
|
||||||
|
let root_path = PathBuf::from(&root);
|
||||||
|
let resolved_root = if root_path.is_relative() {
|
||||||
|
plugin_dir.join(root_path)
|
||||||
|
} else {
|
||||||
|
root_path
|
||||||
|
};
|
||||||
|
this.0.add_file_server(prefix, resolved_root)
|
||||||
|
.map_err(mlua::Error::external)
|
||||||
|
});
|
||||||
|
|
||||||
|
// lua method example -> router:add_static(path, status, body, content_type)
|
||||||
|
methods.add_method("add_static", |_, this, (path, status, body, content_type): (String, u16, String, String)| {
|
||||||
|
this.0.add_static(path, status, body, content_type)
|
||||||
|
.map_err(mlua::Error::external)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// load a plugin and register it
|
||||||
|
pub fn load_plugin(plugin_path: &Path, args: Vec<String>, router: Arc<Router>) -> Result<(), String> {
|
||||||
|
let lua = Lua::new();
|
||||||
|
|
||||||
|
// we need the dir from the plugin so we can make other methods use that instead of just the binary path
|
||||||
|
// using this we dont fuck up when trying to load a file, cause that panics and aborts xD
|
||||||
|
let plugin_dir = plugin_path.parent().unwrap_or(Path::new(".")).to_path_buf();
|
||||||
|
lua.globals().set("_PLUGIN_DIR", plugin_dir.to_string_lossy().to_string())
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
// hacky workaround for getting plugins working in their respective directory; if you like you can see this as a cwd
|
||||||
|
let package_path = format!("{}/?.lua;{}/?/init.lua", plugin_dir.display(), plugin_dir.display());
|
||||||
|
let globals = lua.globals();
|
||||||
|
let package: Table = globals.get("package").map_err(|e| e.to_string())?;
|
||||||
|
let current_path: String = package.get("path").unwrap_or_default();
|
||||||
|
let new_path = format!("{};{}", package_path, current_path);
|
||||||
|
package.set("path", new_path).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
// pass cli args from rust to lua as a Lua table
|
||||||
|
let lua_args = lua.create_table().map_err(|e| e.to_string())?;
|
||||||
|
for (i, arg) in args.iter().enumerate() {
|
||||||
|
lua_args.set(i + 1, arg.as_str()).map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
lua.globals().set("args", lua_args).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
// create and set the router object
|
||||||
|
let lua_router = LuaRouter(router);
|
||||||
|
lua.globals().set("router", lua_router).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
// Execute the script
|
||||||
|
let script = std::fs::read_to_string(plugin_path).map_err(|e| e.to_string())?;
|
||||||
|
lua.load(&script).exec().map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
log_info!("Lua plugin loaded successfully from {:?}", plugin_path);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
+29
-14
@@ -1,50 +1,65 @@
|
|||||||
mod server;
|
mod server;
|
||||||
mod router;
|
mod router;
|
||||||
mod response;
|
mod response;
|
||||||
|
mod lua_plugin;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use logger_rust::*;
|
use logger_rust::*;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use crate::router::Router;
|
||||||
|
|
||||||
// 1CA:b6e8ae4ca8acae66aa3dfd3f0a377e91d041e4d7 ; added clap as a parser, its way cleaner
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(name = "http-rs")]
|
#[command(name = "http-rs")]
|
||||||
#[command(about = "http-rs webserver")]
|
#[command(about = "http-rs webserver with Lua plugin support")]
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
/// port where we should serve on
|
/// Port where the server should listen
|
||||||
#[arg(short, long, default_value_t = 8080)]
|
#[arg(short, long, default_value_t = 8080)]
|
||||||
port : u16,
|
port: u16,
|
||||||
|
|
||||||
/// where we should serve from
|
/// Root directory to serve static files from
|
||||||
#[arg(short, long, default_value = ".")]
|
#[arg(short, long, default_value = ".")]
|
||||||
root: PathBuf,
|
root: PathBuf,
|
||||||
|
|
||||||
/// number of threads to run on
|
/// Number of worker threads
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
threads: Option<usize>,
|
threads: Option<usize>,
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Lua plugin script to load
|
||||||
|
#[arg(long)]
|
||||||
|
lua_plugin: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Arguments passed to the Lua plugin (can be used multiple times)
|
||||||
|
#[arg(long)]
|
||||||
|
lua_arg: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = Arguments::parse();
|
let args = Arguments::parse();
|
||||||
let root = Arc::new(args.root);
|
let root = Arc::new(args.root);
|
||||||
|
|
||||||
if !root.exists() || !root.is_dir() {
|
if !root.exists() || !root.is_dir() {
|
||||||
log_error!("Root {} isnt a valid directory", root.display());
|
log_error!("Root '{}' is not a valid directory", root.display());
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// moved num_cpus from server.rs to main.rs
|
|
||||||
let threads = args.threads.unwrap_or_else(|| {
|
let threads = args.threads.unwrap_or_else(|| {
|
||||||
std::thread::available_parallelism()
|
std::thread::available_parallelism()
|
||||||
.map(|n| n.get() * 2)
|
.map(|n| n.get() * 2)
|
||||||
.unwrap_or(8)
|
.unwrap_or(8)
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Err(_) = server::run(root, args.port, threads) {
|
let router = Arc::new(Router::new((*root).clone()));
|
||||||
log_error!("port {} in use!", args.port);
|
|
||||||
|
if let Some(plugin_path) = args.lua_plugin {
|
||||||
|
if let Err(e) = lua_plugin::load_plugin(&plugin_path, args.lua_arg, router.clone()) {
|
||||||
|
log_error!("Failed to load Lua plugin: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Err(_) = server::run(router, args.port, threads) {
|
||||||
|
log_error!("Port {} is already in use", args.port);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -12,9 +12,9 @@ use tiny_http::{Header, Response, StatusCode};
|
|||||||
use tinytemplate::TinyTemplate;
|
use tinytemplate::TinyTemplate;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
type BoxedResponse = Response<Cursor<Vec<u8>>>;
|
pub type BoxedResponse = Response<Cursor<Vec<u8>>>;
|
||||||
|
|
||||||
fn content_type(value: &str) -> Header {
|
pub fn content_type(value: &str) -> Header {
|
||||||
Header::from_bytes("Content-Type", value).unwrap()
|
Header::from_bytes("Content-Type", value).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+103
-16
@@ -1,8 +1,8 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::RwLock;
|
||||||
use logger_rust::*;
|
use logger_rust::*;
|
||||||
|
use tiny_http::StatusCode;
|
||||||
// Fixed from version a671182b07da0c22ed29e66f87faa3738fbdca64
|
use crate::response;
|
||||||
// onwards
|
|
||||||
|
|
||||||
pub enum ResolveResult {
|
pub enum ResolveResult {
|
||||||
Found(PathBuf),
|
Found(PathBuf),
|
||||||
@@ -10,40 +10,127 @@ pub enum ResolveResult {
|
|||||||
Traversal,
|
Traversal,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve(root: &Path, url_path: &str) -> ResolveResult {
|
pub fn resolve_fs(root: &Path, url_path: &str) -> ResolveResult {
|
||||||
let rel = url_path.trim_start_matches('/');
|
let rel = url_path.trim_start_matches('/');
|
||||||
let rel = if rel.is_empty() { "index.html" } else { rel };
|
let rel = if rel.is_empty() { "index.html" } else { rel };
|
||||||
|
|
||||||
// join once
|
|
||||||
let mut joined = root.join(rel);
|
let mut joined = root.join(rel);
|
||||||
|
|
||||||
// if directory look for index
|
|
||||||
// this is shitty logic, TODO: Improve this snippet
|
|
||||||
if joined.is_dir() {
|
if joined.is_dir() {
|
||||||
let html = joined.join("index.html");
|
let html = joined.join("index.html");
|
||||||
let htm = joined.join("index.htm");
|
let htm = joined.join("index.htm");
|
||||||
if html.exists() {
|
if html.exists() {
|
||||||
joined = html;
|
joined = html;
|
||||||
} else if htm.exists() {
|
} else if htm.exists() {
|
||||||
joined = htm;
|
joined = htm;
|
||||||
} else {
|
} else {
|
||||||
return ResolveResult::NoIndex; // no index found
|
return ResolveResult::NoIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// nono no escape
|
|
||||||
let canonical_root = root.canonicalize().ok();
|
let canonical_root = root.canonicalize().ok();
|
||||||
let canonical_file = joined.canonicalize().ok();
|
let canonical_file = joined.canonicalize().ok();
|
||||||
|
|
||||||
// cleanup 1CA:b6e8ae4ca8acae66aa3dfd3f0a377e91d041e4d7
|
|
||||||
match (canonical_root, canonical_file) {
|
match (canonical_root, canonical_file) {
|
||||||
(Some(root), Some(file))
|
(Some(root), Some(file)) if file.starts_with(&root) => ResolveResult::Found(file),
|
||||||
if file.starts_with(&root) => {
|
|
||||||
ResolveResult::Found(file)
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
log_warn!("ResolveResult::Traversal");
|
log_warn!("ResolveResult::Traversal");
|
||||||
ResolveResult::Traversal
|
ResolveResult::Traversal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum LuaRoute {
|
||||||
|
FileServer { prefix: String, root: PathBuf },
|
||||||
|
StaticResponse { prefix: String, status: u16, body: String, content_type: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Router {
|
||||||
|
root: PathBuf,
|
||||||
|
lua_routes: RwLock<Vec<LuaRoute>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Router {
|
||||||
|
pub fn new(root: PathBuf) -> Self {
|
||||||
|
Self {
|
||||||
|
root,
|
||||||
|
lua_routes: RwLock::new(Vec::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_file_server(&self, prefix: String, root: PathBuf) -> Result<(), String> {
|
||||||
|
if !prefix.starts_with('/') {
|
||||||
|
return Err("Prefix must start with '/'".to_string());
|
||||||
|
}
|
||||||
|
let canonical = root.canonicalize().map_err(|e| e.to_string())?;
|
||||||
|
if !canonical.exists() || !canonical.is_dir() {
|
||||||
|
return Err(format!("Root '{}' is not a valid directory", canonical.display()));
|
||||||
|
}
|
||||||
|
let mut routes = self.lua_routes.write().unwrap();
|
||||||
|
routes.push(LuaRoute::FileServer { prefix, root: canonical });
|
||||||
|
// sort by longest prefix first
|
||||||
|
routes.sort_by(|a, b| {
|
||||||
|
let len_a = match a { LuaRoute::FileServer { prefix, .. } => prefix.len(), LuaRoute::StaticResponse { prefix, .. } => prefix.len() };
|
||||||
|
let len_b = match b { LuaRoute::FileServer { prefix, .. } => prefix.len(), LuaRoute::StaticResponse { prefix, .. } => prefix.len() };
|
||||||
|
len_b.cmp(&len_a)
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_static(&self, path: String, status: u16, body: String, content_type: String) -> Result<(), String> {
|
||||||
|
if !path.starts_with('/') {
|
||||||
|
return Err("Path must start with '/'".to_string());
|
||||||
|
}
|
||||||
|
let mut routes = self.lua_routes.write().unwrap();
|
||||||
|
routes.push(LuaRoute::StaticResponse { prefix: path.clone(), status, body, content_type });
|
||||||
|
routes.sort_by(|a, b| {
|
||||||
|
let len_a = match a { LuaRoute::FileServer { prefix, .. } => prefix.len(), LuaRoute::StaticResponse { prefix, .. } => prefix.len() };
|
||||||
|
let len_b = match b { LuaRoute::FileServer { prefix, .. } => prefix.len(), LuaRoute::StaticResponse { prefix, .. } => prefix.len() };
|
||||||
|
len_b.cmp(&len_a)
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve(&self, url_path: &str) -> response::BoxedResponse {
|
||||||
|
let routes = self.lua_routes.read().unwrap();
|
||||||
|
for route in routes.iter() {
|
||||||
|
let (prefix, matched) = match route {
|
||||||
|
LuaRoute::FileServer { prefix, .. } => (prefix, url_path.starts_with(prefix)),
|
||||||
|
LuaRoute::StaticResponse { prefix, .. } => (prefix, url_path == prefix),
|
||||||
|
};
|
||||||
|
if matched {
|
||||||
|
let remaining = &url_path[prefix.len()..];
|
||||||
|
return match route {
|
||||||
|
LuaRoute::FileServer { root, .. } => {
|
||||||
|
let rel = remaining.trim_start_matches('/');
|
||||||
|
let full_path = if rel.is_empty() {
|
||||||
|
root.join("index.html")
|
||||||
|
} else {
|
||||||
|
root.join(rel)
|
||||||
|
};
|
||||||
|
response::serve_file(&full_path)
|
||||||
|
}
|
||||||
|
LuaRoute::StaticResponse { status, body, content_type, .. } => {
|
||||||
|
let mut resp = tiny_http::Response::from_string(body.clone());
|
||||||
|
resp = resp.with_status_code(StatusCode(*status));
|
||||||
|
resp = resp.with_header(response::content_type(content_type));
|
||||||
|
resp
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match resolve_fs(&self.root, url_path) {
|
||||||
|
ResolveResult::Found(path) => response::serve_file(&path),
|
||||||
|
ResolveResult::NoIndex => {
|
||||||
|
log_warn!("No index file for: {}", url_path);
|
||||||
|
response::not_found("No index file found")
|
||||||
|
}
|
||||||
|
ResolveResult::Traversal => {
|
||||||
|
log_warn!("Rejected path traversal attempt: {}", url_path);
|
||||||
|
response::forbidden("Access denied")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+6
-21
@@ -1,42 +1,28 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use logger_rust::*;
|
use logger_rust::*;
|
||||||
use threadpool::ThreadPool;
|
use threadpool::ThreadPool;
|
||||||
use tiny_http::Server;
|
use tiny_http::Server;
|
||||||
|
|
||||||
use crate::router;
|
use crate::router::Router;
|
||||||
use crate::response;
|
|
||||||
|
|
||||||
pub fn run(
|
pub fn run(
|
||||||
root: Arc<PathBuf>,
|
router: Arc<Router>,
|
||||||
port: u16,
|
port: u16,
|
||||||
threads: usize
|
threads: usize,
|
||||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
|
||||||
let addr = format!("0.0.0.0:{}", port);
|
let addr = format!("0.0.0.0:{}", port);
|
||||||
let server = Arc::new(Server::http(&addr)?);
|
let server = Arc::new(Server::http(&addr)?);
|
||||||
let pool = ThreadPool::new(threads);
|
let pool = ThreadPool::new(threads);
|
||||||
|
|
||||||
log_info!("Serving '{}' on http://{}", root.display(), addr);
|
log_info!("Serving on http://{}", addr);
|
||||||
|
|
||||||
for request in server.incoming_requests() {
|
for request in server.incoming_requests() {
|
||||||
let root = Arc::clone(&root);
|
let router = Arc::clone(&router);
|
||||||
|
|
||||||
pool.execute(move || {
|
pool.execute(move || {
|
||||||
let url = request.url().to_owned();
|
let url = request.url().to_owned();
|
||||||
log_info!("{} {}", request.method(), url);
|
log_info!("{} {}", request.method(), url);
|
||||||
|
|
||||||
let resp = match router::resolve(&root, &url) {
|
let resp = router.resolve(&url);
|
||||||
router::ResolveResult::Found(path) => response::serve_file(&path),
|
|
||||||
router::ResolveResult::NoIndex => {
|
|
||||||
log_warn!("No index file for: {}", url);
|
|
||||||
response::not_found("No index file found")
|
|
||||||
}
|
|
||||||
router::ResolveResult::Traversal => {
|
|
||||||
log_warn!("Rejected path traversal attempt: {}", url);
|
|
||||||
response::forbidden("Access denied")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(e) = request.respond(resp) {
|
if let Err(e) = request.respond(resp) {
|
||||||
log_error!("Failed to send response: {}", e);
|
log_error!("Failed to send response: {}", e);
|
||||||
@@ -46,4 +32,3 @@ pub fn run(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user