caption: escape URLs/handles in HTML captions

Post URLs and author URLs were interpolated raw into <a href> attributes
(and the raw user URL from empty_fetched into caption text), so crafted
links could break the HTML parse and fail the send with a 400. All
attribute interpolations now use encode_double_quoted_attribute; text
stays encode_text.
This commit is contained in:
2026-08-08 20:21:56 +08:00
parent 734cfc2eb3
commit 72130b9023
4 changed files with 13 additions and 11 deletions
+3 -3
View File
@@ -1,7 +1,7 @@
use super::model; use super::model;
use crate::media::Media; use crate::media::Media;
use crate::site::{FetchError, Fetched}; use crate::site::{FetchError, Fetched};
use html_escape::encode_text; use html_escape::{encode_double_quoted_attribute, encode_text};
use regex::Regex; use regex::Regex;
use std::sync::LazyLock; use std::sync::LazyLock;
@@ -224,8 +224,8 @@ impl Post {
pub fn caption(&self) -> String { pub fn caption(&self) -> String {
format!( format!(
"{url}\n<a href=\"{author_url}\">{author}</a>: {text}", "{url}\n<a href=\"{author_url}\">{author}</a>: {text}",
url = self.url(), url = encode_double_quoted_attribute(&self.url()),
author_url = self.author_url(), author_url = encode_double_quoted_attribute(&self.author_url()),
author = encode_text(&self.author), author = encode_text(&self.author),
text = encode_text(&self.text), text = encode_text(&self.text),
) )
+3 -3
View File
@@ -1,7 +1,7 @@
use super::model::{IllustrationModel, TypeModel}; use super::model::{IllustrationModel, TypeModel};
use crate::media::Media; use crate::media::Media;
use crate::site::{FetchError, Fetched}; use crate::site::{FetchError, Fetched};
use html_escape::encode_text; use html_escape::{encode_double_quoted_attribute, encode_text};
use regex::Regex; use regex::Regex;
use std::sync::LazyLock; use std::sync::LazyLock;
@@ -48,9 +48,9 @@ impl Illustration {
pub fn caption(&self) -> String { pub fn caption(&self) -> String {
format!( format!(
"<a href=\"{url}\">{title}</a> / <a href=\"{author_url}\">{author}</a>\n{tags}", "<a href=\"{url}\">{title}</a> / <a href=\"{author_url}\">{author}</a>\n{tags}",
url = self.url(), url = encode_double_quoted_attribute(&self.url()),
title = encode_text(&self.title), title = encode_text(&self.title),
author_url = self.author_url(), author_url = encode_double_quoted_attribute(&self.author_url()),
author = encode_text(&self.author), author = encode_text(&self.author),
tags = encode_text( tags = encode_text(
&self &self
+6 -4
View File
@@ -1,7 +1,7 @@
use super::model; use super::model;
use crate::media::Media; use crate::media::Media;
use crate::site::{FetchError, Fetched}; use crate::site::{FetchError, Fetched};
use html_escape::encode_text; use html_escape::{encode_double_quoted_attribute, encode_text};
use regex::Regex; use regex::Regex;
use std::sync::LazyLock; use std::sync::LazyLock;
@@ -48,7 +48,9 @@ pub async fn fetch_from_url(url: &str) -> Result<Fetched, FetchError> {
fn empty_fetched(url: &str) -> Fetched { fn empty_fetched(url: &str) -> Fetched {
Fetched { Fetched {
source_url: url.to_string(), source_url: url.to_string(),
caption: url.to_string(), // The raw user-supplied URL goes into an HTML caption; escape it so
// crafted links cannot break the parse (Telegram 400).
caption: encode_text(url).into_owned(),
title: String::new(), title: String::new(),
media: vec![], media: vec![],
sensitive: true, sensitive: true,
@@ -151,8 +153,8 @@ impl Tweet {
pub fn caption(&self) -> String { pub fn caption(&self) -> String {
format!( format!(
"{url}\n<a href=\"{author_url}\">{author}</a>: {text}", "{url}\n<a href=\"{author_url}\">{author}</a>: {text}",
url = self.url(), url = encode_double_quoted_attribute(&self.url()),
author_url = self.author_url(), author_url = encode_double_quoted_attribute(&self.author_url()),
author = encode_text(&self.author), author = encode_text(&self.author),
text = encode_text(&self.text), text = encode_text(&self.text),
) )
+1 -1
View File
@@ -162,7 +162,7 @@ async fn edit_message_handler(bot: &Bot, message: &Message) -> bool {
}; };
let link = format!( let link = format!(
"<a href=\"{0}\">{1}</a>", "<a href=\"{0}\">{1}</a>",
edit.url, html_escape::encode_double_quoted_attribute(&edit.url),
html_escape::encode_text(text) html_escape::encode_text(text)
); );
let new_text = if edit.template.is_empty() { let new_text = if edit.template.is_empty() {