Skip to content

Commit

Permalink
Merge pull request project-robius#291 from kevinaboos/html_encode_lin…
Browse files Browse the repository at this point in the history
…kified_text_body

HTML-escape the linkified text parts of links
  • Loading branch information
kevinaboos authored Dec 17, 2024
2 parents 2cb60b6 + 2c4771b commit 4f2712d
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 22 deletions.
4 changes: 2 additions & 2 deletions src/event_preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ pub fn text_preview_of_message(
MessageType::Text(text) => {
text.formatted.as_ref()
.and_then(|fb| (fb.format == MessageFormat::Html)
.then(|| utils::linkify(&fb.body).to_string())
.then(|| utils::linkify(&fb.body, true).to_string())
)
.unwrap_or_else(|| utils::linkify(&text.body).to_string())
.unwrap_or_else(|| utils::linkify(&text.body, false).to_string())
}
MessageType::VerificationRequest(verification) => format!(
"[Verification Request] <i>to user {}</i>",
Expand Down
4 changes: 2 additions & 2 deletions src/home/room_screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2926,9 +2926,9 @@ fn populate_text_message_content(
if let Some(formatted_body) = formatted_body.as_ref()
.and_then(|fb| (fb.format == MessageFormat::Html).then(|| fb.body.clone()))
{
message_content_widget.show_html(utils::linkify(formatted_body.as_ref()));
message_content_widget.show_html(utils::linkify(formatted_body.as_ref(), true));
} else {
match utils::linkify(body) {
match utils::linkify(body, false) {
Cow::Owned(linkified_html) => message_content_widget.show_html(&linkified_html),
Cow::Borrowed(plaintext) => message_content_widget.show_plaintext(plaintext),
}
Expand Down
47 changes: 29 additions & 18 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ pub const MEDIA_THUMBNAIL_FORMAT: MediaFormatConst = MediaFormatConst::Thumbnail


/// Looks for bare links in the given `text` and converts them into proper HTML links.
pub fn linkify(text: &str) -> Cow<'_, str> {
pub fn linkify(text: &str, is_html: bool) -> Cow<'_, str> {
use linkify::{LinkFinder, LinkKind};
let mut links = LinkFinder::new()
.links(text)
Expand All @@ -200,6 +200,15 @@ pub fn linkify(text: &str) -> Cow<'_, str> {
return Cow::Borrowed(text);
}

// A closure to escape text if it's not HTML.
let escaped = |text| {
if is_html {
Cow::from(text)
} else {
htmlize::escape_text(text)
}
};

let mut linkified_text = String::new();
let mut last_end_index = 0;
for link in links {
Expand All @@ -220,14 +229,14 @@ pub fn linkify(text: &str) -> Cow<'_, str> {
LinkKind::Url => {
linkified_text = format!(
"{linkified_text}{}<a href=\"{link_txt}\">{}</a>",
text.get(last_end_index..link.start()).unwrap_or_default(),
escaped(text.get(last_end_index..link.start()).unwrap_or_default()),
htmlize::escape_attribute(link_txt),
);
}
LinkKind::Email => {
linkified_text = format!(
"{linkified_text}{}<a href=\"mailto:{link_txt}\">{}</a>",
text.get(last_end_index..link.start()).unwrap_or_default(),
escaped(text.get(last_end_index..link.start()).unwrap_or_default()),
htmlize::escape_attribute(link_txt),
);
}
Expand All @@ -236,7 +245,9 @@ pub fn linkify(text: &str) -> Cow<'_, str> {
}
last_end_index = link.end();
}
linkified_text.push_str(text.get(last_end_index..).unwrap_or_default());
linkified_text.push_str(
&escaped(text.get(last_end_index..).unwrap_or_default())
);
Cow::Owned(linkified_text)
}

Expand Down Expand Up @@ -287,14 +298,14 @@ mod tests_linkify {
#[test]
fn test_linkify0() {
let text = "Hello, world!";
assert_eq!(linkify(text).as_ref(), text);
assert_eq!(linkify(text, false).as_ref(), text);
}

#[test]
fn test_linkify1() {
let text = "Check out this website: https://example.com";
let expected = "Check out this website: <a href=\"https://example.com\">https://example.com</a>";
let actual = linkify(text);
let actual = linkify(text, false);
println!("{:?}", actual.as_ref());
assert_eq!(actual.as_ref(), expected);
}
Expand All @@ -303,22 +314,22 @@ mod tests_linkify {
fn test_linkify2() {
let text = "Send an email to [email protected]";
let expected = "Send an email to <a href=\"mailto:[email protected]\">[email protected]</a>";
let actual = linkify(text);
let actual = linkify(text, false);
println!("{:?}", actual.as_ref());
assert_eq!(actual.as_ref(), expected);
}

#[test]
fn test_linkify3() {
let text = "Visit our website at www.example.com";
assert_eq!(linkify(text).as_ref(), text);
assert_eq!(linkify(text, false).as_ref(), text);
}

#[test]
fn test_linkify4() {
let text = "Link 1 http://google.com Link 2 https://example.com";
let expected = "Link 1 <a href=\"http://google.com\">http://google.com</a> Link 2 <a href=\"https://example.com\">https://example.com</a>";
let actual = linkify(text);
let actual = linkify(text, false);
println!("{:?}", actual.as_ref());
assert_eq!(actual.as_ref(), expected);
}
Expand All @@ -328,66 +339,66 @@ mod tests_linkify {
fn test_linkify5() {
let text = "html test <a href=http://google.com>Link title</a> Link 2 https://example.com";
let expected = "html test <a href=http://google.com>Link title</a> Link 2 <a href=\"https://example.com\">https://example.com</a>";
let actual = linkify(text);
let actual = linkify(text, true);
println!("{:?}", actual.as_ref());
assert_eq!(actual.as_ref(), expected);
}

#[test]
fn test_linkify6() {
let text = "<a href=http://google.com>link title</a>";
assert_eq!(linkify(text).as_ref(), text);
assert_eq!(linkify(text, true).as_ref(), text);
}

#[test]
fn test_linkify7() {
let text = "https://example.com";
let expected = "<a href=\"https://example.com\">https://example.com</a>";
assert_eq!(linkify(text).as_ref(), expected);
assert_eq!(linkify(text, false).as_ref(), expected);
}

#[test]
fn test_linkify8() {
let text = "test test https://crates.io/crates/cargo-packager test test";
let expected = "test test <a href=\"https://crates.io/crates/cargo-packager\">https://crates.io/crates/cargo-packager</a> test test";
assert_eq!(linkify(text).as_ref(), expected);
assert_eq!(linkify(text, false).as_ref(), expected);
}

#[test]
fn test_linkify9() {
let text = "<mx-reply><blockquote><a href=\"https://matrix.to/#/!ifW4td0it0scmZpEM6:computer.surgery/$GwDzIlPzNgxhJ2QCIsmcPMC-sHdoKNsb0g2MS1psyyM?via=matrix.org&via=mozilla.org&via=gitter.im\">In reply to</a> <a href=\"https://matrix.to/#/@spore:mozilla.org\">@spore:mozilla.org</a><br />So I asked if there's a crate for it (bc I don't have the time to test and debug it) or if there's simply a better way that involves less states and invariants</blockquote></mx-reply>https://docs.rs/aho-corasick/latest/aho_corasick/struct.AhoCorasick.html#method.stream_find_iter";

let expected = "<mx-reply><blockquote><a href=\"https://matrix.to/#/!ifW4td0it0scmZpEM6:computer.surgery/$GwDzIlPzNgxhJ2QCIsmcPMC-sHdoKNsb0g2MS1psyyM?via=matrix.org&via=mozilla.org&via=gitter.im\">In reply to</a> <a href=\"https://matrix.to/#/@spore:mozilla.org\">@spore:mozilla.org</a><br />So I asked if there's a crate for it (bc I don't have the time to test and debug it) or if there's simply a better way that involves less states and invariants</blockquote></mx-reply><a href=\"https://docs.rs/aho-corasick/latest/aho_corasick/struct.AhoCorasick.html#method.stream_find_iter\">https://docs.rs/aho-corasick/latest/aho_corasick/struct.AhoCorasick.html#method.stream_find_iter</a>";
assert_eq!(linkify(text).as_ref(), expected);
assert_eq!(linkify(text, true).as_ref(), expected);
}

#[test]
fn test_linkify10() {
let text = "And then call <a href=\"https://doc.rust-lang.org/std/io/trait.BufRead.html#method.read_until\"><code>read_until</code></a> or other <code>BufRead</code> methods.";
let expected = "And then call <a href=\"https://doc.rust-lang.org/std/io/trait.BufRead.html#method.read_until\"><code>read_until</code></a> or other <code>BufRead</code> methods.";
assert_eq!(linkify(text).as_ref(), expected);
assert_eq!(linkify(text, true).as_ref(), expected);
}


#[test]
fn test_linkify11() {
let text = "And then https://google.com call <a href=\"https://doc.rust-lang.org/std/io/trait.BufRead.html#method.read_until\"><code>read_until</code></a> or other <code>BufRead</code> methods.";
let expected = "And then <a href=\"https://google.com\">https://google.com</a> call <a href=\"https://doc.rust-lang.org/std/io/trait.BufRead.html#method.read_until\"><code>read_until</code></a> or other <code>BufRead</code> methods.";
assert_eq!(linkify(text).as_ref(), expected);
assert_eq!(linkify(text, true).as_ref(), expected);
}

#[test]
fn test_linkify12() {
let text = "And then https://google.com call <a href=\"https://doc.rust-lang.org/std/io/trait.BufRead.html#method.read_until\"><code>read_until</code></a> or other <code>BufRead http://another-link.http.com </code> methods.";
let expected = "And then <a href=\"https://google.com\">https://google.com</a> call <a href=\"https://doc.rust-lang.org/std/io/trait.BufRead.html#method.read_until\"><code>read_until</code></a> or other <code>BufRead <a href=\"http://another-link.http.com\">http://another-link.http.com</a> </code> methods.";
assert_eq!(linkify(text).as_ref(), expected);
assert_eq!(linkify(text, true).as_ref(), expected);
}

#[test]
fn test_linkify13() {
let text = "Check out this website: <a href=\"https://example.com\">https://example.com</a>";
let expected = "Check out this website: <a href=\"https://example.com\">https://example.com</a>";
assert_eq!(linkify(text).as_ref(), expected);
assert_eq!(linkify(text, true).as_ref(), expected);
}
}

Expand Down

0 comments on commit 4f2712d

Please sign in to comment.