diff --git a/src/executor.rs b/src/executor.rs new file mode 100644 index 0000000..1349080 --- /dev/null +++ b/src/executor.rs @@ -0,0 +1,30 @@ +use crate::executor::python::PythonExecutor; + + +pub mod python; +pub mod helper; + +pub fn executors() -> Vec> { + vec![ + Box::new(PythonExecutor {}), + + ] +} + +pub fn executors_id() -> Vec { + executors() + .iter() + .map(|x| x.id()) + .collect() +} + +pub fn get_executor<'a, T: Into>(id: T, executors: &'a Vec>) -> Option<&'a Box> { + let id: String = id.into(); + let found = executors.iter().find(|x| x.id() == id); + found +} + +pub trait Executor { + fn id(&self) -> String; + fn exec(&self, code: String) -> Result; +} \ No newline at end of file diff --git a/src/executor/helper.rs b/src/executor/helper.rs new file mode 100644 index 0000000..e7e8d58 --- /dev/null +++ b/src/executor/helper.rs @@ -0,0 +1,30 @@ +use std::{fs::create_dir_all, process::ExitStatus}; + +use rand::{distributions::Alphanumeric, Rng, thread_rng}; + +pub fn assure_dir_exists() -> Result<(), std::io::Error> { + create_dir_all("/tmp/sandy-tmp")?; + Ok(()) +} + +pub fn create_path() -> String { + let name = thread_rng() + .sample_iter(&Alphanumeric) + .take(64) + .map(char::from) + .collect::(); + format!("/tmp/sandy-tmp/{name}") +} + +pub fn exit_code_msg(code: ExitStatus) -> String { + let status = code.code(); + if let Some(status) = status { + if status != 0 { + format!("Command exited with code {status}") + } else { + "Command exited with code 0".into() + } + } else { + "Command exited with unknown code".into() + } +} diff --git a/src/executor/python.rs b/src/executor/python.rs new file mode 100644 index 0000000..37489c4 --- /dev/null +++ b/src/executor/python.rs @@ -0,0 +1,51 @@ +use std::fs; +use std::io::Read; +use std::process::Command; +use std::process::Stdio; + +use super::Executor; + +use super::helper::*; + +#[derive(Debug, Clone, Copy)] +pub struct PythonExecutor {} + +impl Executor for PythonExecutor { + fn id(&self) -> String { + "python".into() + } + + fn exec(&self, code: String) -> Result { + assure_dir_exists().map_err(|x| x.to_string())?; + + let path = create_path(); + + // 1. Save the code + fs::write(path.clone(), code).map_err(|x| x.to_string())?; + + // 2. Run + let mut out = Command::new("python") + .arg(path.clone()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + + // 3. Grab the stuff and exit + let exit_status = out.wait().map_err(|x| x.to_string())?; + + let mut stdout: String = "".into(); + + if let Some(mut sout) = out.stdout { + let mut buf = vec![]; + sout.read_to_end(&mut buf).map_err(|x| x.to_string())?; + stdout += String::from_utf8(buf).map_err(|x| x.to_string())?.as_str(); + } else { + stdout += "Couldn't get stdout from the process" + } + + stdout += "\n\n"; + stdout += exit_code_msg(exit_status).as_str(); + + Ok(stdout) + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e9645a4..de78901 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,8 +3,10 @@ use std::{fs::create_dir_all, net::SocketAddr, process::Stdio}; use web::state::{SharedState, TemplateState}; +use executor::executors_id; mod web; +pub mod executor; fn check() { let res = std::process::Command::new("python") @@ -26,7 +28,9 @@ async fn main() { check(); let state = SharedState { - template: TemplateState {} + template: TemplateState { + langs: executors_id() + } }; let routes = web::routes(state); diff --git a/src/web/executor.rs b/src/web/executor.rs index 596957e..855bc3f 100644 --- a/src/web/executor.rs +++ b/src/web/executor.rs @@ -1,11 +1,8 @@ -use std::{fs::{self, remove_file}, io::Read, process::{Command, Stdio}}; - -use rand::{distributions::Alphanumeric, Rng}; use serde::{Serialize, Deserialize}; use warp::{reject::Rejection, reply::{json, with_status, Reply}, Filter, http::StatusCode}; -use crate::SharedState; +use crate::{executor::{executors, executors_id, get_executor, python::PythonExecutor, Executor}, SharedState}; #[derive(Serialize, Deserialize)] struct ExecutorData { @@ -15,49 +12,25 @@ struct ExecutorData { async fn executor(_state: SharedState, data: ExecutorData) -> Result, Rejection> { - if data.lang != "python" { + let lang = data.lang; + let execs = executors(); + let found = get_executor(&lang, &execs); + + if found.is_none() { return Ok( Box::new( with_status( - json(&"only python supported".to_string()), + json(&format!("{lang} is not supported")), StatusCode::BAD_REQUEST ) ) ) } - let name = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(64) - .map(char::from) - .collect::(); - let path = format!("/tmp/sandy-tmp/{name}"); + let lang = found.unwrap(); + let out = lang.exec(data.code).unwrap(); - fs::write(path.clone(), &data.code).unwrap(); - let mut out = Command::new("python") - .arg(path.clone()) - .stdout(Stdio::piped()) - .spawn() - .unwrap(); - - let exit_status = out.wait().unwrap().code().unwrap(); - - let mut buf = vec![]; - out.stdout.unwrap().read_to_end(&mut buf).unwrap(); - - let mut stdout = String::from_utf8(buf).unwrap(); - stdout += "\n---\n"; - if exit_status != 0 { - stdout += format!("Command exited with code {}", exit_status).as_str(); - } else { - stdout += "Command exited with code 0"; - } - - let stdout = stdout.replace('\n', "
"); - - remove_file(path).unwrap(); - - Ok(Box::new(warp::reply::json(&stdout))) + Ok(Box::new(warp::reply::json(&out))) } fn executor_f(state: SharedState) -> impl Filter + Clone { diff --git a/src/web/pages.rs b/src/web/pages.rs index 4b55b9c..91d00c7 100644 --- a/src/web/pages.rs +++ b/src/web/pages.rs @@ -9,7 +9,7 @@ use super::state::TemplateState; #[derive(Template)] #[template( path = "index.html" )] pub struct Index { - pub state: TemplateState + state: TemplateState } impl Index { diff --git a/src/web/state.rs b/src/web/state.rs index 7c29e01..221c46f 100644 --- a/src/web/state.rs +++ b/src/web/state.rs @@ -6,5 +6,5 @@ pub struct SharedState { #[derive(Debug, Clone)] pub struct TemplateState { - + pub langs: Vec } \ No newline at end of file