-
Notifications
You must be signed in to change notification settings - Fork 2.5k
telegram: exclude link targets from message length #5499
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,16 +22,19 @@ import ( | |
| "os" | ||
| "strconv" | ||
| "strings" | ||
| "unicode/utf8" | ||
|
|
||
| commoncfg "github.com/prometheus/common/config" | ||
| "golang.org/x/net/html" | ||
| "gopkg.in/telebot.v3" | ||
|
|
||
| "github.com/prometheus/alertmanager/notify" | ||
| "github.com/prometheus/alertmanager/template" | ||
| "github.com/prometheus/alertmanager/types" | ||
| ) | ||
|
|
||
| // Telegram supports 4096 chars max - from https://limits.tginfo.me/en. | ||
| // Telegram supports up to 4096 characters after entity parsing. | ||
| // See https://core.telegram.org/bots/api#sendmessage. | ||
| const maxMessageLenRunes = 4096 | ||
|
|
||
| // Notifier implements a Notifier for telegram notifications. | ||
|
|
@@ -92,7 +95,7 @@ func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, err | |
| if err != nil { | ||
| return false, err | ||
| } | ||
| if len([]rune(messageText)) > maxMessageLenRunes { | ||
| if htmlTextRuneCount(messageText) > maxMessageLenRunes { | ||
| messageText = `Alertmanager notification could not be sent: message length exceeds Telegram limits. | ||
| Please check the template used for producing the message content.` | ||
| } | ||
|
|
@@ -147,6 +150,26 @@ func wrapWithFailureReason(err error) error { | |
| return err | ||
| } | ||
|
|
||
| // htmlTextRuneCount excludes HTML markup and attribute values such as link targets. | ||
| func htmlTextRuneCount(message string) int { | ||
| tokenizer := html.NewTokenizer(strings.NewReader(message)) | ||
| count := 0 | ||
|
|
||
| for { | ||
| switch tokenizer.Next() { | ||
| case html.ErrorToken: | ||
| if len(tokenizer.Raw()) > 0 { | ||
| return utf8.RuneCountInString(message) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
| return count | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like the wrong with to return here?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| case html.CommentToken, html.DoctypeToken: | ||
| return utf8.RuneCountInString(message) | ||
| case html.TextToken: | ||
| count += utf8.RuneCount(tokenizer.Text()) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func createTelegramClient(apiURL, parseMode string, httpClient *http.Client) (*telebot.Bot, error) { | ||
| bot, err := telebot.NewBot(telebot.Settings{ | ||
| URL: apiURL, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -83,6 +83,13 @@ func TestTelegramRetry(t *testing.T) { | |
|
|
||
| func TestTelegramNotify(t *testing.T) { | ||
| token := "secret" | ||
| longHTMLLink := `<a href="https://example.com/` + strings.Repeat("x", maxMessageLenRunes) + `">Open</a>` | ||
| longMalformedHTML := `<a href="` + strings.Repeat("x", maxMessageLenRunes) | ||
| longMalformedHTMLTemplate := fmt.Sprintf(`{{ %q | safeHtml }}`, longMalformedHTML) | ||
| longMalformedComment := `<!--` + strings.Repeat("x", maxMessageLenRunes) | ||
| longMalformedCommentTemplate := fmt.Sprintf(`{{ %q | safeHtml }}`, longMalformedComment) | ||
| longMalformedDoctype := `<!DOCTYPE ` + strings.Repeat("x", maxMessageLenRunes) | ||
| longMalformedDoctypeTemplate := fmt.Sprintf(`{{ %q | safeHtml }}`, longMalformedDoctype) | ||
|
|
||
| fileWithToken, err := os.CreateTemp(t.TempDir(), "telegram-bot-token") | ||
| require.NoError(t, err, "creating temp file failed") | ||
|
|
@@ -133,6 +140,49 @@ func TestTelegramNotify(t *testing.T) { | |
| expText: `Alertmanager notification could not be sent: message length exceeds Telegram limits. | ||
| Please check the template used for producing the message content.`, | ||
| }, | ||
| { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add a test case for broken html too?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added in fc7887d. The regression renders a malformed anchor at the beginning through |
||
| name: "HTML mode ignores link targets when enforcing length limit", | ||
| cfg: TelegramConfig{ | ||
| ParseMode: "HTML", | ||
| Message: longHTMLLink, | ||
| HTTPConfig: &commoncfg.HTTPClientConfig{}, | ||
| BotToken: commoncfg.Secret(token), | ||
| }, | ||
| expText: longHTMLLink, | ||
| }, | ||
| { | ||
| name: "HTML mode falls back for too-large malformed tag", | ||
| cfg: TelegramConfig{ | ||
| ParseMode: "HTML", | ||
| Message: longMalformedHTMLTemplate, | ||
| HTTPConfig: &commoncfg.HTTPClientConfig{}, | ||
| BotToken: commoncfg.Secret(token), | ||
| }, | ||
| expText: `Alertmanager notification could not be sent: message length exceeds Telegram limits. | ||
| Please check the template used for producing the message content.`, | ||
| }, | ||
| { | ||
| name: "HTML mode falls back for too-large malformed comment", | ||
| cfg: TelegramConfig{ | ||
| ParseMode: "HTML", | ||
| Message: longMalformedCommentTemplate, | ||
| HTTPConfig: &commoncfg.HTTPClientConfig{}, | ||
| BotToken: commoncfg.Secret(token), | ||
| }, | ||
| expText: `Alertmanager notification could not be sent: message length exceeds Telegram limits. | ||
| Please check the template used for producing the message content.`, | ||
| }, | ||
| { | ||
| name: "HTML mode falls back for too-large malformed doctype", | ||
| cfg: TelegramConfig{ | ||
| ParseMode: "HTML", | ||
| Message: longMalformedDoctypeTemplate, | ||
| HTTPConfig: &commoncfg.HTTPClientConfig{}, | ||
| BotToken: commoncfg.Secret(token), | ||
| }, | ||
| expText: `Alertmanager notification could not be sent: message length exceeds Telegram limits. | ||
| Please check the template used for producing the message content.`, | ||
| }, | ||
| { | ||
| name: "Default mode with too-large message", | ||
| cfg: TelegramConfig{ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you find the corresponding source code? I don't feel comfortable just trusting the documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Verified against source. The Bot API calls
parseTextEntitiesin Client.cpp. TDLib HTML parsing storeshrefseparately as entity data and removes markup from resulting text in MessageEntity.cpp. The 4096 limit is then checked against parsed text in MessageContent.cpp. Thus the embedded link target is excluded from that limit. There is also a separate 32768-byte raw-input guard before parsing; this regression case remains below it.