use cfg_if::cfg_if; use serde::Deserialize; use js_sys::*; use std::collections::HashMap; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::*; use web_sys::*; cfg_if! { // When the `console_error_panic_hook` feature is enabled, we can call the // `set_panic_hook` function at least once during initialization, and then // we will get better error messages if our code ever panics. // // For more details see // https://github.com/rustwasm/console_error_panic_hook#readme if #[cfg(feature = "console_error_panic_hook")] { extern crate console_error_panic_hook; pub use self::console_error_panic_hook::set_once as set_panic_hook; } else { #[inline] pub fn set_panic_hook() {} } } #[macro_export] macro_rules! cors { ($headers:ident) => { $headers.set("Access-Control-Allow-Origin", "*").unwrap(); $headers.set("Access-Control-Allow-Headers", "*").unwrap(); }; } // Adapted from #[macro_export] macro_rules! headers( { $($key:expr => $value:expr),+ } => { { let headers = ::web_sys::Headers::new().unwrap(); $( headers.set($key, $value).unwrap(); )+ headers } }; () => { ::web_sys::Headers::new().unwrap() }; ); // Remove all non-ascii characters from string pub fn filter_non_ascii_alphanumeric(s: &str) -> String { s.chars().into_iter() .filter(|c| c.is_ascii_alphanumeric() || c.is_whitespace()) .collect() } // A URL is "/" // The UUID involvement is to reduce the chance that two // articles have the same URL by having the same title when // all non-ASCII characters are removed pub fn title_to_url(uuid: &str, title: &str) -> String { let title_part = filter_non_ascii_alphanumeric(title) .split_whitespace() .collect::>() .join("-") .to_lowercase(); format!("{}/{}", &uuid[0..4], title_part) } #[wasm_bindgen] extern "C" { static crypto: Crypto; } // SHA-1 digest (hexed) via SubtleCrypto pub async fn sha1(s: &str) -> String { let mut bytes: Vec = s.bytes().collect(); let promise = crypto.subtle().digest_with_str_and_u8_array("SHA-1", &mut bytes).unwrap(); let buffer: ArrayBuffer = JsFuture::from(promise).await.unwrap().into(); let digest_arr = Uint8Array::new(&buffer).to_vec(); hex::encode(digest_arr) } pub trait HeadersExt { fn add_cors(self) -> Self; } impl HeadersExt for Headers { fn add_cors(self) -> Self { self.set("Access-Control-Allow-Origin", "*").unwrap(); self.set("Access-Control-Allow-Headers", "*").unwrap(); self } } pub trait ResultExt { // Ignore any error and return InternalError for them all // Used in place of ugly `.unwrap()`. fn internal_err(self) -> Result; } impl ResultExt for Result { fn internal_err(self) -> Result { self.map_err(|_| crate::utils::Error::InternalError()) } } pub type MyResult = Result; pub enum Error { NotFound(String), BadRequest(String), Unauthorized(String), InternalError() } impl Error { pub fn status_code(&self) -> u16 { match self { Error::NotFound(_) => 404, Error::BadRequest(_) => 400, Error::Unauthorized(_) => 401, Error::InternalError() => 500 } } } impl Into for Error { fn into(self) -> String { match self { Error::NotFound(reason) => { format!("Not Found, Reason: {}", reason) }, Error::BadRequest(reason) => { format!("Bad Request, Reason: {}", reason) }, Error::Unauthorized(reason) => { format!("Unauthorized, Reason: {}", reason) }, Error::InternalError() => { format!("Internal Errror") } } } } #[derive(Deserialize)] pub struct Config { // The secret value used to authenticate the Standard Notes plugin link pub secret: String, // Title of the blog pub title: String, // Language of blog pub lang: String, // Description of the blog pub description: String, // Plugin identifier used for Standard Notes pub plugin_identifier: String, // How many posts to show in one page pub posts_per_page: usize, // The browser cache timeout for static resources in seconds #[serde(default = "default_maxage")] pub cache_maxage: u64, // Hard-coded redirects (for migrating old articles and such) // Paths here MUST include the starting "/" // UNLIKE in article headers pub redirects: Option>, // Additional remote resource proxy whitelist pub extra_remote_proxy_whitelist: Option>, // Preferred URL of the blog for "Open Post" and "Open Blog" options in SN // Must NOT include the trailing "/" pub preferred_url: Option } fn default_maxage() -> u64 { 60 * 60 * 24 * 7 // default to a week } include!(concat!(env!("OUT_DIR"), "/build_timestamp.rs")); // Strip HTML tags from a string via JS binding pub fn strip_html_tags(s: &str) -> String { let js_str: JsString = s.into(); js_str.replace_by_pattern(&RegExp::new("(<([^>]+)>)", "ig"), "").into() }