From ecc4f3ef8a23e3d4143954fdccc5a28a83f9039f Mon Sep 17 00:00:00 2001 From: bnewbold Date: Wed, 11 Jan 2017 18:26:04 -0800 Subject: basic sandbox wiki editing and creation --- src/bin/mt-webface.rs | 169 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 165 insertions(+), 4 deletions(-) (limited to 'src/bin') diff --git a/src/bin/mt-webface.rs b/src/bin/mt-webface.rs index 3d17bb5..96bc456 100644 --- a/src/bin/mt-webface.rs +++ b/src/bin/mt-webface.rs @@ -4,16 +4,21 @@ extern crate pencil; extern crate modelthing; extern crate env_logger; extern crate pulldown_cmark; - -#[macro_use] -extern crate log; +extern crate regex; +#[macro_use] extern crate lazy_static; +#[macro_use] extern crate log; use std::env; +use std::fs::{File, create_dir}; +use std::io::Write; use std::collections::BTreeMap; use std::path::Path; use getopts::Options; use pencil::Pencil; use pencil::{Request, PencilResult, Response, HTTPError, PencilError}; +use pencil::{redirect, abort}; +use pencil::method::{Get, Post}; +use regex::Regex; /* This command doesn't use error_chain (or raise errors in general) because the @@ -35,6 +40,13 @@ fn readme(r: &mut Request) -> PencilResult { fn model_list(r: &mut Request) -> PencilResult { let namespace = r.view_args.get("namespace").unwrap(); + if !is_clean_name(namespace) { + return abort(404); + } + if !(namespace == "examples" || namespace == "sandbox") { + // TODO: check if namespace path exists, else 404 + return abort(404); + } let paths = modelthing::search_models(Path::new(namespace)); let l: Vec> = paths.iter() .map(|x| vec![namespace.to_string(), @@ -46,6 +58,7 @@ fn model_list(r: &mut Request) -> PencilResult { .collect(); let mut context = BTreeMap::new(); context.insert("model_slug_list".to_string(), l); + context.insert("namespace".to_string(), vec![vec![namespace.to_string()]]); return r.app.render_template("model_list.html", &context); } @@ -64,10 +77,13 @@ fn model_view(r: &mut Request) -> PencilResult { context.insert("namespace".to_string(), namespace.to_string()); context.insert("model_slug".to_string(), model_slug.to_string()); context.insert("model_name".to_string(), me.ast.name.clone()); - context.insert("model_description".to_string(), me.ast.description.clone().unwrap_or("".to_string())); + context.insert("model_description".to_string(), + me.ast.description.clone().unwrap_or("".to_string())); context.insert("markdown_html".to_string(), markdown_html); context.insert("markdown".to_string(), me.markdown.clone()); context.insert("modelica".to_string(), format!("{:?}", me.ast)); + context.insert("editable".to_string(), + if namespace == "sandbox" { "true".to_string() } else { "".to_string() }); r.app.render_template("model_view.html", &context) }, Err(_) => Err(PencilError::PenHTTPError(pencil::HTTPError::NotFound)), @@ -82,6 +98,146 @@ fn model_raw(r: &mut Request) -> PencilResult { pencil::helpers::send_from_directory(&model_path, "model.modelica", true) } +// Sends the markdown +fn model_markdown(r: &mut Request) -> PencilResult { + let namespace = r.view_args.get("namespace").unwrap(); + let model_slug = r.view_args.get("model_slug").unwrap(); + let model_path = Path::new(namespace).join(model_slug).to_string_lossy().to_string(); + pencil::helpers::send_from_directory(&model_path, "page.md", true) +} + +fn model_edit(r: &mut Request) -> PencilResult { + let namespace = r.view_args.get("namespace").unwrap().clone(); + let model_slug = r.view_args.get("model_slug").unwrap().clone(); + let model_path = Path::new(&namespace).join(&model_slug); + let mut context = BTreeMap::new(); + if namespace != "sandbox" { + warn!("Tried to write outside of sandbox: {}", namespace); + return abort(403); + } + match modelthing::load_model_entry(model_path.as_path()) { + Ok(me) => { + context.insert("namespace".to_string(), namespace.to_string()); + context.insert("model_slug".to_string(), model_slug.to_string()); + context.insert("model_name".to_string(), me.ast.name.clone()); + context.insert("markdown".to_string(), me.markdown.clone()); + context.insert("modelica".to_string(), format!("{:?}", me.ast)); + match r.method { + Get => { + r.app.render_template("model_edit.html", &context) + }, + Post => { + let modelica = r.form().get("modelica").expect("missing form val: modelica").clone(); + let markdown = r.form().get("markdown").expect("missing form val: markdown").clone(); + + // Check that modelica is valid + if let Err(e) = modelthing::modelica_parser::parse_model(&modelica) { + error!("Modelica code not valid: {:?}", e); + return abort(500); + }; + + { + // overwrite existing + let mut f = File::create(format!("{}/{}/page.md", namespace, model_slug)) + .expect("unable to create wiki file"); + f.write_all(markdown.as_bytes()).expect("unable to write wiki file"); + } + { + // overwrite existing + let mut f = File::create( format!("{}/{}/model.modelica", namespace, model_slug)) + .expect("unable to create modelicafile"); + f.write_all(modelica.as_bytes()).expect("unable to write modelica file"); + } + redirect(&format!("/m/{}/{}/", namespace, model_slug), 301) + }, + _ => { + warn!("Invalid method attempted"); + abort(404) + }, + } + }, + Err(_) => Err(PencilError::PenHTTPError(pencil::HTTPError::NotFound)), + } +} + +fn is_clean_name(s: &str) -> bool { + lazy_static! { + static ref RE: Regex = Regex::new(r"^[0-9A-Za-z_-]+$").unwrap(); + } + RE.is_match(s) +} + +#[test] +fn test_is_clean_name() { + assert_eq!(is_clean_name("name"), true); + assert_eq!(is_clean_name("name.two"), false); + assert_eq!(is_clean_name("name/thing"), false); + assert_eq!(is_clean_name("unicode_α"), false); + assert_eq!(is_clean_name("name_thing"), true); + assert_eq!(is_clean_name("name-thing"), true); + assert_eq!(is_clean_name("1234"), true); +} + +fn model_create(r: &mut Request) -> PencilResult { + let context: BTreeMap = BTreeMap::new(); + match r.method { + Get => { + r.app.render_template("model_create.html", &context) + }, + Post => { + let namespace = r.form().get("namespace").expect("missing form val: namespace").clone(); + let model_slug = r.form().get("model_slug").expect("missing form val: model_slug").clone(); + if namespace != "sandbox" { + warn!("Tried to write outside of sandbox: {}", namespace); + return abort(403); + } + if !(is_clean_name(&namespace) && is_clean_name(&model_slug)) { + warn!("Unclean: {} or {}", namespace, model_slug); + return abort(404); + } + + let modelica = &format!( + "model {}\n\n\"blank description, edit me\"\n\tReal a;\nequation\n\ta = 1;\nend {};\n", + model_slug, + model_slug); + let wiki_text = "blank wikitext; edit me!"; + + // Check that modelica is valid + if let Err(e) = modelthing::modelica_parser::parse_model(modelica) { + error!("Modelica code not valid: {:?}", e); + return abort(500); + }; + + // Ensure that namespace directory exists + if let Err(e) = create_dir(format!("{}", namespace)) { + error!("Couldn't create namespace diectory: {:?}", e); + return abort(500); + }; + + if let Err(e) = create_dir(format!("{}/{}", namespace, model_slug)) { + error!("Couldn't create model diectory: {:?}", e); + return abort(500); + }; + + { + let mut f = File::create(format!("{}/{}/page.md", namespace, model_slug)) + .expect("unable to create wiki file"); + f.write_all(wiki_text.as_bytes()).expect("unable to write wiki file"); + } + { + let mut f = File::create( format!("{}/{}/model.modelica", namespace, model_slug)) + .expect("unable to create modelicafile"); + f.write_all(modelica.as_bytes()).expect("unable to write modelica file"); + } + redirect(&format!("/m/{}/{}/", namespace, model_slug), 301) + }, + _ => { + warn!("Invalid method attempted"); + abort(404) + }, + } +} + fn page_not_found(_: HTTPError) -> PencilResult { let mut response = Response::from("404: Not Found"); response.status_code = 404; @@ -149,6 +305,11 @@ fn main() { app.register_template("model_view.html"); app.get("/m///", "model_view", model_view); app.get("/m///raw/", "model_raw", model_raw); + app.get("/m///markdown/", "model_markdown", model_markdown); + app.register_template("model_create.html"); + app.route("/create/", &[Get, Post], "model_create", model_create); + app.register_template("model_edit.html"); + app.route("/m///edit/", &[Get, Post], "model_edit", model_edit); let bind = matches.opt_str("bind").unwrap_or("127.0.0.1:5000".to_string()); let bind_str: &str = &bind; -- cgit v1.2.3