diff --git a/Cargo.lock b/Cargo.lock index 885f2486..0f01f20b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -800,6 +800,7 @@ dependencies = [ "figment", "futures-util", "hmac", + "html-escape", "http", "hyper", "image", @@ -937,6 +938,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + [[package]] name = "http" version = "0.2.12" @@ -3045,6 +3055,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + [[package]] name = "uuid" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index 9b6e6f0a..46c5c8f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ semicolon_inside_block = "warn" str_to_string = "warn" string_add = "warn" string_lit_chars_any = "warn" +string_slice = "warn" string_to_string = "warn" suspicious_xor_used_as_pow = "warn" tests_outside_test_module = "warn" @@ -87,6 +88,7 @@ clap = { version = "4.3.0", default-features = false, features = ["std", "derive figment = { version = "0.10.8", features = ["env", "toml"] } futures-util = { version = "0.3.28", default-features = false } hmac = "0.12.1" +html-escape = "0.2.13" http = "0.2.9" hyper = "0.14.26" image = { version = "0.24.6", default-features = false, features = ["jpeg", "png", "gif"] } diff --git a/src/api/client_server/report.rs b/src/api/client_server/report.rs index 8e7dbed2..af07760d 100644 --- a/src/api/client_server/report.rs +++ b/src/api/client_server/report.rs @@ -1,4 +1,4 @@ -use crate::{services, utils::HtmlEscape, Error, Result, Ruma}; +use crate::{services, Error, Result, Ruma}; use ruma::{ api::client::{error::ErrorKind, room::report_content}, events::room::message, @@ -61,7 +61,7 @@ pub(crate) async fn report_event_route( pdu.room_id, pdu.sender, body.score, - HtmlEscape(body.reason.as_deref().unwrap_or("")) + html_escape::encode_safe(body.reason.as_deref().unwrap_or("")) ), )); diff --git a/src/api/server_server.rs b/src/api/server_server.rs index 6f46a232..eae0ed7a 100644 --- a/src/api/server_server.rs +++ b/src/api/server_server.rs @@ -107,7 +107,7 @@ impl FedDest { fn port(&self) -> Option { match &self { Self::Literal(addr) => Some(addr.port()), - Self::Named(_, port) => port[1..].parse().ok(), + Self::Named(_, port) => port.strip_prefix(':').and_then(|x| x.parse().ok()), } } } diff --git a/src/config/proxy.rs b/src/config/proxy.rs index bf725190..7672e02b 100644 --- a/src/config/proxy.rs +++ b/src/config/proxy.rs @@ -124,13 +124,10 @@ impl std::str::FromStr for WildCardedDomain { type Err = std::convert::Infallible; fn from_str(s: &str) -> Result { // maybe do some domain validation? - Ok(if s.starts_with("*.") { - WildCardedDomain::WildCarded(s[1..].to_owned()) - } else if s == "*" { - WildCardedDomain::WildCarded("".to_owned()) - } else { - WildCardedDomain::Exact(s.to_owned()) - }) + Ok(s.strip_prefix("*.") + .map(|x| WildCardedDomain::WildCarded(x.to_owned())) + .or_else(|| (s == "*").then(|| WildCardedDomain::WildCarded(String::new()))) + .unwrap_or_else(|| WildCardedDomain::Exact(s.to_owned()))) } } impl<'de> Deserialize<'de> for WildCardedDomain { diff --git a/src/service/admin.rs b/src/service/admin.rs index fd954bb3..cad796c0 100644 --- a/src/service/admin.rs +++ b/src/service/admin.rs @@ -33,9 +33,7 @@ use tracing::warn; use crate::{ api::client_server::{leave_all_rooms, AUTO_GEN_PASSWORD_LENGTH}, - services, - utils::{self, HtmlEscape}, - Error, PduEvent, Result, + services, utils, Error, PduEvent, Result, }; use super::pdu::PduBuilder; @@ -516,7 +514,7 @@ impl Service { } else { "PDU was accepted" }, - HtmlEscape(&json_text) + html_escape::encode_safe(&json_text) ), ) } @@ -897,29 +895,15 @@ impl Service { // Look for a `[commandbody]()` tag. If it exists, use all lines below it that // start with a `#` in the USAGE section. let mut text_lines: Vec<&str> = text.lines().collect(); - let mut command_body = String::new(); - - if let Some(line_index) = text_lines + let command_body = text_lines .iter() - .position(|line| *line == "[commandbody]()") - { - text_lines.remove(line_index); - - while text_lines - .get(line_index) - .map(|line| line.starts_with('#')) - .unwrap_or(false) - { - command_body += if text_lines[line_index].starts_with("# ") { - &text_lines[line_index][2..] - } else { - &text_lines[line_index][1..] - }; - command_body += "[nobr]\n"; - text_lines.remove(line_index); - } - } + .skip_while(|x| x != &&"[commandbody]()") + .skip(1) + .map_while(|&x| x.strip_prefix('#')) + .map(|x| x.strip_prefix(' ').unwrap_or(x)) + .collect::(); + text_lines.retain(|x| x != &"[commandbody]()"); let text = text_lines.join("\n"); // Improve the usage section diff --git a/src/utils.rs b/src/utils.rs index 4b2d7c3f..d3d0c0a8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,7 +6,7 @@ use rand::prelude::*; use ring::digest; use ruma::{canonical_json::try_from_json_map, CanonicalJsonError, CanonicalJsonObject}; use std::{ - cmp, fmt, + cmp, str::FromStr, time::{SystemTime, UNIX_EPOCH}, }; @@ -149,40 +149,3 @@ pub(crate) fn deserialize_from_str< } deserializer.deserialize_str(Visitor(std::marker::PhantomData)) } - -// Copied from librustdoc: -// https://github.com/rust-lang/rust/blob/cbaeec14f90b59a91a6b0f17fc046c66fa811892/src/librustdoc/html/escape.rs - -/// Wrapper struct which will emit the HTML-escaped version of the contained -/// string when passed to a format string. -pub(crate) struct HtmlEscape<'a>(pub(crate) &'a str); - -impl fmt::Display for HtmlEscape<'_> { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - // Because the internet is always right, turns out there's not that many - // characters to escape: http://stackoverflow.com/questions/7381974 - let HtmlEscape(s) = *self; - let pile_o_bits = s; - let mut last = 0; - for (i, ch) in s.char_indices() { - let s = match ch { - '>' => ">", - '<' => "<", - '&' => "&", - '\'' => "'", - '"' => """, - _ => continue, - }; - fmt.write_str(&pile_o_bits[last..i])?; - fmt.write_str(s)?; - // NOTE: we only expect single byte characters here - which is fine as long as we - // only match single byte characters - last = i + 1; - } - - if last < s.len() { - fmt.write_str(&pile_o_bits[last..])?; - } - Ok(()) - } -}