diff --git a/Cargo.lock b/Cargo.lock index 9240e0b..c6f773f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "ammonia" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee7d6eb157f337c5cedc95ddf17f0cbc36d36eb7763c8e0d1c1aeb3722f6279" +dependencies = [ + "html5ever", + "lazy_static", + "maplit", + "markup5ever_rcdom", + "matches", + "tendril", + "url", +] + [[package]] name = "async-stream" version = "0.3.2" @@ -166,7 +181,7 @@ dependencies = [ "libc", "num-integer", "num-traits", - "time 0.1.43", + "time 0.1.44", "winapi 0.3.9", ] @@ -272,6 +287,7 @@ checksum = "bba51ca66f57261fd17cadf8b73e4775cc307d0521d855de3f5de91a8f074e0e" dependencies = [ "bitflags", "byteorder", + "chrono", "diesel_derives", "pq-sys", "r2d2", @@ -366,6 +382,16 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + [[package]] name = "fsevent" version = "0.4.0" @@ -401,6 +427,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "futf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.15" @@ -517,6 +553,26 @@ dependencies = [ "typenum", ] +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.3" @@ -525,7 +581,7 @@ checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi", + "wasi 0.10.0+wasi-snapshot-preview1", ] [[package]] @@ -592,6 +648,20 @@ dependencies = [ "libc", ] +[[package]] +name = "html5ever" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "http" version = "0.2.4" @@ -656,6 +726,17 @@ dependencies = [ "want", ] +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "ignore" version = "0.4.18" @@ -729,6 +810,15 @@ dependencies = [ "libc", ] +[[package]] +name = "itertools" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.7" @@ -794,12 +884,50 @@ dependencies = [ "serde_json", ] +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + [[package]] name = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +[[package]] +name = "markup5ever" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" +dependencies = [ + "log", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f015da43bcd8d4f144559a3423f4591d69b8ce0652c905374da7205df336ae2b" +dependencies = [ + "html5ever", + "markup5ever", + "tendril", + "xml5ever", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + [[package]] name = "memchr" version = "2.4.0" @@ -930,6 +1058,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + [[package]] name = "normpath" version = "0.3.0" @@ -1045,8 +1179,15 @@ dependencies = [ name = "pastebinrun" version = "0.1.0" dependencies = [ + "ammonia", + "chrono", "diesel", "diesel_migrations", + "itertools", + "log", + "once_cell", + "pulldown-cmark", + "rand 0.8.3", "rocket", "rocket_dyn_templates", "rocket_sync_db_pools", @@ -1126,6 +1267,44 @@ dependencies = [ "sha-1", ] +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared", + "rand 0.7.3", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.6" @@ -1153,6 +1332,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -1187,6 +1372,18 @@ dependencies = [ "yansi", ] +[[package]] +name = "pulldown-cmark" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +dependencies = [ + "bitflags", + "getopts", + "memchr", + "unicase", +] + [[package]] name = "quote" version = "1.0.9" @@ -1207,6 +1404,20 @@ dependencies = [ "scheduled-thread-pool", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", + "rand_pcg", +] + [[package]] name = "rand" version = "0.8.3" @@ -1214,9 +1425,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ "libc", - "rand_chacha", - "rand_core", - "rand_hc", + "rand_chacha 0.3.1", + "rand_core 0.6.2", + "rand_hc 0.3.0", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] @@ -1226,7 +1447,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.2", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", ] [[package]] @@ -1235,7 +1465,16 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" dependencies = [ - "getrandom", + "getrandom 0.2.3", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", ] [[package]] @@ -1244,7 +1483,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" dependencies = [ - "rand_core", + "rand_core 0.6.2", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", ] [[package]] @@ -1324,7 +1572,7 @@ dependencies = [ "num_cpus", "parking_lot", "pin-project-lite", - "rand", + "rand 0.8.3", "ref-cast", "rocket_codegen", "rocket_http", @@ -1547,6 +1795,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27" + [[package]] name = "slab" version = "0.4.3" @@ -1660,6 +1914,31 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" +[[package]] +name = "string_cache" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a" +dependencies = [ + "lazy_static", + "new_debug_unreachable", + "phf_shared", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + [[package]] name = "syn" version = "1.0.73" @@ -1679,12 +1958,23 @@ checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ "cfg-if 1.0.0", "libc", - "rand", + "rand 0.8.3", "redox_syscall", "remove_dir_all", "winapi 0.3.9", ] +[[package]] +name = "tendril" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "tera" version = "1.10.0" @@ -1699,7 +1989,7 @@ dependencies = [ "percent-encoding", "pest", "pest_derive", - "rand", + "rand 0.8.3", "regex", "serde", "serde_json", @@ -1718,11 +2008,12 @@ dependencies = [ [[package]] name = "time" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi 0.3.9", ] @@ -1764,6 +2055,21 @@ dependencies = [ "syn", ] +[[package]] +name = "tinyvec" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + [[package]] name = "tokio" version = "1.6.1" @@ -1957,12 +2263,63 @@ dependencies = [ "unic-common", ] +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "vcpkg" version = "0.2.13" @@ -1998,9 +2355,15 @@ dependencies = [ [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" @@ -2109,6 +2472,18 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "xml5ever" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b1b52e6e8614d4a58b8e70cf51ec0cc21b256ad8206708bcff8139b5bbd6a59" +dependencies = [ + "log", + "mac", + "markup5ever", + "time 0.1.44", +] + [[package]] name = "yansi" version = "0.5.0" diff --git a/Cargo.toml b/Cargo.toml index 7dc95e1..6cb8c7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,15 @@ license = "AGPL-3.0-or-later" build = "src/build.rs" [dependencies] -diesel = "1.4.6" +ammonia = "3.1.1" +chrono = "0.4.19" +diesel = { version = "1.4.6", features = ["chrono"] } diesel_migrations = "1.4.0" +itertools = "0.10.1" +log = "0.4.14" +once_cell = "1.8.0" +pulldown-cmark = "0.8.0" +rand = "0.8.3" rocket = "0.5.0-rc.1" rocket_dyn_templates = { version = "0.1.0-rc.1", features = ["tera"] } rocket_sync_db_pools = { version = "0.1.0-rc.1", features = ["diesel_postgres_pool"] } diff --git a/src/main.rs b/src/main.rs index e071aa2..da69fe4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,21 +14,23 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -#![cfg_attr(test, deny(warnings))] - #[macro_use] extern crate diesel; +#[macro_use] +extern crate rocket; mod migration; mod models; mod schema; use crate::models::language::Language; +use crate::models::paste::{self, ExtraPasteParameters, InsertionError}; +use chrono::{Duration, Utc}; use diesel::PgConnection; use rocket::fairing::AdHoc; +use rocket::form::Form; use rocket::fs::{relative, FileServer}; -use rocket::response::Debug; -use rocket::{get, launch, routes}; +use rocket::response::{Debug, Redirect}; use rocket_dyn_templates::Template; use rocket_sync_db_pools::database; use serde::Serialize; @@ -47,6 +49,54 @@ async fn index(db: Db) -> Result> { Ok(Template::render("index", &Index { languages })) } +#[derive(FromForm)] +pub struct PasteForm { + language: String, + code: String, + share: Share, + #[field(default = "")] + stdin: String, + stdout: Option, + stderr: Option, + status: Option, +} + +#[derive(FromFormField)] +pub enum Share { + Share, + Share24, +} + +#[post("/", data = "
")] +async fn insert_paste(db: Db, form: Form) -> Result { + let delete_at = match form.share { + Share::Share => None, + Share::Share24 => Some(Utc::now() + Duration::hours(24)), + }; + let identifier = db + .run(move |conn| { + paste::insert( + conn, + delete_at, + &form.language, + &form.code, + ExtraPasteParameters { + stdin: &form.stdin, + stdout: form.stdout.as_deref(), + stderr: form.stderr.as_deref(), + exit_code: form.status, + }, + ) + }) + .await?; + Ok(Redirect::to(uri!(display_paste(identifier)))) +} + +#[get("/<_identifier>")] +async fn display_paste(_identifier: &str) { + unimplemented!() +} + #[launch] async fn rocket() -> _ { rocket::build() @@ -64,6 +114,6 @@ async fn rocket() -> _ { .expect("database to be migrated"); rocket })) - .mount("/", routes![index]) + .mount("/", routes![index, insert_paste, display_paste]) .mount("/static", FileServer::from(relative!("static"))) } diff --git a/src/models/mod.rs b/src/models/mod.rs index 6a2d23e..f71600b 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -15,3 +15,4 @@ // along with this program. If not, see . pub mod language; +pub mod paste; diff --git a/src/models/paste.rs b/src/models/paste.rs new file mode 100644 index 0000000..2b156c4 --- /dev/null +++ b/src/models/paste.rs @@ -0,0 +1,240 @@ +// pastebin.run +// Copyright (C) 2020 Konrad Borowski +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::schema::{languages, pastes}; +use ammonia::Builder; +use chrono::{DateTime, Utc}; +use diesel::prelude::*; +use itertools::Itertools; +use log::info; +use once_cell::sync::Lazy; +use pulldown_cmark::{Options, Parser}; +use rand::seq::SliceRandom; +use rocket::http::Status; +use rocket::response::{self, Debug, Responder}; +use rocket::Request; +use std::iter; + +#[derive(Queryable)] +pub struct Paste { + pub identifier: String, + pub paste: String, + pub language_id: i32, + pub delete_at: Option>, + pub language_identifier: String, + pub stdin: String, + pub exit_code: Option, + pub stdout: Option, + pub stderr: Option, +} + +impl Paste { + pub fn delete_old(connection: &PgConnection) -> Result<(), diesel::result::Error> { + let pastes = diesel::delete(pastes::table) + .filter(pastes::delete_at.lt(Utc::now())) + .execute(connection)?; + if pastes > 0 { + info!("Deleted {} paste(s)", pastes); + } + Ok(()) + } +} + +const CHARACTERS: &[u8] = b"23456789bcdfghjkmnpqrstvwxz-"; + +#[derive(Insertable)] +#[table_name = "pastes"] +struct InsertPaste<'a> { + identifier: &'a str, + delete_at: Option>, + language_id: i32, + paste: &'a str, + stdin: &'a str, + stdout: Option<&'a str>, + stderr: Option<&'a str>, + exit_code: Option, +} + +#[derive(Default)] +pub struct ExtraPasteParameters<'a> { + pub stdin: &'a str, + pub stdout: Option<&'a str>, + pub stderr: Option<&'a str>, + pub exit_code: Option, +} + +pub enum InsertionError { + Diesel(diesel::result::Error), + UnrecognizedLanguageIdentifier, +} + +impl From for InsertionError { + fn from(e: diesel::result::Error) -> Self { + Self::Diesel(e) + } +} + +impl<'r> Responder<'r, 'static> for InsertionError { + fn respond_to(self, request: &'r Request<'_>) -> response::Result<'static> { + match self { + Self::Diesel(e) => Debug(e).respond_to(request), + Self::UnrecognizedLanguageIdentifier => Err(Status::BadRequest), + } + } +} + +pub fn insert( + connection: &PgConnection, + delete_at: Option>, + language: &str, + paste: &str, + ExtraPasteParameters { + stdin, + stdout, + stderr, + exit_code, + }: ExtraPasteParameters, +) -> Result { + let mut rng = rand::thread_rng(); + let identifier: String = (0..12) + .map(|_| char::from(*CHARACTERS.choose(&mut rng).expect("a random character"))) + .collect(); + let language_id = languages::table + .select(languages::language_id) + .filter(languages::identifier.eq(language)) + .get_result(connection) + .optional()? + .ok_or(InsertionError::UnrecognizedLanguageIdentifier)?; + let insert_paste = InsertPaste { + identifier: &identifier, + delete_at, + language_id, + paste, + stdin, + stdout, + stderr, + exit_code, + }; + diesel::insert_into(pastes::table) + .values(&insert_paste) + .execute(connection)?; + Ok(identifier) +} + +#[derive(Default)] +pub struct ExternPaste { + pub identifier: Option, + pub paste: String, + pub language_id: i32, + pub delete_at: Option>, + pub markdown: String, + pub stdin: String, + pub exit_code: Option, + pub stdout: Option, + pub stderr: Option, +} + +impl ExternPaste { + pub fn from_paste(paste: Paste) -> Self { + let Paste { + identifier, + paste, + language_id, + language_identifier, + delete_at, + stdin, + exit_code, + stdout, + stderr, + } = paste; + let markdown = if language_identifier == "markdown" { + render_markdown(&paste) + } else { + String::new() + }; + Self { + identifier: Some(identifier), + paste, + language_id, + delete_at, + markdown, + stdin, + exit_code, + stdout, + stderr, + } + } +} + +fn render_markdown(markdown: &str) -> String { + static FILTER: Lazy> = Lazy::new(|| { + let mut builder = Builder::new(); + builder.link_rel(Some("noopener noreferrer nofollow")); + builder.add_generic_attributes(iter::once("class")); + builder.attribute_filter(|_, attribute, value| { + if attribute == "class" { + Some( + value + .split_ascii_whitespace() + .filter(|value| value.starts_with("language-")) + .join(" ") + .into(), + ) + } else { + Some(value.into()) + } + }); + builder + }); + let mut output = String::new(); + let options = Options::ENABLE_TABLES | Options::ENABLE_STRIKETHROUGH; + pulldown_cmark::html::push_html(&mut output, Parser::new_ext(markdown, options)); + FILTER.clean(&output).to_string() +} + +#[cfg(test)] +mod test { + use super::render_markdown; + + #[test] + fn markdown_works() { + assert_eq!( + render_markdown("**bold**"), + "

bold

\n" + ); + } + + #[test] + fn strikethrough_works() { + assert_eq!(render_markdown("~~strike~~"), "

strike

\n"); + } + + #[test] + fn code_blocks_work() { + assert_eq!( + render_markdown("```rust\nfn main() {}\n```"), + "
fn main() {}\n
\n", + ); + } + + #[test] + fn only_language_classes_are_allowed() { + assert_eq!( + render_markdown(r#"
"#), + "
", + ); + } +}