diff --git a/src/main.rs b/src/main.rs index 11924da..ac91157 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use std::{collections::HashMap, fs, io::ErrorKind, path::PathBuf, str::FromStr}; use icon::extract_icon; use mlua::{ - Lua, Table, + FromLua, Lua, Number, Table, Value::{self as LuaValue}, }; use once_cell::sync::{Lazy, OnceCell}; @@ -17,6 +17,7 @@ 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; @@ -29,6 +30,34 @@ pub type AResult = Result; pub static CWD: Lazy = Lazy::new(|| std::env::current_dir().unwrap()); +#[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())); + } + + 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, + }) + } +} + pub static TERA: Lazy = Lazy::new(|| { let mut tera = Tera::default(); tera.add_raw_template( @@ -172,6 +201,48 @@ struct FileInfo { description: 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 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; + } + + Ok(result) + } +} + #[tokio::main] async fn main() -> AResult<()> { let listener = tokio::net::TcpListener::bind("[::]:3000").await.unwrap(); @@ -216,12 +287,75 @@ async fn file_handler(request: Request) -> impl IntoResponse { use mlua::{ExternalResult, LuaSerdeExt}; - let fetch_json = lua - .create_function(|lua, uri: String| { - let resp = reqwest::blocking::get(&uri) + 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.json::().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| { @@ -229,14 +363,10 @@ async fn file_handler(request: Request) -> impl IntoResponse { render_lua_error(e) })?; - let fetch_b64 = lua - .create_function(|lua, uri: String| { - let resp = reqwest::blocking::get(&uri) - .and_then(|resp| resp.error_for_status()) - .into_lua_err()?; - let bytes = resp.bytes().into_lua_err()?; - let b64 = BASE64_STANDARD.encode(bytes); - lua.to_value(&b64) + 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); @@ -253,21 +383,41 @@ async fn file_handler(request: Request) -> impl IntoResponse { render_lua_error(e) })?; - globals.set("fetch", fetch_json).map_err(|e| { + globals.set("fetch", fetch).map_err(|e| { eprintln!("Lua Error: {:?}", e); render_lua_error(e) })?; - globals.set("fetch_b64", fetch_b64).map_err(|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) @@ -415,6 +565,12 @@ async fn file_handler(request: Request) -> impl IntoResponse { let mut response = Html(script).into_response(); + if let Ok(status_code) = result.get::("code") { + if let Ok(code) = StatusCode::from_u16(status_code as u16) { + *response.status_mut() = code; + } + } + if let Ok(headers) = result.get::("headers") { let pairs = headers.pairs::(); @@ -425,11 +581,11 @@ async fn file_handler(request: Request) -> impl IntoResponse { })?; response.headers_mut().insert( HeaderName::from_str(&k).map_err(|e| { - eprintln!("Lua Error: {:?}", e); + eprintln!("Response Error: {:?}", e); (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()) })?, HeaderValue::from_str(&v).map_err(|e| { - eprintln!("Lua Error: {:?}", e); + eprintln!("Response Error: {:?}", e); (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()) })?, ); @@ -439,17 +595,94 @@ async fn file_handler(request: Request) -> impl IntoResponse { Ok(response) } +#[allow(unused_variables)] fn render_lua_error(e: mlua::Error) -> (StatusCode, String) { - let e = e - .to_string() - .split(':') - .skip(4) - .collect::>() - .join(":"); - ( - StatusCode::INTERNAL_SERVER_ERROR, - format!("LuaError on line {}", e), - ) + match e { + mlua::Error::SyntaxError { + message, + incomplete_input, + } => { + let mut message = message.split("]:"); + message.next(); + ( + StatusCode::INTERNAL_SERVER_ERROR, + message.next().unwrap_or_default().to_string(), + ) + } + mlua::Error::RuntimeError(message) => (StatusCode::INTERNAL_SERVER_ERROR, message), + mlua::Error::MemoryError(message) => (StatusCode::INTERNAL_SERVER_ERROR, message), + mlua::Error::SafetyError(message) => (StatusCode::INTERNAL_SERVER_ERROR, message), + mlua::Error::MemoryControlNotAvailable => ( + StatusCode::INTERNAL_SERVER_ERROR, + "MemoryControlNotAvailable".to_string(), + ), + mlua::Error::RecursiveMutCallback => ( + StatusCode::INTERNAL_SERVER_ERROR, + "RecursiveMutCallback".to_string(), + ), + mlua::Error::CallbackDestructed => ( + StatusCode::INTERNAL_SERVER_ERROR, + "CallbackDestructed".to_string(), + ), + mlua::Error::StackError => (StatusCode::INTERNAL_SERVER_ERROR, "StackError".to_string()), + mlua::Error::BindError => (StatusCode::INTERNAL_SERVER_ERROR, "BindError".to_string()), + mlua::Error::BadArgument { + to, + pos, + name, + cause, + } => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!( + "Bad Argument:\n\tto: {}\n\tpos: {}\n\tname: {}\n\tcause: {:?}", + to.unwrap_or_default(), + pos, + name.unwrap_or_default(), + cause + ), + ), + mlua::Error::ToLuaConversionError { from, to, message } => ( + StatusCode::INTERNAL_SERVER_ERROR, + message.unwrap_or_default(), + ), + mlua::Error::FromLuaConversionError { from, to, message } => ( + StatusCode::INTERNAL_SERVER_ERROR, + message.unwrap_or_default(), + ), + mlua::Error::CoroutineUnresumable => (StatusCode::INTERNAL_SERVER_ERROR, String::new()), + mlua::Error::UserDataTypeMismatch => (StatusCode::INTERNAL_SERVER_ERROR, String::new()), + mlua::Error::UserDataDestructed => (StatusCode::INTERNAL_SERVER_ERROR, String::new()), + mlua::Error::UserDataBorrowError => (StatusCode::INTERNAL_SERVER_ERROR, String::new()), + mlua::Error::UserDataBorrowMutError => (StatusCode::INTERNAL_SERVER_ERROR, String::new()), + mlua::Error::MetaMethodRestricted(message) => (StatusCode::INTERNAL_SERVER_ERROR, message), + mlua::Error::MetaMethodTypeError { + method, + type_name, + message, + } => ( + StatusCode::INTERNAL_SERVER_ERROR, + message.unwrap_or_default(), + ), + mlua::Error::MismatchedRegistryKey => (StatusCode::INTERNAL_SERVER_ERROR, String::new()), + mlua::Error::CallbackError { traceback, cause } => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("{}\n\n{:#?}", traceback, cause), + ), + mlua::Error::PreviouslyResumedPanic => (StatusCode::INTERNAL_SERVER_ERROR, String::new()), + mlua::Error::SerializeError(message) => (StatusCode::INTERNAL_SERVER_ERROR, message), + mlua::Error::DeserializeError(message) => (StatusCode::INTERNAL_SERVER_ERROR, message), + mlua::Error::ExternalError(error) => { + (StatusCode::INTERNAL_SERVER_ERROR, format!("{:#?}", error)) + } + mlua::Error::WithContext { context, cause } => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("{}\n\n{:#?}", context, cause), + ), + _ => ( + StatusCode::INTERNAL_SERVER_ERROR, + String::from("Unknown Error"), + ), + } } async fn handler(request: Request) -> Result, (StatusCode, String)> {