diff --git a/Cargo.toml b/Cargo.toml index f212fe1..4907afb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,4 +20,5 @@ sanitize-filename = "0.3.0" log = "0.4" env_logger = "0.9.0" actix-web-httpauth = "0.6.0" -lazy_static = "1.4.0" \ No newline at end of file +lazy_static = "1.4.0" +rutie = "0.8.4" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 28be727..9e743d1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,5 +22,10 @@ COPY --from=builder /usr/src/microbin/target/release/microbin /usr/local/bin/mic # copy /static folder containing the stylesheets COPY --from=builder /usr/src/microbin/static /usr/local/bin/static +# Install Ruby (no need if you're disabling all plugins) +RUN \ + apt-get update && \ + apt-get install -y ruby + # run the binary CMD ["microbin"] \ No newline at end of file diff --git a/plugins/BasicSyntaxHighlighter.rb b/plugins/BasicSyntaxHighlighter.rb new file mode 100644 index 0000000..db561b9 --- /dev/null +++ b/plugins/BasicSyntaxHighlighter.rb @@ -0,0 +1,78 @@ +module MBP + module BasicSyntaxHighlighter + + # Plugin Properties + + def self.get_id() + "BasicSyntaxHighlighter" + end + + def self.get_name() + "Basic Syntax Highlighter Plugin" + end + + def self.get_version() + "1.0.0" + end + + def self.get_author() + "Daniel Szabo" + end + + def self.get_webpage() + "https://github.com/szabodanika/microbin" + end + + def self.get_description() + "This plugin will simply color keywords and special characters in four different colors based on some very basic RegEx - it is meant to univesally make code pastas more readable but is not a robust syntax highlighter solution." + end + + # Plugin Event Hooks + + def self.init() + # Ignore event + "OK" + end + + def self.on_pasta_created(content) + # We do not modify stored content + return content + end + + def self.on_pasta_read(content) + + tokens = { + + "orchid" => [/([0-9])/, /([t|T][r|R][u|U][e|E]|[f|F][a|A][l|L][s|S][e|E])/], + + "palevioletred" => ['(', ')', '{', '}', '[', ']'], + + "royalblue" => [/(\s(for|while|do|select|async|await|mut|break|continue|in|as|switch|let|fn|async|if|else|elseif|new|switch|match|case|default|public|protected|private|return|class|interface|static|final|const|var|int|integer|boolean|float|double|module|def|end|void))(?![a-z])/], + + "mediumorchid" => [/(:|\.|;|=|>|<|\?|!|#|%|@|\^|&|\*|\|)/], + + "mediumseagreen" => [/(\".*\")/, /(\'.*\')/] + + }; + + tokens.each { | color, tokens | + for token in tokens do + if(token.class == String) + content.gsub!(token, "$$#{color}$$" + token + "$$/#{color}$$") + elsif + content.gsub!(token, "$$#{color}$$" + '\1' + "$$/#{color}$$") + end + end + }; + + tokens.each { | color, tokens | + content.gsub!("$$#{color}$$", ""); + content.gsub!("$$/#{color}$$", ""); + }; + + return content + + end + + end +end \ No newline at end of file diff --git a/plugins/MBPlugin.rb b/plugins/MBPlugin.rb new file mode 100644 index 0000000..b463c01 --- /dev/null +++ b/plugins/MBPlugin.rb @@ -0,0 +1,78 @@ +require 'rutie' + +module MB + + class MBPlugin + # Plugin Properties + + def self.get_id() + "[Enter Plugin ID]" + end + + def self.get_name() + "[Eenter Plugin Name]" + end + + def self.get_version() + "1.0.0" + end + + def self.get_author() + "[Enter Author name]" + end + + def self.get_webpage() + "[Enter Web URL]" + end + + def self.get_description() + "[Enter Description]" + end + + # Plugin Event Hooks + + def self.init() + raise "Operation not supported"; + end + + def self.on_pasta_deleted(id, content, created, expiration, file) + raise "Operation not supported"; + end + + def self.on_pasta_expired(id, content, created, expiration, file) + raise "Operation not supported"; + end + + def self.on_pasta_created(id, content, created, expiration, file) + raise "Operation not supported"; + end + + def self.on_pasta_read(id, content, created, expiration, file) + raise "Operation not supported"; + end + + # Rust Function Calls + + def self.init() + raise "Operation not supported"; + end + + def self.P=on_pasta_deleted(id, content, created, expiration, file) + raise "Operation not supported"; + end + + def self.on_pasta_expired(id, content, created, expiration, file) + raise "Operation not supported"; + end + + def self.on_pasta_created(id, content, created, expiration, file) + raise "Operation not supported"; + end + + def self.on_pasta_read(id, content, created, expiration, file) + raise "Operation not supported"; + end + + end + +end \ No newline at end of file diff --git a/plugins/helloWorld.rb b/plugins/helloWorld.rb new file mode 100644 index 0000000..b94cd8b --- /dev/null +++ b/plugins/helloWorld.rb @@ -0,0 +1,42 @@ +module MBP + class HelloWorld < MBPlugin + + def self.get_id() + "HelloWorld" + end + + def self.get_name() + "Hello World Plugin" + end + + def self.get_version() + "1.0.0" + end + + def self.get_description() + "This is just a demo plugin. It does not do anything." + end + + def self.get_author() + "Daniel Szabo" + end + + def self.get_webpage() + "https://github.com/szabodanika/microbin" + end + + def self.init() + # Ignore event + "OK" + end + + def self.on_pasta_created(content) + return content + end + + def self.on_pasta_read(content) + return content + end + + end +end diff --git a/src/animalnumbers.rs b/src/animalnumbers.rs index a94e702..8af5ac9 100644 --- a/src/animalnumbers.rs +++ b/src/animalnumbers.rs @@ -24,7 +24,7 @@ pub fn to_animal_names(mut number: u64) -> String { number -= digit * ANIMAL_NAMES.len().pow(power) as u64; if power > 0 { power -= 1; - } else if power <= 0 || number == 0 { + } else if power == 0 || number == 0 { break; } } diff --git a/src/main.rs b/src/main.rs index 6b2c8f4..7053d04 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,6 +29,7 @@ use crate::pasta::Pasta; mod animalnumbers; mod dbio; mod pasta; +mod plugins; lazy_static! { static ref ARGS: Args = Args::parse(); @@ -47,6 +48,12 @@ struct Args { #[clap(short, long, default_value_t = 1)] threads: u8, + #[clap(short, long)] + wide: bool, + + #[clap(short, long, default_value_t = 3)] + animals: u8, + #[clap(long)] hide_header: bool, @@ -103,7 +110,7 @@ struct ErrorTemplate<'a> { } #[derive(Template)] -#[template(path = "pasta.html")] +#[template(path = "pasta.html", escape = "none")] struct PastaTemplate<'a> { pasta: &'a Pasta, args: &'a Args, @@ -165,7 +172,8 @@ async fn create(data: web::Data, mut payload: Multipart) -> Result { while let Some(chunk) = field.try_next().await? { - new_pasta.content = std::str::from_utf8(&chunk).unwrap().to_string(); + new_pasta.content = + plugins::on_pasta_created(std::str::from_utf8(&chunk).unwrap()); new_pasta.pasta_type = if is_valid_url(new_pasta.content.as_str()) { String::from("url") } else { @@ -223,9 +231,23 @@ async fn getpasta(data: web::Data, id: web::Path) -> HttpRespo for pasta in pastas.iter() { if pasta.id == id { - return HttpResponse::Found() - .content_type("text/html") - .body(PastaTemplate { pasta, args: &ARGS }.render().unwrap()); + let pasta_copy = Pasta { + id: pasta.id, + content: plugins::on_pasta_read(&pasta.content), + file: pasta.file.to_string(), + created: pasta.created, + pasta_type: pasta.pasta_type.to_string(), + expiration: pasta.expiration, + }; + + return HttpResponse::Found().content_type("text/html").body( + PastaTemplate { + pasta: &pasta_copy, + args: &ARGS, + } + .render() + .unwrap(), + ); } } diff --git a/src/plugins.rs b/src/plugins.rs new file mode 100644 index 0000000..03e8950 --- /dev/null +++ b/src/plugins.rs @@ -0,0 +1,134 @@ +extern crate rutie; + +use lazy_static::lazy_static; +use log::{error, log}; +use rutie::{AnyException, AnyObject, Object, RString, VM}; +use std::fs::File; +use std::io::{BufReader, Read}; +use std::{fs, io}; + +const CACHE_PLUGINS: bool = false; + +lazy_static! { + static ref PLUGIN_IDENTIFIERS: Vec = init(); +} + +fn init() -> Vec { + VM::init(); + + let plugin_paths = load_plugin_paths(); + + let plugin_codes = read_plugins(plugin_paths.clone()); + + feed_plugins(plugin_codes); + + let identifiers = get_plugin_identifiers(plugin_paths); + + init_plugins(&identifiers); + + identifiers +} + +pub fn pasta_filter(s: &str) -> bool { + true +} + +pub fn on_pasta_read(s: &str) -> String { + let mut processed_content: String = String::from(s); + + for PLUGIN_IDENTIFIER in PLUGIN_IDENTIFIERS.iter() { + processed_content = eval_for_string(PLUGIN_IDENTIFIER, "on_pasta_read", s); + } + + processed_content +} + +pub fn on_pasta_created(s: &str) -> String { + let mut processed_content: String = String::from(s); + + for PLUGIN_IDENTIFIER in PLUGIN_IDENTIFIERS.iter() { + processed_content = eval_for_string(PLUGIN_IDENTIFIER, "on_pasta_created", s); + } + + processed_content +} + +pub fn init_plugins(plugin_identifiers: &Vec) { + for PLUGIN_IDENTIFIER in plugin_identifiers.iter() { + eval_for_string(PLUGIN_IDENTIFIER, "init", ""); + + let init_result = eval_for_string(&PLUGIN_IDENTIFIER, "init", ""); + let id = eval_for_string(&PLUGIN_IDENTIFIER, "get_id", ""); + let name = eval_for_string(&id, "get_name", ""); + let version = eval_for_string(&id, "get_version", ""); + + log::info!("Initialised plugin {id} - {name} ({version})"); + } +} + +fn eval_for_string(plugin_id: &str, function: &str, parameter: &str) -> String { + match VM::eval(&*format!("MBP::{}::{}({})", plugin_id, function, parameter)) { + Ok(result) => match result.try_convert_to::() { + Ok(ruby_string) => ruby_string.to_string(), + Err(err) => err.to_string(), + }, + Err(err) => { + log::error!( + "Failed to run function '{}' on plugin {}: {}", + function, + plugin_id, + err + ); + err.to_string() + } + } +} + +fn load_plugin_paths() -> Vec { + let paths = fs::read_dir("./plugins").expect("Failed to access ./plugins library."); + + let mut plugin_paths: Vec = Vec::new(); + + for path in paths { + plugin_paths.push(path.unwrap().path().to_str().unwrap().parse().unwrap()); + } + + plugin_paths +} + +fn read_plugins(plugin_paths: Vec) -> Vec { + let mut plugin_codes: Vec = Vec::new(); + + for plugin_path in plugin_paths { + let plugin_code = match fs::read_to_string(&plugin_path) { + Ok(result) => result, + Err(err) => { + log::error!("Failed to read plugin file {}: {}", plugin_path, err); + continue; + } + }; + plugin_codes.push(plugin_code); + } + + plugin_codes +} + +fn feed_plugins(plugin_codes: Vec) { + for plugin_code in plugin_codes { + match VM::eval(plugin_code.as_str()) { + Ok(result) => {} + Err(error) => { + log::error!("Failed to initialise plugin: {}", error); + continue; + } + } + } +} + +fn get_plugin_identifiers(plugin_paths: Vec) -> Vec { + let mut plugin_ids: Vec = Vec::new(); + for plugin_path in plugin_paths { + plugin_ids.push(plugin_path.replace("./plugins/", "").replace(".rb", "")) + } + plugin_ids +}