From c75299e1c376026ada795d7edf704e0bf5f0eb6b Mon Sep 17 00:00:00 2001
From: Přemysl Janouch <p@janouch.name>
Date: Mon, 30 Jul 2018 17:39:32 +0200
Subject: hid: port IRC 3.2 message tag parsing, unused

---
 hid/main.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 66 insertions(+), 18 deletions(-)

diff --git a/hid/main.go b/hid/main.go
index 1c72fca..eec67d9 100644
--- a/hid/main.go
+++ b/hid/main.go
@@ -296,17 +296,72 @@ func ircFnmatch(pattern string, s string) bool {
 	return matched
 }
 
-// TODO: We will need to add support for IRCv3 tags.
 var reMsg = regexp.MustCompile(
-	`^(?::([^! ]*)(?:!([^@]*)@([^ ]*))? +)?([^ ]+)(.*)?$`)
+	`^(?:@[^ ]* +)(?::([^! ]*)(?:!([^@]*)@([^ ]*))? +)?([^ ]+)(.*)?$`)
 var reArgs = regexp.MustCompile(`:.*| [^: ][^ ]*`)
 
 type message struct {
-	nick    string   // optional nickname
-	user    string   // optional username
-	host    string   // optional hostname or IP address
-	command string   // command name
-	params  []string // arguments
+	tags    map[string]string // IRC 3.2 message tags
+	nick    string            // optional nickname
+	user    string            // optional username
+	host    string            // optional hostname or IP address
+	command string            // command name
+	params  []string          // arguments
+}
+
+func ircUnescapeMessageTag(value string) string {
+	var buf []byte
+	escape := false
+	for i := 0; i < len(value); i++ {
+		if escape {
+			switch value[i] {
+			case ':':
+				buf = append(buf, ';')
+			case 's':
+				buf = append(buf, ' ')
+			case 'r':
+				buf = append(buf, '\r')
+			case 'n':
+				buf = append(buf, '\n')
+			default:
+				buf = append(buf, value[i])
+			}
+			escape = false
+		} else if value[i] == '\\' {
+			escape = true
+		} else {
+			buf = append(buf, value[i])
+		}
+	}
+	return string(buf)
+}
+
+func ircParseMessageTags(tags string, out map[string]string) {
+	for _, tag := range splitString(tags, ";", true /* ignoreEmpty */) {
+		if equal := strings.IndexByte(tag, '='); equal < 0 {
+			out[tag] = ""
+		} else {
+			out[tag[:equal]] = ircUnescapeMessageTag(tag[equal+1:])
+		}
+	}
+}
+
+func ircParseMessage(line string) *message {
+	m := reMsg.FindStringSubmatch(line)
+	if m == nil {
+		return nil
+	}
+
+	msg := message{nil, m[2], m[3], m[4], m[5], nil}
+	if m[1] != "" {
+		msg.tags = make(map[string]string)
+		ircParseMessageTags(m[1], msg.tags)
+	}
+	for _, x := range reArgs.FindAllString(m[6], -1) {
+		msg.params = append(msg.params, x[1:])
+	}
+	return &msg
+
 }
 
 // Everything as per RFC 2812
@@ -2627,23 +2682,16 @@ func (c *client) onRead(data []byte, readErr error) {
 			break
 		}
 
+		// XXX: And since it accepts LF, we miscalculate receivedBytes within.
 		c.recvQ = c.recvQ[advance:]
 		line := string(token)
 		log.Printf("-> %s\n", line)
 
-		m := reMsg.FindStringSubmatch(line)
-		if m == nil {
+		if msg := ircParseMessage(line); msg == nil {
 			log.Println("error: invalid line")
-			continue
-		}
-
-		msg := message{m[1], m[2], m[3], m[4], nil}
-		for _, x := range reArgs.FindAllString(m[5], -1) {
-			msg.params = append(msg.params, x[1:])
+		} else {
+			ircProcessMessage(c, msg, line)
 		}
-
-		// XXX: And since it accepts LF, we miscalculate receivedBytes within.
-		ircProcessMessage(c, &msg, line)
 	}
 
 	if readErr != nil {
-- 
cgit v1.2.3-70-g09d2