diff --git a/Cargo.lock b/Cargo.lock index 3984a61..6894d83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -534,16 +534,6 @@ dependencies = [ "typeid", ] -[[package]] -name = "errno" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "exe" version = "0.5.6" @@ -846,15 +836,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "http" version = "0.2.12" @@ -1268,12 +1249,6 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - [[package]] name = "lock_api" version = "0.4.9" @@ -1299,25 +1274,6 @@ dependencies = [ "imgref", ] -[[package]] -name = "lua-src" -version = "547.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edaf29e3517b49b8b746701e5648ccb5785cde1c119062cbabbc5d5cd115e42" -dependencies = [ - "cc", -] - -[[package]] -name = "luajit-src" -version = "210.5.10+f725e44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a0fa0df28e21f785c48d9c0f0be355cf40658badb667284207dbb4d1e574a9" -dependencies = [ - "cc", - "which", -] - [[package]] name = "markdown" version = "1.0.0-alpha.16" @@ -1460,8 +1416,6 @@ checksum = "63a11d485edf0f3f04a508615d36c7d50d299cf61a7ee6d3e2530651e0a31771" dependencies = [ "cc", "cfg-if 1.0.0", - "lua-src", - "luajit-src", "pkg-config", ] @@ -2154,19 +2108,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.38.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" -dependencies = [ - "bitflags 2.4.2", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - [[package]] name = "rustls" version = "0.18.1" @@ -3119,18 +3060,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" -[[package]] -name = "which" -version = "6.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" -dependencies = [ - "either", - "home", - "rustix", - "winsafe", -] - [[package]] name = "widestring" version = "1.0.2" @@ -3412,12 +3341,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "winsafe" -version = "0.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" - [[package]] name = "ws2_32-sys" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index e52d312..ba1527c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,13 +25,7 @@ exe = "0.5" image = { version = "0.25", features = ["ico"] } urlencoding = "2.1" markdown = "1.0.0-alpha.16" -mlua = { version = "0.10", features = [ - "lua54", - "vendored", - "serialize", - "async", - "send", -] } +mlua = { version = "0.10", features = ["lua54", "serialize", "async", "send"] } reqwest = { version = "0.10", default-features = false, features = [ "json", "rustls-tls", diff --git a/Dockerfile b/Dockerfile index e1a2508..8fedd37 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,29 @@ -# Build stage -FROM rust:alpine AS build +# Lua Build +FROM nickblah/lua:5.4-luarocks-alpine AS build +RUN apk add --no-cache build-base rust cargo lua5.4-dev pkgconfig openssl-dev m4 bsd-compat-headers +# Install lua modules here. +RUN luarocks install luasec +RUN luarocks install luasocket +RUN luarocks install lua-cjson +RUN luarocks install base64 +RUN luarocks install inspect + +# Need to build rust in this container too due to version mismatch between +# build environments of lua and rust WORKDIR /app COPY . . -RUN apk update && apk add --no-cache musl-dev RUN cargo build --release # Deploy stage FROM alpine WORKDIR /app -COPY --from=build /app/target/release/webserver . +RUN apk update && apk add --no-cache ca-certificates openssl libgcc lua5.4-libs + +COPY --from=build /usr/local/bin/lua /usr/local/bin/lua +COPY --from=build /usr/local/bin/luarocks /usr/local/bin/luarocks +COPY --from=build /usr/local/lib/lua /usr/local/lib/lua +COPY --from=build /usr/local/share/lua /usr/local/share/lua +COPY --from=build /usr/local/lib/luarocks /usr/local/lib/luarocks +COPY --from=build /app/target/release/webserver /app/webserver + CMD ["./webserver"] diff --git a/build.nu b/build.nu index a138789..43e82f9 100644 --- a/build.nu +++ b/build.nu @@ -1,3 +1,5 @@ docker build --progress=plain -t webserver . docker image tag webserver registry.avii.nl/aviinl/webserver:latest docker image push --all-tags registry.avii.nl/aviinl/webserver + +# docker run --init -p 3000:3000 -v ./public:/app/public webserver diff --git a/src/main.rs b/src/main.rs index ac91157..60ff22c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,12 @@ mod icon; -use std::{collections::HashMap, fs, io::ErrorKind, path::PathBuf, str::FromStr}; +use std::{collections::HashMap, fs, io::ErrorKind, path::PathBuf, str::FromStr, sync::LazyLock}; use icon::extract_icon; use mlua::{ - FromLua, Lua, Number, Table, + Lua, Number, Table, Value::{self as LuaValue}, }; -use once_cell::sync::{Lazy, OnceCell}; use axum::{ extract::Request, @@ -17,7 +16,6 @@ use axum::{ routing::get, }; use base64::prelude::*; -use reqwest::Method; use serde::Serialize; use tera::{to_value, Context, Result as TeraResult, Tera, Value}; use time::OffsetDateTime; @@ -28,37 +26,38 @@ use tower_http::services::ServeDir; pub type AError = Box; pub type AResult = Result; -pub static CWD: Lazy = Lazy::new(|| std::env::current_dir().unwrap()); +pub static CWD: LazyLock = LazyLock::new(|| std::env::current_dir().unwrap()); -#[derive(Debug, Clone)] -struct ArrayBuffer(Vec); +// use reqwest::Method; +// #[derive(Debug, Clone)] +// struct ArrayBuffer(Vec); -impl mlua::FromLua for ArrayBuffer { - fn from_lua(value: LuaValue, _: &Lua) -> mlua::Result { - if let Some(data) = value.as_str() { - return Ok(Self(data.as_bytes().to_owned())); - } +// impl mlua::FromLua for ArrayBuffer { +// fn from_lua(value: LuaValue, _: &Lua) -> mlua::Result { +// if let Some(data) = value.as_str() { +// return Ok(Self(data.as_bytes().to_owned())); +// } - if let Some(data) = value.as_table() { - let mut out = vec![]; - for pair in data.pairs::() { - let (_, v) = pair?; - if let Some(value) = v.as_integer() { - out.push(value as u8); - } - } - return Ok(Self(out)); - } +// if let Some(data) = value.as_table() { +// let mut out = vec![]; +// for pair in data.pairs::() { +// let (_, v) = pair?; +// if let Some(value) = v.as_integer() { +// out.push(value as u8); +// } +// } +// return Ok(Self(out)); +// } - Err(mlua::Error::FromLuaConversionError { - from: value.type_name(), - to: "ArrayBuffer".to_string(), - message: None, - }) - } -} +// Err(mlua::Error::FromLuaConversionError { +// from: value.type_name(), +// to: "ArrayBuffer".to_string(), +// message: None, +// }) +// } +// } -pub static TERA: Lazy = Lazy::new(|| { +pub static TERA: LazyLock = LazyLock::new(|| { let mut tera = Tera::default(); tera.add_raw_template( "index.html.jinja", @@ -74,7 +73,7 @@ pub static TERA: Lazy = Lazy::new(|| { tera }); -pub static ICONS: Lazy> = Lazy::new(|| { +pub static ICONS: LazyLock> = LazyLock::new(|| { let mut map = HashMap::new(); map.insert( "unknown", @@ -188,7 +187,39 @@ fn md(value: &Value, _args: &HashMap) -> TeraResult { Ok(to_value(value).unwrap()) } -pub static BASE_DIR: OnceCell = OnceCell::new(); +pub static LUA_CPATH: LazyLock = LazyLock::new(|| { + String::from_utf8_lossy( + &std::process::Command::new("luarocks") + .arg("path") + .arg("--lr-cpath") + .arg("--full") + .output() + .map_err::, _>(|_| vec![]) + .map(|r| r.stdout) + .unwrap_or_default(), + ) + .trim() + .to_string() +}); + +pub static LUA_PATH: LazyLock = LazyLock::new(|| { + String::from_utf8_lossy( + &std::process::Command::new("luarocks") + .arg("path") + .arg("--lr-path") + .arg("--full") + .output() + .map_err::, _>(|_| vec![]) + .map(|r| r.stdout) + .unwrap_or_default(), + ) + .trim() + .to_string() +}); + +pub static BASE_DIR: LazyLock = LazyLock::new(|| { + PathBuf::from_str(&std::env::var("BASE_DIR").unwrap_or(String::from("./public"))).unwrap() +}); #[derive(Debug, Serialize)] struct FileInfo { @@ -201,61 +232,62 @@ struct FileInfo { description: Option, } -#[derive(Debug, Clone)] -struct FetchOptions { - method: String, - headers: HashMap, - body: Option, -} +// #[derive(Debug, Clone)] +// struct FetchOptions { +// method: String, +// headers: HashMap, +// body: Option, +// } -impl Default for FetchOptions { - fn default() -> Self { - Self { - method: "GET".to_string(), - headers: HashMap::new(), - body: None, - } - } -} +// impl Default for FetchOptions { +// fn default() -> Self { +// Self { +// method: "GET".to_string(), +// headers: HashMap::new(), +// body: None, +// } +// } +// } -impl FromLua for FetchOptions { - fn from_lua(value: LuaValue, _lua: &Lua) -> mlua::Result { - let Some(options) = value.as_table() else { - return Err(mlua::Error::FromLuaConversionError { - from: value.type_name(), - to: "FetchOptions".to_string(), - message: Some("options must be a table".to_string()), - }); - }; +// impl mlua::FromLua for FetchOptions { +// fn from_lua(value: LuaValue, _lua: &Lua) -> mlua::Result { +// let Some(options) = value.as_table() else { +// return Err(mlua::Error::FromLuaConversionError { +// from: value.type_name(), +// to: "FetchOptions".to_string(), +// message: Some("options must be a table".to_string()), +// }); +// }; - let mut result = FetchOptions::default(); - if let Ok(method) = options.get::("method") { - result.method = method; - } - if let Ok(headers) = options.get::>("headers") { - result.headers = headers; - } - if let Ok(body) = options.get::>("body") { - result.body = body; - } +// let mut result = FetchOptions::default(); +// if let Ok(method) = options.get::("method") { +// result.method = method; +// } +// if let Ok(headers) = options.get::>("headers") { +// result.headers = headers; +// } +// if let Ok(body) = options.get::>("body") { +// result.body = body; +// } - Ok(result) - } -} +// Ok(result) +// } +// } #[tokio::main] async fn main() -> AResult<()> { let listener = tokio::net::TcpListener::bind("[::]:3000").await.unwrap(); + println!("Server running on port 3000"); Ok(axum::serve(listener, file_handler.into_make_service()).await?) } async fn file_handler(request: Request) -> impl IntoResponse { std::env::set_current_dir(&*CWD).unwrap(); - let base_dir: &PathBuf = BASE_DIR.get_or_init(|| { - PathBuf::from_str(&std::env::var("BASE_DIR").unwrap_or(String::from("./public"))).unwrap() - }); + // let base_dir: &PathBuf = BASE_DIR.get_or_init(|| { + // PathBuf::from_str(&std::env::var("BASE_DIR").unwrap_or(String::from("./public"))).unwrap() + // }); - let base_dir = base_dir.canonicalize().map_err(|e| { + let base_dir = BASE_DIR.canonicalize().map_err(|e| { dbg!(e); (StatusCode::NOT_FOUND, "404: Not Found".to_string()) })?; @@ -282,142 +314,21 @@ async fn file_handler(request: Request) -> impl IntoResponse { .map_err(|_| (StatusCode::NOT_FOUND, "404: Not Found".to_string())); }; - let lua = Lua::new(); + let lua = unsafe { Lua::unsafe_new() }; + + // Fix load paths + lua.load(format!( + "package.path = \"{}\"\npackage.cpath = \"{}\"", + &*LUA_PATH, &*LUA_CPATH + )) + .exec() + .map_err(|e| { + eprintln!("Lua Error: {:#?}", e); + render_lua_error(e) + })?; + let globals = lua.globals(); - use mlua::{ExternalResult, LuaSerdeExt}; - - let fetch = lua - .create_function(|lua, params: (String, Option)| { - let uri = params.0; - let options = params.1; - let options = options.unwrap_or_default(); - - let Ok(method) = Method::from_bytes(options.method.as_bytes()) else { - return Err(mlua::Error::RuntimeError("Invalid method".to_string())); - }; - let Ok(uri) = reqwest::Url::parse(&uri) else { - return Err(mlua::Error::RuntimeError("Invalid uri".to_string())); - }; - - let mut request = reqwest::blocking::Request::new(method, uri); - // let mut headers = ; - - for (k, v) in &options.headers { - let Ok(k) = reqwest::header::HeaderName::from_str(k) else { - return Err(mlua::Error::RuntimeError("Invalid header name".to_string())); - }; - let Ok(v) = reqwest::header::HeaderValue::from_str(v) else { - return Err(mlua::Error::RuntimeError( - "Invalid header value".to_string(), - )); - }; - - let _ = request.headers_mut().try_append(k, v); - } - - *request.body_mut() = options.body.map(|f| f.0.into()); - - let client = reqwest::blocking::Client::new(); - let resp = client - .execute(request) - .and_then(|resp| resp.error_for_status()) - .into_lua_err()?; - - let json = resp.bytes().into_lua_err()?; - lua.to_value(&json.to_vec()) - }) - .map_err(|e| { - eprintln!("Lua Error: {:?}", e); - render_lua_error(e) - })?; - - let base64_encode = lua - .create_function(|lua, bytes: ArrayBuffer| { - let b64 = BASE64_STANDARD.encode(&bytes.0); - lua.to_value(&b64) - }) - .map_err(|e| { - eprintln!("Lua Error: {:?}", e); - render_lua_error(e) - })?; - - let base64_decode = lua - .create_function(|lua, data: ArrayBuffer| { - let b64 = BASE64_STANDARD.decode(&data.0).into_lua_err()?; - lua.to_value(&b64) - }) - .map_err(|e| { - eprintln!("Lua Error: {:?}", e); - render_lua_error(e) - })?; - - let json_decode = lua - .create_function(|lua, data: ArrayBuffer| { - let data = String::from_utf8_lossy(&data.0); - let json = serde_json::from_str::(&data).into_lua_err()?; //data.json::().into_lua_err()?; - lua.to_value(&json) - }) - .map_err(|e| { - eprintln!("Lua Error: {:?}", e); - render_lua_error(e) - })?; - - let to_string = lua - .create_function(|lua, data: ArrayBuffer| { - let data = String::from_utf8_lossy(&data.0); - lua.to_value(&data) - }) - .map_err(|e| { - eprintln!("Lua Error: {:?}", e); - render_lua_error(e) - })?; - - let dbg = lua - .create_function(|_, value: LuaValue| { - dbg!(value); - Ok(()) - }) - .map_err(|e| { - eprintln!("Lua Error: {:?}", e); - render_lua_error(e) - })?; - - globals.set("fetch", fetch).map_err(|e| { - eprintln!("Lua Error: {:?}", e); - render_lua_error(e) - })?; - - globals.set("base64_encode", base64_encode).map_err(|e| { - eprintln!("Lua Error: {:?}", e); - render_lua_error(e) - })?; - - globals.set("base64_decode", base64_decode).map_err(|e| { - eprintln!("Lua Error: {:?}", e); - render_lua_error(e) - })?; - - globals.set("json_decode", json_decode).map_err(|e| { - eprintln!("Lua Error: {:?}", e); - render_lua_error(e) - })?; - - // globals.set("fetch_b64", fetch_b64).map_err(|e| { - // eprintln!("Lua Error: {:?}", e); - // render_lua_error(e) - // })?; - - globals.set("dbg", dbg).map_err(|e| { - eprintln!("Lua Error: {:?}", e); - render_lua_error(e) - })?; - - globals.set("to_string", to_string).map_err(|e| { - eprintln!("Lua Error: {:?}", e); - render_lua_error(e) - })?; - let request_table = lua.create_table().map_err(|e| { eprintln!("Lua Error: {:?}", e); render_lua_error(e) @@ -537,6 +448,25 @@ async fn file_handler(request: Request) -> impl IntoResponse { // TODO: FIX THIS, THIS IS DISGUSTING std::env::set_current_dir(full_base_path).unwrap(); + // Before executing user-code, make sure they can't screw with `global` + lua.load( + r#" + setmetatable(_G, { + __newindex = function (_, n) + error("attempt to write to undeclared variable "..n, 2) + end, + __index = function (_, n) + error("attempt to read undeclared variable "..n, 2) + end, + })"#, + ) + .exec() + .map_err(|e| { + std::env::set_current_dir(&*CWD).unwrap(); + eprintln!("Lua Error: {:?}", e); + render_lua_error(e) + })?; + let script = lua.load(script).eval::().map_err(|e| { std::env::set_current_dir(&*CWD).unwrap(); eprintln!("Lua Error: {:?}", e); @@ -687,8 +617,7 @@ fn render_lua_error(e: mlua::Error) -> (StatusCode, String) { async fn handler(request: Request) -> Result, (StatusCode, String)> { let start = Instant::now(); - let base_dir = BASE_DIR.get().unwrap(); - let mut path = base_dir.clone(); + let mut path = BASE_DIR.clone(); let uri = urlencoding::decode(&request.uri().path()[1..]).map_err(|e| { println!("{:?}", e); @@ -696,12 +625,12 @@ async fn handler(request: Request) -> Result, (StatusCode, String)> })?; path.push(&*uri); - let full_path = fs::canonicalize(&path).map_err(|e| { + let full_path = path.canonicalize().map_err(|e| { println!("{:?}", e); (StatusCode::NOT_FOUND, "404: Not Found".to_string()) })?; - let full_base_path = fs::canonicalize(base_dir).map_err(|e| { + let full_base_path = BASE_DIR.canonicalize().map_err(|e| { println!("{:?}", e); (StatusCode::NOT_FOUND, "404: Not Found".to_string()) })?; @@ -732,9 +661,8 @@ async fn handler(request: Request) -> Result, (StatusCode, String)> } async fn render_dir(path: &PathBuf, time: Instant) -> AResult { - let base_dir = BASE_DIR.get().unwrap(); let time2 = Instant::now(); - let base = base_dir.display().to_string(); + let base = BASE_DIR.display().to_string(); let dirname = path .display() .to_string()