use std::fmt; use std::io::{Error, Result as IOResult, Write}; use jetscii::{bytes, BytesConst}; use crate::elements::{Element, Table, TableCell, TableRow, Timestamp}; use crate::export::write_datetime; /// A wrapper for escaping sensitive characters in html. /// /// ```rust /// use orgize::export::HtmlEscape as Escape; /// /// assert_eq!(format!("{}", Escape("< < <")), "< < <"); /// assert_eq!( /// format!("{}", Escape("")), /// "<script>alert('Hello XSS')</script>" /// ); /// ``` pub struct HtmlEscape>(pub S); impl> fmt::Display for HtmlEscape { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut pos = 0; let content = self.0.as_ref(); let bytes = content.as_bytes(); lazy_static::lazy_static! { static ref ESCAPE_BYTES: BytesConst = bytes!(b'<', b'>', b'&', b'\'', b'"'); } while let Some(off) = ESCAPE_BYTES.find(&bytes[pos..]) { write!(f, "{}", &content[pos..pos + off])?; pos += off + 1; match bytes[pos - 1] { b'<' => write!(f, "<")?, b'>' => write!(f, ">")?, b'&' => write!(f, "&")?, b'\'' => write!(f, "'")?, b'"' => write!(f, """)?, _ => unreachable!(), } } write!(f, "{}", &content[pos..]) } } pub trait HtmlHandler>: Default { fn start(&mut self, w: W, element: &Element) -> Result<(), E>; fn end(&mut self, w: W, element: &Element) -> Result<(), E>; } /// Default Html Handler #[derive(Default)] pub struct DefaultHtmlHandler; impl HtmlHandler for DefaultHtmlHandler { fn start(&mut self, mut w: W, element: &Element) -> IOResult<()> { match element { // container elements Element::SpecialBlock(_) => (), Element::QuoteBlock(_) => write!(w, "
")?, Element::CenterBlock(_) => write!(w, "
")?, Element::VerseBlock(_) => write!(w, "

")?, Element::Bold => write!(w, "")?, Element::Document { .. } => write!(w, "

