From f51f51ec723f680ac4be5068b47d727fa0f80818 Mon Sep 17 00:00:00 2001 From: gotjosh Date: Fri, 11 Nov 2022 15:14:35 +0000 Subject: [PATCH] Use the new truncation in bytes functions to ensure strings are not butchered Signed-off-by: gotjosh --- notify/util.go | 22 ++++++++++++++++++++-- notify/util_test.go | 18 +++++++++--------- notify/webex/webex.go | 2 +- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/notify/util.go b/notify/util.go index c127913e..6b71ac7b 100644 --- a/notify/util.go +++ b/notify/util.go @@ -20,6 +20,7 @@ import ( "io" "net/http" "net/url" + "strings" "github.com/go-kit/log" "github.com/go-kit/log/level" @@ -100,15 +101,32 @@ func TruncateInRunes(s string, n int) (string, bool) { // TruncateInBytes truncates a string to fit the given size in Bytes. func TruncateInBytes(s string, n int) (string, bool) { + // First, measure the string the w/o a to-rune conversion. if len(s) <= n { return s, false } + // The truncationMarker itself is 3 bytes, we can't return any part of the string when it's less than 3. if n <= 3 { - return string(s[:n]), true + switch n { + case 3: + return truncationMarker, true + default: + return strings.Repeat(".", n), true + } } - return string(s[:n-3]) + truncationMarker, true // In bytes, the truncation marker is 3 bytes. + // Now, to ensure we don't butcher the string we need to remove using runes. + r := []rune(s) + truncationTarget := n - 3 + + // Next, let's truncate the runes to the lower possible number. + truncatedRunes := r[:truncationTarget] + for len(string(truncatedRunes)) > truncationTarget { + truncatedRunes = r[:len(truncatedRunes)-1] + } + + return string(truncatedRunes) + truncationMarker, true } // TmplText is using monadic error handling in order to make string templating diff --git a/notify/util_test.go b/notify/util_test.go index 434e8c55..032fc515 100644 --- a/notify/util_test.go +++ b/notify/util_test.go @@ -49,7 +49,7 @@ func TestTruncate(t *testing.T) { in: "abcde", n: 2, runes: expect{out: "ab", trunc: true}, - bytes: expect{out: "ab", trunc: true}, + bytes: expect{out: "..", trunc: true}, }, { in: "abcde", @@ -73,25 +73,25 @@ func TestTruncate(t *testing.T) { in: "a⌘cde", n: 5, runes: expect{out: "a⌘cde", trunc: false}, - bytes: expect{out: "a\xe2…", trunc: true}, + bytes: expect{out: "a…", trunc: true}, }, { in: "a⌘cdef", n: 5, runes: expect{out: "a⌘cd…", trunc: true}, - bytes: expect{out: "a\xe2…", trunc: true}, + bytes: expect{out: "a…", trunc: true}, }, { in: "世界cdef", n: 3, runes: expect{out: "世界c", trunc: true}, - bytes: expect{out: "世", trunc: true}, + bytes: expect{out: "…", trunc: true}, }, { - in: "❤️✅🚀🔥❌", - n: 4, - runes: expect{out: "❤️✅…", trunc: true}, - bytes: expect{out: "\xe2…", trunc: true}, + in: "❤️✅🚀🔥❌❤️✅🚀🔥❌❤️✅🚀🔥❌❤️✅🚀🔥❌", + n: 19, + runes: expect{out: "❤️✅🚀🔥❌❤️✅🚀🔥❌❤️✅🚀🔥❌…", trunc: true}, + bytes: expect{out: "❤️✅🚀…", trunc: true}, }, } @@ -117,8 +117,8 @@ func TestTruncate(t *testing.T) { t.Run(fmt.Sprintf("%s(%s,%d)", fnName, tc.in, tc.n), func(t *testing.T) { s, trunc := fn(tc.in, tc.n) - require.Equal(t, truncated, trunc) require.Equal(t, out, s) + require.Equal(t, truncated, trunc) }) } } diff --git a/notify/webex/webex.go b/notify/webex/webex.go index 968fac4b..a19f6eac 100644 --- a/notify/webex/webex.go +++ b/notify/webex/webex.go @@ -94,7 +94,7 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) return false, err } - message, truncated := notify.Truncate(message, maxMessageSize) + message, truncated := notify.TruncateInBytes(message, maxMessageSize) if truncated { level.Debug(n.logger).Log("msg", "message truncated due to exceeding maximum allowed length by webex", "truncated_message", message) }