implement camo in rust

This commit is contained in:
Luna D 2021-09-13 20:54:57 +02:00
parent 1bf7c647a1
commit 6aa9baefd2
No known key found for this signature in database
GPG key ID: 81AF416F2CC36FC8
9 changed files with 302 additions and 145 deletions

3
.gitignore vendored
View file

@ -50,3 +50,6 @@ npm-debug.log
# Unportable compiled binaries
/priv/native/
# Rust binaries
/native/**/target

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -0,0 +1,11 @@
let
pkgs = import <nixos> {};
in pkgs.mkShell {
buildInputs = [
pkgs.cargo
pkgs.rustc
pkgs.rustfmt
pkgs.clippy
pkgs.rust-analyzer
];
}

View file

@ -0,0 +1,46 @@
use ring::hmac;
use std::env;
use url::Url;
fn trusted_host(mut url: Url) -> Option<String> {
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<String> {
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<String> {
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("")),
}
}

View file

@ -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(""))
}

View file

@ -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)
}