implement the escaping rules

This commit is contained in:
David 2021-10-09 14:08:55 -04:00
parent 765d35b399
commit 092bc50199
2 changed files with 132 additions and 4 deletions

View File

@ -42,8 +42,8 @@ func (m *Message) parseTags() (map[string]string, error) {
key := m.Raw[m.parseIndex : m.parseIndex+next]
m.parseIndex += next + 1
eot := m.findNext(";", " ")
tags[key] = m.Raw[m.parseIndex : m.parseIndex+eot]
m.parseIndex += len(tags[key]) + 1
tags[key] = unescapeTag(m.Raw[m.parseIndex : m.parseIndex+eot])
m.parseIndex += len(m.Raw[m.parseIndex:m.parseIndex+eot]) + 1
} else if m.Raw[m.parseIndex+next] == ';' {
key := m.Raw[m.parseIndex : m.parseIndex+next]
m.parseIndex += next + 1
@ -69,9 +69,10 @@ func (m *Message) parseCommand() (command, error) {
start := m.parseIndex
endofparse := strings.Index(m.Raw[m.parseIndex:], " ")
if endofparse == -1 {
return "", fmt.Errorf("end of string encountered while parsing command")
m.parseIndex = len(m.Raw) // a command with no parameters
} else {
m.parseIndex += endofparse
}
m.parseIndex += endofparse
return command(m.Raw[start:m.parseIndex]), nil
}
@ -131,6 +132,45 @@ func (m *Message) findNext(bs ...string) int {
return r
}
func unescapeTag(s string) string {
sb := strings.Builder{}
escaping := false
i := 0
for i < len(s) {
if escaping {
if i+1 > len(s) {
break
}
switch s[i] {
case ':':
sb.WriteByte(';')
case '\\':
sb.WriteByte('\\')
case 's':
sb.WriteByte(' ')
case 'r':
sb.WriteByte('\r')
case 'n':
sb.WriteByte('\n')
default:
sb.WriteByte(s[i])
}
i++
escaping = false
} else if s[i] == '\\' {
if !escaping {
escaping = true
i++
continue
}
} else {
sb.WriteByte(s[i])
i++
}
}
return sb.String()
}
func (m *Message) ToBytes() []byte {
return nil
}

View File

@ -33,6 +33,12 @@ func TestParsing(t *testing.T) {
Command: PRIVMSG,
Parameters: []string{"#chan", "Hey what's up!"},
}},
{
input: `@a=b\\and\nk;c=72\s45;d=gh\:764 CAP`,
output: &Message{
Tags: map[string]string{"a": "b\\and\nk", "c": "72 45", "d": `gh;764`},
Command: command("CAP"),
}},
{
input: `@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4= :dan!d@localhost PRIVMSG #chan :Hey what's up!`,
output: &Message{
@ -41,6 +47,18 @@ func TestParsing(t *testing.T) {
Command: PRIVMSG,
Parameters: []string{"#chan", "Hey what's up!"},
}},
{
input: ":src AWAY",
output: &Message{
Source: "src",
Command: AWAY,
}},
{
input: ":src AWAY ",
output: &Message{
Source: "src",
Command: AWAY,
}},
{
input: `CAP REQ :sasl`,
output: &Message{
@ -65,6 +83,13 @@ func TestParsing(t *testing.T) {
Command: CAP,
Parameters: []string{"REQ", " asdf qwer"},
}},
{
input: ":coolguy!ag@net\x035w\x03ork.admin PRIVMSG foo :bar baz",
output: &Message{
Source: "coolguy!ag@net\x035w\x03ork.admin",
Command: PRIVMSG,
Parameters: []string{"foo", "bar baz"},
}},
{
input: `:coolguy PRIVMSG bar :lol :) `,
output: &Message{
@ -86,6 +111,57 @@ func TestParsing(t *testing.T) {
Command: MODE,
Parameters: []string{"#tckk", "+n"},
}},
{
input: ":services.esper.net MODE #foo-bar +o foobar ",
output: &Message{
Source: "services.esper.net",
Command: MODE,
Parameters: []string{"#foo-bar", "+o", "foobar"},
}},
{
input: `@tag1=value\\ntest COMMAND`,
output: &Message{
Tags: map[string]string{"tag1": `value\ntest`},
Command: command("COMMAND"),
}},
{
input: `@tag1=value\1 COMMAND`,
output: &Message{
Tags: map[string]string{"tag1": `value1`},
Command: command("COMMAND"),
}},
{
input: `@tag1=value\ COMMAND`,
output: &Message{
Tags: map[string]string{"tag1": `value`},
Command: command("COMMAND"),
}},
{
input: `@tag1=1;tag2=3;tag3=4;tag1=5 COMMAND`,
output: &Message{
Tags: map[string]string{"tag1": "5", "tag2": "3", "tag3": "4"},
Command: command("COMMAND"),
}},
{
input: `@tag1=1;tag2=3;tag3=4;tag1=5;vendor/tag2=8 COMMAND`,
output: &Message{
Tags: map[string]string{"tag1": "5", "tag2": "3", "tag3": "4", "vendor/tag2": "8"},
Command: command("COMMAND"),
}},
{
input: `:SomeOp MODE #channel :+i`,
output: &Message{
Source: "SomeOp",
Command: MODE,
Parameters: []string{"#channel", "+i"},
}},
{
input: `:SomeOp MODE #channel +oo SomeUser :AnotherUser`,
output: &Message{
Source: "SomeOp",
Command: MODE,
Parameters: []string{"#channel", "+oo", "SomeUser", "AnotherUser"},
}},
}
for _, tc := range testcases {
m := &Message{Raw: tc.input}
@ -94,6 +170,12 @@ func TestParsing(t *testing.T) {
t.Logf("tags: actual: %v, expected: %v", m.Tags, tc.output.Tags)
t.Fail()
}
for i := range tc.output.Tags {
if tc.output.Tags[i] != m.Tags[i] {
t.Logf("tags: actual: %v, expected: %v", m.Tags[i], tc.output.Tags[i])
t.Fail()
}
}
if tc.output.Source != m.Source {
t.Logf("source: actual: %v, expected: %v", m.Source, tc.output.Source)
t.Fail()
@ -106,5 +188,11 @@ func TestParsing(t *testing.T) {
t.Logf("parameters: actual: %v (%d), expected: %v (%d)", m.Parameters, len(m.Parameters), tc.output.Parameters, len(tc.output.Parameters))
t.Fail()
}
for i := range tc.output.Parameters {
if tc.output.Parameters[i] != m.Parameters[i] {
t.Logf("parameters: actual: %v, expected: %v", m.Parameters[i], tc.output.Parameters[i])
t.Fail()
}
}
}
}