// Back - An extremely simple git issue tracking system. Inspired by tvix's // panettone // // Copyright (C) 2024 Benedikt Peetz // SPDX-License-Identifier: AGPL-3.0-or-later // // This file is part of Back. // // You should have received a copy of the License along with this program. // If not, see . use std::fmt::Display; use chrono::DateTime; use markdown::to_html; use serde::Deserialize; use serde_json::Value; #[derive(Debug, Default, Clone)] /// Markdown content. pub struct MarkDown { value: String, } impl Display for MarkDown { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(to_html(&self.value).as_str()) } } impl From<&Value> for MarkDown { fn from(value: &Value) -> Self { Self { value: value.as_str().expect("This will exist").to_owned(), } } } /// An UNIX time stamp. /// /// These should only ever be used for human-display, because timestamps are unreliably in a /// distributed system. /// Because of this reason, there is no `value()` function. #[derive(Debug, Default, Clone, Copy)] pub struct TimeStamp { value: u64, } impl From<&Value> for TimeStamp { fn from(value: &Value) -> Self { Self { value: value.as_u64().expect("This must exist"), } } } impl Display for TimeStamp { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let date = DateTime::from_timestamp(self.value as i64, 0).expect("This timestamp should be vaild"); let newdate = date.format("%Y-%m-%d %H:%M:%S"); f.write_str(newdate.to_string().as_str()) } } /// An UNIX time stamp. /// /// These should only ever be used for human-display, because timestamps are unreliably in a /// distributed system. /// /// This one allows underlying access to it's value and is only obtainable via `unsafe` code. /// The reason behind this is, that you might need to access this to improve the display for humans /// (i.e., sorting by date). #[derive(Debug, Default, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)] pub struct UnsafeTimeStamp { value: u64, } impl TimeStamp { /// # Safety /// This is not really unsafe, but there is just no way your can trust a time stamp in a /// distributed system. As such access to the raw value could lead to bugs. pub unsafe fn to_unsafe(self) -> UnsafeTimeStamp { UnsafeTimeStamp { value: self.value } } } #[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq)] /// A string that should be escaped when injected into html content. pub struct HtmlString { value: String, } impl From for HtmlString { fn from(value: MarkDown) -> Self { Self { value: value.value } } } impl From<&Value> for HtmlString { fn from(value: &Value) -> Self { Self { value: value.as_str().expect("This will exist").to_owned(), } } } impl Display for HtmlString { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(escape_html(&self.value).as_str()) } } // From `tera::escape_html` /// Escape HTML following [OWASP](https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet) /// /// Escape the following characters with HTML entity encoding to prevent switching /// into any execution context, such as script, style, or event handlers. Using /// hex entities is recommended in the spec. In addition to the 5 characters /// significant in XML (&, <, >, ", '), the forward slash is included as it helps /// to end an HTML entity. /// /// ```text /// & --> & /// < --> < /// > --> > /// " --> " /// ' --> ' ' is not recommended /// / --> / forward slash is included as it helps end an HTML entity /// ``` #[inline] pub fn escape_html(input: &str) -> String { let mut output = String::with_capacity(input.len() * 2); for c in input.chars() { match c { '&' => output.push_str("&"), '<' => output.push_str("<"), '>' => output.push_str(">"), '"' => output.push_str("""), '\'' => output.push_str("'"), '/' => output.push_str("/"), _ => output.push(c), } } // Not using shrink_to_fit() on purpose output }