ircmsg: add ParseTags function to allow parsing of tag-like strings arbitrarily

This commit is contained in:
Daniel Oaks 2017-08-24 08:39:31 +10:00
parent ea7e22b650
commit a5eafb7ec8
3 changed files with 125 additions and 20 deletions

@ -11,6 +11,8 @@ import (
var (
// ErrorLineIsEmpty indicates that the given IRC line was empty.
ErrorLineIsEmpty = errors.New("Line is empty")
// ErrorTagsContainsBadChar indicates that the passed tag string contains a space or newline.
ErrorTagsContainsBadChar = errors.New("Tag string contains bad character (such as a space or newline)")
)
// IrcMessage represents an IRC message, as defined by the RFCs and as
@ -75,25 +77,10 @@ func parseLine(line string, maxlenTags, maxlenRest int, useMaxLen bool) (IrcMess
return ircmsg, ErrorLineIsEmpty
}
// truncate if desired
if useMaxLen && len(tags) > maxlenTags {
tags = tags[:maxlenTags]
}
for _, fulltag := range strings.Split(tags, ";") {
var name string
var val TagValue
if strings.Contains(fulltag, "=") {
val.HasValue = true
splittag := strings.SplitN(fulltag, "=", 2)
name = splittag[0]
val.Value = UnescapeTagValue(splittag[1])
} else {
name = fulltag
val.HasValue = false
}
ircmsg.Tags[name] = val
var err error
ircmsg.Tags, err = parseTags(tags, maxlenTags, useMaxLen)
if err != nil {
return ircmsg, err
}
}

@ -103,3 +103,46 @@ func MakeTags(values ...interface{}) *map[string]TagValue {
return &tags
}
// ParseTags takes a tag string such as "network=freenode;buffer=#chan;joined=1;topic=some\stopic" and outputs a TagValue map.
func ParseTags(tags string) (map[string]TagValue, error) {
return parseTags(tags, 0, false)
}
// parseTags does the actual tags parsing for the above user-facing function.
func parseTags(tags string, maxlenTags int, useMaxLen bool) (map[string]TagValue, error) {
tagMap := make(map[string]TagValue)
// confirm no bad strings exist
if strings.ContainsAny(tags, " \r\n") {
return tagMap, ErrorTagsContainsBadChar
}
// truncate if desired
if useMaxLen && len(tags) > maxlenTags {
tags = tags[:maxlenTags]
}
for _, fulltag := range strings.Split(tags, ";") {
// skip empty tag string values
if len(fulltag) < 1 {
continue
}
var name string
var val TagValue
if strings.Contains(fulltag, "=") {
val.HasValue = true
splittag := strings.SplitN(fulltag, "=", 2)
name = splittag[0]
val.Value = UnescapeTagValue(splittag[1])
} else {
name = fulltag
val.HasValue = false
}
tagMap[name] = val
}
return tagMap, nil
}

@ -1,6 +1,9 @@
package ircmsg
import "testing"
import (
"reflect"
"testing"
)
type testcase struct {
escaped string
@ -56,3 +59,75 @@ func TestUnescape(t *testing.T) {
}
}
}
// tag string tests
type testtags struct {
raw string
tags map[string]TagValue
}
type testtagswithlen struct {
raw string
length int
tags map[string]TagValue
}
var tagdecodelentests = []testtagswithlen{
{"time=12732;re", 512, *MakeTags("time", "12732", "re", nil)},
{"time=12732;re", 12, *MakeTags("time", "12732", "r", nil)},
{"", 512, *MakeTags()},
}
var tagdecodetests = []testtags{
{"", *MakeTags()},
{"time=12732;re", *MakeTags("time", "12732", "re", nil)},
}
var tagdecodetesterrors = []string{
"\r\n",
" \r\n",
"tags=tesa\r\n",
"tags=tested \r\n",
}
func TestDecodeTags(t *testing.T) {
for _, pair := range tagdecodelentests {
tags, err := parseTags(pair.raw, pair.length, true)
if err != nil {
t.Error(
"For", pair.raw,
"Failed to parse tags:", err,
)
}
if !reflect.DeepEqual(tags, pair.tags) {
t.Error(
"For", pair.raw,
"expected", pair.tags,
"got", tags,
)
}
}
for _, pair := range tagdecodetests {
tags, err := ParseTags(pair.raw)
if err != nil {
t.Error(
"For", pair.raw,
"Failed to parse line:", err,
)
}
if !reflect.DeepEqual(tags, pair.tags) {
t.Error(
"For", pair.raw,
"expected", pair.tags,
"got", tags,
)
}
}
for _, line := range tagdecodetesterrors {
_, err := ParseTags(line)
if err == nil {
t.Error(
"Expected to fail parsing", line,
)
}
}
}