From 3cf33c9922d0e54dc631c1576ccbef504f023252 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 23 Jan 2023 07:58:41 -0500 Subject: [PATCH] add support for C hex escapes --- ircdog.go | 1 + lib/strings_test.go | 38 ++++++++++++++++++++++++++ lib/utils.go | 66 ++++++++++++++++++++++++++++++++++----------- 3 files changed, 90 insertions(+), 15 deletions(-) create mode 100644 lib/strings_test.go diff --git a/ircdog.go b/ircdog.go index f4f7be6..cd368ca 100644 --- a/ircdog.go +++ b/ircdog.go @@ -81,6 +81,7 @@ Sending Escapes: Strikethrough | [[S]] | 0x1e Underscore | [[U]] | 0x1f Reset | [[R]] | 0x0f + C hex escape | [[\x??]] | 0x?? --------------------------------- These escapes are only enabled in standard mode (not listening mode), diff --git a/lib/strings_test.go b/lib/strings_test.go new file mode 100644 index 0000000..f8319aa --- /dev/null +++ b/lib/strings_test.go @@ -0,0 +1,38 @@ +// Copyright (c) 2023 Shivaram Lingamneni +// released under the ISC license + +package lib + +import ( + "testing" +) + +var replacementTestCases = []struct { + input string + output string +}{ + {"", ""}, + {`a`, "a"}, + {`[`, "["}, + {`[[`, "[["}, + {`[[a]]`, "[[a]]"}, + {`a[[CTCP]]b`, "a\x01b"}, + {`[[B]]b[[B]]`, "\x02b\x02"}, + {`a[[\x67]]b`, "a\x67b"}, + {`[[\x67]]`, "\x67"}, + {`www[[\x00]]`, "www\x00"}, + {`www[[\x0D]]`, "www\x0d"}, + {`www[[\xff]]`, "www\xff"}, + {`www[[\xFF]]`, "www\xff"}, + {`[[notanescape]]`, "[[notanescape]]"}, + {`[[[U]]]`, "[\x1f]"}, +} + +func TestReplaceControlCodes(t *testing.T) { + for _, testCase := range replacementTestCases { + actual := ReplaceControlCodes(testCase.input) + if actual != testCase.output { + t.Errorf("expected `%s` -> %#v, got %#v", testCase.input, []byte(testCase.output), []byte(actual)) + } + } +} diff --git a/lib/utils.go b/lib/utils.go index 6ab74b9..4e21e85 100644 --- a/lib/utils.go +++ b/lib/utils.go @@ -3,18 +3,11 @@ package lib -import "strings" - -var controlCodeReplacements = map[string]string{ - "[[CTCP]]": "\x01", - "[[B]]": "\x02", - "[[C]]": "\x03", - "[[M]]": "\x11", - "[[I]]": "\x1d", - "[[S]]": "\x1e", - "[[U]]": "\x1f", - "[[R]]": "\x0f", -} +import ( + "regexp" + "strconv" + "strings" +) // SplitLineIntoParts splits the given IRC line into separate parts. func SplitLineIntoParts(line string) []string { @@ -64,11 +57,54 @@ func SplitLineIntoParts(line string) []string { return lineParts } +var ( + // e.g., [[\x00]] for \x00, [[\xFF]] or [[\xff]] for \xff + hexEscapeRegex = regexp.MustCompile(`^\[\[\\x[0-9a-fA-F]{2}\]\]`) +) + +var controlCodeReplacements = []struct { + escape string + value byte +}{ + {"[[CTCP]]", '\x01'}, + {"[[B]]", '\x02'}, + {"[[C]]", '\x03'}, + {"[[M]]", '\x11'}, + {"[[I]]", '\x1d'}, + {"[[S]]", '\x1e'}, + {"[[U]]", '\x1f'}, + {"[[R]]", '\x0f'}, +} + // ReplaceControlCodes applies our control code replacements to the line. func ReplaceControlCodes(line string) string { - for k, v := range controlCodeReplacements { - line = strings.Replace(line, k, v, -1) + if idx := strings.Index(line, "[["); idx == -1 { + return line + } + + var buf strings.Builder + +LineLoop: + for line != "" { + if line[0] == '[' { + for _, replacement := range controlCodeReplacements { + if strings.HasPrefix(line, replacement.escape) { + buf.WriteByte(replacement.value) + line = line[len(replacement.escape):] + continue LineLoop + } + } + if hexEscapeRegex.MatchString(line) { + if val, err := strconv.ParseUint(strings.ToLower(line[4:6]), 16, 8); err == nil { + buf.WriteByte(byte(val)) + line = line[8:] + continue LineLoop + } + } + } + buf.WriteByte(line[0]) + line = line[1:] } - return line + return buf.String() }