")?, Element::DynBlock(_dyn_block) => (), Element::Headline { .. } => (), Element::List(list) => { if list.ordered { write!(w, "
    ")?; } else { write!(w, "
      ")?; } } Element::Italic => write!(w, "")?, Element::ListItem(_) => write!(w, "
    • ")?, Element::Paragraph { .. } => write!(w, "

      ")?, Element::Section => write!(w, "

      ")?, Element::Strike => write!(w, "")?, Element::Underline => write!(w, "")?, // non-container elements Element::CommentBlock(_) => (), Element::ExampleBlock(block) => write!( w, "
      {}
      ", HtmlEscape(&block.contents) )?, Element::ExportBlock(block) => { if block.data.eq_ignore_ascii_case("HTML") { write!(w, "{}", block.contents)? } } Element::SourceBlock(block) => { if block.language.is_empty() { write!( w, "
      {}
      ", HtmlEscape(&block.contents) )?; } else { write!( w, "
      {}
      ", block.language, HtmlEscape(&block.contents) )?; } } Element::BabelCall(_) => (), Element::InlineSrc(inline_src) => write!( w, "{}", inline_src.lang, HtmlEscape(&inline_src.body) )?, Element::Code { value } => write!(w, "{}", HtmlEscape(value))?, Element::FnRef(_fn_ref) => (), Element::InlineCall(_) => (), Element::Link(link) => write!( w, "{}", HtmlEscape(&link.path), HtmlEscape(link.desc.as_ref().unwrap_or(&link.path)), )?, Element::Macros(_macros) => (), Element::RadioTarget => (), Element::Snippet(snippet) => { if snippet.name.eq_ignore_ascii_case("HTML") { write!(w, "{}", snippet.value)?; } } Element::Target(_target) => (), Element::Text { value } => write!(w, "{}", HtmlEscape(value))?, Element::Timestamp(timestamp) => { write!( &mut w, "" )?; match timestamp { Timestamp::Active { start, .. } => { write_datetime(&mut w, "<", start, ">")?; } Timestamp::Inactive { start, .. } => { write_datetime(&mut w, "[", start, "]")?; } Timestamp::ActiveRange { start, end, .. } => { write_datetime(&mut w, "<", start, ">–")?; write_datetime(&mut w, "<", end, ">")?; } Timestamp::InactiveRange { start, end, .. } => { write_datetime(&mut w, "[", start, "]–")?; write_datetime(&mut w, "[", end, "]")?; } Timestamp::Diary { value } => { write!(&mut w, "<%%({})>", HtmlEscape(value))? } } write!(&mut w, "")?; } Element::Verbatim { value } => write!(&mut w, "{}", HtmlEscape(value))?, Element::FnDef(_fn_def) => (), Element::Clock(_clock) => (), Element::Comment(_) => (), Element::FixedWidth(fixed_width) => write!( w, "
      {}
      ", HtmlEscape(&fixed_width.value) )?, Element::Keyword(_keyword) => (), Element::Drawer(_drawer) => (), Element::Rule(_) => write!(w, "
      ")?, Element::Cookie(cookie) => write!(w, "{}", cookie.value)?, Element::Title(title) => { write!(w, "", if title.level <= 6 { title.level } else { 6 })?; } Element::Table(Table::TableEl { .. }) => (), Element::Table(Table::Org { has_header, .. }) => { write!(w, "")?; if *has_header { write!(w, "")?; } else { write!(w, "")?; } } Element::TableRow(row) => match row { TableRow::Body => write!(w, "")?, TableRow::BodyRule => write!(w, "")?, TableRow::Header => write!(w, "")?, TableRow::HeaderRule => write!(w, "")?, }, Element::TableCell(cell) => match cell { TableCell::Body => write!(w, "
      ")?, TableCell::Header => write!(w, "")?, }, } Ok(()) } fn end(&mut self, mut w: W, element: &Element) -> IOResult<()> { match element { // container elements Element::SpecialBlock(_) => (), Element::QuoteBlock(_) => write!(w, "")?, Element::CenterBlock(_) => write!(w, "")?, Element::VerseBlock(_) => write!(w, "

      ")?, Element::Bold => write!(w, "")?, Element::Document { .. } => write!(w, "")?, Element::DynBlock(_dyn_block) => (), Element::Headline { .. } => (), Element::List(list) => { if list.ordered { write!(w, "")?; } else { write!(w, "")?; } } Element::Italic => write!(w, "")?, Element::ListItem(_) => write!(w, "")?, Element::Paragraph { .. } => write!(w, "

      ")?, Element::Section => write!(w, "")?, Element::Strike => write!(w, "")?, Element::Underline => write!(w, "")?, Element::Title(title) => { write!(w, "", if title.level <= 6 { title.level } else { 6 })? } Element::Table(Table::TableEl { .. }) => (), Element::Table(Table::Org { .. }) => { write!(w, "
      ")?; } Element::TableRow(TableRow::Body) | Element::TableRow(TableRow::Header) => { write!(w, "")?; } Element::TableCell(cell) => match cell { TableCell::Body => write!(w, "")?, TableCell::Header => write!(w, "")?, }, // non-container elements _ => debug_assert!(!element.is_container()), } Ok(()) } } #[cfg(feature = "syntect")] mod syntect_handler { use super::*; use std::marker::PhantomData; use syntect::{ easy::HighlightLines, highlighting::ThemeSet, html::{styled_line_to_highlighted_html, IncludeBackground}, parsing::SyntaxSet, }; /// Syntect Html Handler /// /// Simple Usage: /// /// ```rust /// use orgize::Org; /// use orgize::export::{DefaultHtmlHandler, SyntectHtmlHandler}; /// /// let mut handler = SyntectHtmlHandler::new(DefaultHtmlHandler); /// let org = Org::parse("src_rust{println!(\"Hello\")}"); /// /// let mut vec = vec![]; /// /// org.write_html_custom(&mut vec, &mut handler).unwrap(); /// ``` /// /// Customize: /// /// ```rust,no_run /// // orgize has re-exported the whole syntect crate /// use orgize::syntect::parsing::SyntaxSet; /// use orgize::export::{DefaultHtmlHandler, SyntectHtmlHandler}; /// /// let mut handler = SyntectHtmlHandler { /// syntax_set: { /// let set = SyntaxSet::load_defaults_newlines(); /// let mut builder = set.into_builder(); /// // add extra language syntax /// builder.add_from_folder("path/to/syntax/dir", true).unwrap(); /// builder.build() /// }, /// // specify theme /// theme: String::from("Solarized (dark)"), /// inner: DefaultHtmlHandler, /// ..Default::default() /// }; /// /// // Make sure to check if theme presents or it will panic at runtime /// if handler.theme_set.themes.contains_key("dont-exists") { /// /// } /// ``` pub struct SyntectHtmlHandler, H: HtmlHandler> { /// syntax set, default is `SyntaxSet::load_defaults_newlines()` pub syntax_set: SyntaxSet, /// theme set, default is `ThemeSet::load_defaults()` pub theme_set: ThemeSet, /// theme used for highlighting, default is `"InspiredGitHub"` pub theme: String, /// inner html handler pub inner: H, /// background color, default is `IncludeBackground::No` pub background: IncludeBackground, /// handler error type pub error_type: PhantomData, } impl, H: HtmlHandler> SyntectHtmlHandler { pub fn new(inner: H) -> Self { SyntectHtmlHandler { inner, ..Default::default() } } fn highlight(&self, language: Option<&str>, content: &str) -> String { let mut highlighter = HighlightLines::new( language .and_then(|lang| self.syntax_set.find_syntax_by_token(lang)) .unwrap_or_else(|| self.syntax_set.find_syntax_plain_text()), &self.theme_set.themes[&self.theme], ); let regions = highlighter.highlight(content, &self.syntax_set); styled_line_to_highlighted_html(®ions[..], self.background) } } impl, H: HtmlHandler> Default for SyntectHtmlHandler { fn default() -> Self { SyntectHtmlHandler { syntax_set: SyntaxSet::load_defaults_newlines(), theme_set: ThemeSet::load_defaults(), theme: String::from("InspiredGitHub"), inner: H::default(), background: IncludeBackground::No, error_type: PhantomData, } } } impl, H: HtmlHandler> HtmlHandler for SyntectHtmlHandler { fn start(&mut self, mut w: W, element: &Element) -> Result<(), E> { match element { Element::InlineSrc(inline_src) => write!( w, "{}", self.highlight(Some(&inline_src.lang), &inline_src.body) )?, Element::SourceBlock(block) => { if block.language.is_empty() { write!(w, "
      {}
      ", block.contents)?; } else { write!( w, "
      {}
      ", block.language, self.highlight(Some(&block.language), &block.contents) )?; } } Element::FixedWidth(fixed_width) => write!( w, "
      {}
      ", self.highlight(None, &fixed_width.value) )?, Element::ExampleBlock(block) => write!( w, "
      {}
      ", self.highlight(None, &block.contents) )?, _ => self.inner.start(w, element)?, } Ok(()) } fn end(&mut self, w: W, element: &Element) -> Result<(), E> { self.inner.end(w, element) } } } #[cfg(feature = "syntect")] pub use syntect_handler::SyntectHtmlHandler;