blob: 43a28335e052d282823945b9bb550cbf7039cabc (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
// Almost everything in this file is adapted from Ergo IRCd
// This is probably considered a derived work for copyright purposes
//
// SPDX-License-Identifier: MIT
package main
import (
"strings"
"unicode/utf8"
)
func parseTags(tagsString string) (tags map[string]string, err error) {
tags = make(map[string]string)
for 0 < len(tagsString) {
tagEnd := strings.IndexByte(tagsString, ';')
endPos := tagEnd
nextPos := tagEnd + 1
if tagEnd == -1 {
endPos = len(tagsString)
nextPos = len(tagsString)
}
tagPair := tagsString[:endPos]
equalsIndex := strings.IndexByte(tagPair, '=')
var tagName, tagValue string
if equalsIndex == -1 {
// Tag with no value
tagName = tagPair
} else {
tagName, tagValue = tagPair[:equalsIndex], tagPair[equalsIndex+1:]
}
// "Implementations [...] MUST NOT perform any validation that would
// reject the message if an invalid tag key name is used."
if validateTagName(tagName) {
if !validateTagValue(tagValue) {
err = ErrInvalidTagContent
return
}
tags[tagName] = UnescapeTagValue(tagValue)
}
tagsString = tagsString[nextPos:]
}
return
}
func UnescapeTagValue(inString string) string {
// buf.Len() == 0 is the fastpath where we have not needed to unescape any chars
var buf strings.Builder
remainder := inString
for {
backslashPos := strings.IndexByte(remainder, '\\')
if backslashPos == -1 {
if buf.Len() == 0 {
return inString
} else {
buf.WriteString(remainder)
break
}
} else if backslashPos == len(remainder)-1 {
// trailing backslash, which we strip
if buf.Len() == 0 {
return inString[:len(inString)-1]
} else {
buf.WriteString(remainder[:len(remainder)-1])
break
}
}
// Non-trailing backslash detected; we're now on the slowpath
// where we modify the string
if buf.Len() < len(inString) {
buf.Grow(len(inString))
}
buf.WriteString(remainder[:backslashPos])
buf.WriteByte(escapedCharLookupTable[remainder[backslashPos+1]])
remainder = remainder[backslashPos+2:]
}
return buf.String()
}
var escapedCharLookupTable [256]byte
func init() {
for i := 0; i < 256; i += 1 {
escapedCharLookupTable[i] = byte(i)
}
escapedCharLookupTable[':'] = ';'
escapedCharLookupTable['s'] = ' '
escapedCharLookupTable['r'] = '\r'
escapedCharLookupTable['n'] = '\n'
}
// https://ircv3.net/specs/extensions/message-tags.html#rules-for-naming-message-tags
func validateTagName(name string) bool {
if len(name) == 0 {
return false
}
if name[0] == '+' {
name = name[1:]
}
if len(name) == 0 {
return false
}
// Let's err on the side of leniency here; allow -./ (45-47) in any position
for i := 0; i < len(name); i++ {
c := name[i]
if !(('-' <= c && c <= '/') || ('0' <= c && c <= '9') || ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) {
return false
}
}
return true
}
// "Tag values MUST be encoded as UTF8."
func validateTagValue(value string) bool {
return utf8.ValidString(value)
}
|