From 6aa9baefd23e76ed79aba850c27c1a7a15952f51 Mon Sep 17 00:00:00 2001 From: Luna D Date: Mon, 13 Sep 2021 20:54:57 +0200 Subject: [PATCH] implement camo in rust --- .gitignore | 3 + lib/camo/image.ex | 39 +---- lib/philomena/native.ex | 5 +- native/philomena/Cargo.lock | 273 +++++++++++++++++++++---------- native/philomena/Cargo.toml | 4 +- native/philomena/shell.nix | 11 ++ native/philomena/src/camo.rs | 46 ++++++ native/philomena/src/lib.rs | 37 ++--- native/philomena/src/markdown.rs | 29 ++++ 9 files changed, 302 insertions(+), 145 deletions(-) create mode 100644 native/philomena/shell.nix create mode 100644 native/philomena/src/camo.rs create mode 100644 native/philomena/src/markdown.rs diff --git a/.gitignore b/.gitignore index fcadf67e..0caa7395 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,6 @@ npm-debug.log # Unportable compiled binaries /priv/native/ + +# Rust binaries +/native/**/target diff --git a/lib/camo/image.ex b/lib/camo/image.ex index 43152d7a..a2d49655 100644 --- a/lib/camo/image.ex +++ b/lib/camo/image.ex @@ -1,40 +1,3 @@ defmodule Camo.Image do - def image_url(input) do - uri = URI.parse(input) - - cond do - is_nil(uri.host) -> - "" - - is_nil(camo_key()) -> - input - - uri.host in [cdn_host(), camo_host()] -> - URI.to_string(%{uri | scheme: "https", port: 443}) - - true -> - camo_digest = :crypto.mac(:hmac, :sha, camo_key(), input) |> Base.encode16(case: :lower) - - camo_uri = %URI{ - host: camo_host(), - path: "/" <> camo_digest, - query: URI.encode_query(url: input), - scheme: "https" - } - - URI.to_string(camo_uri) - end - end - - defp cdn_host do - Application.get_env(:philomena, :cdn_host) - end - - defp camo_key do - Application.get_env(:philomena, :camo_key) - end - - defp camo_host do - Application.get_env(:philomena, :camo_host) - end + def image_url(input), do: Philomena.Native.camo_image_url(input) end diff --git a/lib/philomena/native.ex b/lib/philomena/native.ex index b5950029..4d9f5ebf 100644 --- a/lib/philomena/native.ex +++ b/lib/philomena/native.ex @@ -1,7 +1,10 @@ defmodule Philomena.Native do use Rustler, otp_app: :philomena - # When your NIF is loaded, it will override this function. + # Markdown def markdown_to_html(_text), do: :erlang.nif_error(:nif_not_loaded) def markdown_to_html_unsafe(_text), do: :erlang.nif_error(:nif_not_loaded) + + # Camo + def camo_image_url(_uri), do: :erlang.nif_error(:nif_not_loaded) end diff --git a/native/philomena/Cargo.lock b/native/philomena/Cargo.lock index f808352d..817d2379 100644 --- a/native/philomena/Cargo.lock +++ b/native/philomena/Cargo.lock @@ -43,6 +43,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "base16" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27c3610c36aee21ce8ac510e6224498de4228ad772a171ed65643a24693a5a8" + [[package]] name = "base64" version = "0.13.0" @@ -85,6 +91,12 @@ dependencies = [ "byte-tools", ] +[[package]] +name = "bumpalo" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" + [[package]] name = "byte-tools" version = "0.3.1" @@ -201,24 +213,22 @@ 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 = "fs_extra" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - -[[package]] -name = "gcc" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" - [[package]] name = "generic-array" version = "0.12.4" @@ -252,6 +262,17 @@ dependencies = [ "libc", ] +[[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 = "indexmap" version = "1.7.0" @@ -289,6 +310,15 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1866b355d9c878e5e607473cbe3f63282c0b7aad2db1dbebf55076c686918254" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -316,12 +346,27 @@ dependencies = [ "safemem", ] +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + [[package]] name = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + [[package]] name = "memchr" version = "2.4.1" @@ -357,6 +402,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + [[package]] name = "onig" version = "6.2.0" @@ -385,6 +436,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + [[package]] name = "pest" version = "2.1.3" @@ -432,10 +489,12 @@ dependencies = [ name = "philomena" version = "0.3.0" dependencies = [ + "base16", "comrak", "jemallocator", - "rust-crypto", + "ring", "rustler", + "url", ] [[package]] @@ -476,53 +535,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" -dependencies = [ - "libc", - "rand 0.4.6", -] - -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "regex" version = "1.5.4" @@ -541,24 +553,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] -name = "rust-crypto" -version = "0.2.36" +name = "ring" +version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" dependencies = [ - "gcc", + "cc", "libc", - "rand 0.3.23", - "rustc-serialize", - "time", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", ] -[[package]] -name = "rustc-serialize" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" - [[package]] name = "rustler" version = "0.22.0" @@ -658,6 +666,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fa3938c99da4914afedd13bf3d79bcb6c277d1b2c398d23257a304d9e1b074" +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "strsim" version = "0.8.0" @@ -706,16 +720,20 @@ dependencies = [ ] [[package]] -name = "time" -version = "0.1.44" +name = "tinyvec" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "5241dd6f21443a3606b432718b166d3cedc962fd4b8bea54a8bc7f514ebda986" dependencies = [ - "libc", - "wasi", - "winapi", + "tinyvec_macros", ] +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + [[package]] name = "twoway" version = "0.2.2" @@ -750,6 +768,21 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" +[[package]] +name = "unicode-bidi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085" + +[[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-segmentation" version = "1.8.0" @@ -783,6 +816,24 @@ dependencies = [ "void", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[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 = "vec_map" version = "0.8.2" @@ -807,10 +858,68 @@ dependencies = [ ] [[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" +name = "wasm-bindgen" +version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +checksum = "5e68338db6becec24d3c7977b5bf8a48be992c934b5d07177e3931f5dc9b076c" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f34c405b4f0658583dba0c1c7c9b694f3cac32655db463b56c254a1c75269523" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d5a6580be83b19dc570a8f9c324251687ab2184e57086f71625feb57ec77c8" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3775a030dc6f5a0afd8a84981a21cc92a781eb429acef9ecce476d0c9113e92" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c279e376c7a8e8752a8f1eaa35b7b0bee6bb9fb0cdacfa97cc3f1f289c87e2b4" + +[[package]] +name = "web-sys" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a84d70d1ec7d2da2d26a5bd78f4bca1b8c3254805363ce743b7a05bc30d195a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] name = "winapi" diff --git a/native/philomena/Cargo.toml b/native/philomena/Cargo.toml index 2ef3e497..2fb77ea9 100644 --- a/native/philomena/Cargo.toml +++ b/native/philomena/Cargo.toml @@ -13,7 +13,9 @@ crate-type = ["dylib"] comrak = { git = "https://github.com/philomena-dev/comrak", branch = "main" } rustler = "0.22" jemallocator = "0.3.2" -rust-crypto = "0.2" +ring = "0.16" +base16 = "0.2" +url = "2.2" [profile.release] opt-level = 3 diff --git a/native/philomena/shell.nix b/native/philomena/shell.nix new file mode 100644 index 00000000..92bfa4f6 --- /dev/null +++ b/native/philomena/shell.nix @@ -0,0 +1,11 @@ +let + pkgs = import {}; +in pkgs.mkShell { + buildInputs = [ + pkgs.cargo + pkgs.rustc + pkgs.rustfmt + pkgs.clippy + pkgs.rust-analyzer + ]; +} diff --git a/native/philomena/src/camo.rs b/native/philomena/src/camo.rs new file mode 100644 index 00000000..cdd4d041 --- /dev/null +++ b/native/philomena/src/camo.rs @@ -0,0 +1,46 @@ +use ring::hmac; +use std::env; +use url::Url; + +fn trusted_host(mut url: Url) -> Option { + url.set_port(Some(443)).ok()?; + url.set_scheme("https").ok()?; + + Some(url.to_string()) +} + +fn untrusted_host(url: Url, camo_host: String, camo_key: String) -> Option { + let camo_url = format!("https://{}", camo_host); + let key = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, camo_key.as_ref()); + let tag = hmac::sign(&key, url.to_string().as_bytes()); + let encoded = base16::encode_lower(tag.as_ref()); + + let mut camo_uri = Url::parse(&camo_url).ok()?; + camo_uri.set_path(&encoded); + camo_uri.set_port(Some(443)).ok()?; + camo_uri.set_scheme("https").ok()?; + camo_uri + .query_pairs_mut() + .clear() + .append_pair("url", &url.to_string()); + + Some(camo_uri.to_string()) +} + +pub fn image_url(uri: String) -> Option { + let cdn_host = env::var("CDN_HOST").ok()?; + let camo_host = env::var("CAMO_HOST").ok()?; + let camo_key = env::var("CAMO_KEY").ok()?; + + if camo_key.is_empty() { + return Some(uri); + } + + let url = Url::parse(&uri).ok()?; + + match url.host_str() { + Some(hostname) if hostname == cdn_host || hostname == camo_host => trusted_host(url), + Some(_) => untrusted_host(url, camo_host, camo_key), + None => Some(String::from("")), + } +} diff --git a/native/philomena/src/lib.rs b/native/philomena/src/lib.rs index 03bcf8b1..6cc393f6 100644 --- a/native/philomena/src/lib.rs +++ b/native/philomena/src/lib.rs @@ -1,40 +1,31 @@ -use comrak::ComrakOptions; use jemallocator::Jemalloc; +mod camo; +mod markdown; + #[global_allocator] static GLOBAL: Jemalloc = Jemalloc; rustler::init! { "Elixir.Philomena.Native", - [markdown_to_html, markdown_to_html_unsafe] + [markdown_to_html, markdown_to_html_unsafe, camo_image_url] } -fn common_options() -> ComrakOptions { - let mut options = ComrakOptions::default(); - options.extension.autolink = true; - options.extension.table = true; - options.extension.description_lists = true; - options.extension.superscript = true; - options.extension.strikethrough = true; - options.extension.philomena = true; - options.parse.smart = true; - options.render.hardbreaks = true; - options.render.github_pre_lang = true; - options -} +// Markdown NIF wrappers. #[rustler::nif(schedule = "DirtyCpu")] fn markdown_to_html(input: String) -> String { - let mut options = common_options(); - options.render.escape = true; - - comrak::markdown_to_html(&input, &options) + markdown::to_html(input) } #[rustler::nif(schedule = "DirtyCpu")] fn markdown_to_html_unsafe(input: String) -> String { - let mut options = common_options(); - options.render.unsafe_ = true; - - comrak::markdown_to_html(&input, &options) + markdown::to_html_unsafe(input) +} + +// Camo NIF wrappers. + +#[rustler::nif] +fn camo_image_url(input: String) -> String { + camo::image_url(input).unwrap_or_else(|| String::from("")) } diff --git a/native/philomena/src/markdown.rs b/native/philomena/src/markdown.rs new file mode 100644 index 00000000..41fcac45 --- /dev/null +++ b/native/philomena/src/markdown.rs @@ -0,0 +1,29 @@ +use comrak::ComrakOptions; + +fn common_options() -> ComrakOptions { + let mut options = ComrakOptions::default(); + options.extension.autolink = true; + options.extension.table = true; + options.extension.description_lists = true; + options.extension.superscript = true; + options.extension.strikethrough = true; + options.extension.philomena = true; + options.parse.smart = true; + options.render.hardbreaks = true; + options.render.github_pre_lang = true; + options +} + +pub fn to_html(input: String) -> String { + let mut options = common_options(); + options.render.escape = true; + + comrak::markdown_to_html(&input, &options) +} + +pub fn to_html_unsafe(input: String) -> String { + let mut options = common_options(); + options.render.unsafe_ = true; + + comrak::markdown_to_html(&input, &options) +}