// Copyright 2016-2017 Liam Stanley . All rights reserved. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. package girc import ( "bytes" "fmt" "io" "strings" "time" ) var possibleCap = []string{ "account-notify", "away-notify", "chghost", "message-tags", } // capHandler attempts to find out what IRCv3 capabilities the server supports. // This will lock further registration until we have acknowledged the // capabilities. func (c *Client) capHandler() { // testnet.inspircd.org may potentially be used for testing. capDone := make(chan struct{}) var caps []string possible := c.Config.SupportedCaps possible = append(possible, possibleCap...) cuid := c.Callbacks.sregister(true, CAP, CallbackFunc(func(client *Client, e Event) { // We can assume there was a failure attempting to enable a capability. if len(e.Params) == 2 && e.Params[1] == CAP_NAK { // Let the server know that we're done. client.Send(&Event{Command: CAP, Params: []string{CAP_END}}) capDone <- struct{}{} return } if len(e.Params) >= 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_LS { client.state.mu.Lock() client.state.supportedCap = strings.Split(e.Trailing, " ") for i := 0; i < len(client.state.supportedCap); i++ { for j := 0; j < len(possible); j++ { if client.state.supportedCap[i] == possibleCap[j] { // It's one for which we support. caps = append(caps, client.state.supportedCap[i]) break } } } client.state.mu.Unlock() // Indicates if this is a multi-line LS. (2 args means it's the // last LS) if len(e.Params) == 2 { // If we support no caps, just ack the CAP message and END. if len(caps) == 0 { client.Send(&Event{Command: CAP, Params: []string{CAP_END}}) capDone <- struct{}{} return } // Let them know which ones we'd like to enable. client.Send(&Event{Command: CAP, Params: []string{CAP_REQ}, Trailing: strings.Join(caps, " ")}) } } if len(e.Params) == 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_ACK { client.state.mu.Lock() client.state.enabledCap = strings.Split(e.Trailing, " ") client.state.mu.Unlock() // Let the server know that we're done. client.Send(&Event{Command: CAP, Params: []string{CAP_END}}) capDone <- struct{}{} return } })) unknCuid := c.Callbacks.sregister(true, ERR_UNKNOWNCOMMAND, CallbackFunc(func(client *Client, e Event) { capDone <- struct{}{} })) // Ensure that the callbacks are removed when done. defer c.Callbacks.Remove(cuid) defer c.Callbacks.Remove(unknCuid) // List the capabilities, specifically with the max protocol we support. c.Send(&Event{Command: CAP, Params: []string{CAP_LS, "302"}}) select { case <-capDone: close(capDone) case <-time.After(time.Second * 15): // Wait 15 seconds, only because the server HAS YET to tell us that // it's an unknown command, meaning it's likely doing things behind // the scenes. } } func handleCHGHOST(c *Client, e Event) { if len(e.Params) != 2 { return } c.state.mu.Lock() for chanName := range c.state.channels { if _, ok := c.state.channels[chanName].users[e.Source.Name]; !ok { continue } c.state.channels[chanName].users[e.Source.Name].Ident = e.Params[0] c.state.channels[chanName].users[e.Source.Name].Host = e.Params[1] } c.state.mu.Unlock() } func handleAWAY(c *Client, e Event) { c.state.mu.Lock() for chanName := range c.state.channels { if _, ok := c.state.channels[chanName].users[e.Source.Name]; !ok { continue } c.state.channels[chanName].users[e.Source.Name].Extras.Away = e.Trailing } c.state.mu.Unlock() } func handleACCOUNT(c *Client, e Event) { if len(e.Params) != 1 { return } account := e.Params[0] if account == "*" { account = "" } c.state.mu.Lock() for chanName := range c.state.channels { if _, ok := c.state.channels[chanName].users[e.Source.Name]; !ok { continue } c.state.channels[chanName].users[e.Source.Name].Extras.Account = account } c.state.mu.Unlock() } const ( prefixTag byte = 0x40 // @ prefixTagValue byte = 0x3D // = tagSeparator byte = 0x3B // ; maxTagLength int = 511 // 510 + @ and " " (space), though space usually not included. ) // Tags represents the key-value pairs in IRCv3 message tags. The map contains // the encoded message-tag values. If the tag is present, it may still be // empty. See Tags.Get() and Tags.Set() for use with getting/setting // information within the tags. // // Note that retrieving and setting tags are not concurrent safe. If this is // necessary, you will need to implement it yourself. type Tags map[string]string // ParseTags parses out the key-value map of tags. raw should only be the tag // data, not a full message. For example: // @aaa=bbb;ccc;example.com/ddd=eee // NOT: // @aaa=bbb;ccc;example.com/ddd=eee :nick!ident@host.com PRIVMSG me :Hello func ParseTags(raw string) (t Tags) { t = make(Tags) parts := strings.Split(raw, string(tagSeparator)) var hasValue int for i := 0; i < len(parts); i++ { hasValue = strings.IndexByte(parts[i], prefixTagValue) if hasValue < 1 { // The tag doesn't contain a value. t[parts[i]] = "" continue } // May have equals sign and no value as well. if len(parts[i]) < hasValue+1 { t[parts[i]] = "" continue } t[parts[i][:hasValue]] = parts[i][hasValue+1:] continue } return t } // Len determines the length of the string representation of this tag map. func (t Tags) Len() (length int) { return len(t.String()) } // Count finds how many total tags that there are. func (t Tags) Count() int { return len(t) } // Bytes returns a []byte representation of this tag map. func (t Tags) Bytes() []byte { max := len(t) if max == 0 { return nil } buffer := new(bytes.Buffer) var current int for tagName, tagValue := range t { // Trim at max allowed chars. if (buffer.Len() + len(tagName) + len(tagValue) + 2) > maxTagLength { return buffer.Bytes() } buffer.WriteString(tagName) // Write the value as necessary. if len(tagValue) > 0 { buffer.WriteByte(prefixTagValue) buffer.WriteString(tagValue) } // add the separator ";" between tags. if current <= max { buffer.WriteByte(tagSeparator) } current++ } return buffer.Bytes() } // String returns a string representation of this tag map. func (t Tags) String() string { return string(t.Bytes()) } // writeTo writes the necessary tag bytes to an io.Writer, including a trailing // space-separator. func (t Tags) writeTo(w io.Writer) (n int, err error) { b := t.Bytes() if len(b) == 0 { return n, err } n, err = w.Write(b) if err != nil { return n, err } var j int j, err = w.Write([]byte{eventSpace}) n += j return n, err } // tagDecode are encoded -> decoded pairs for replacement to decode. var tagDecode = []string{ "\\:", ";", "\\s", " ", "\\\\", "\\", "\\r", "\r", "\\n", "\n", } var tagDecoder = strings.NewReplacer(tagDecode...) // tagEncode are decoded -> encoded pairs for replacement to decode. var tagEncode = []string{ ";", "\\:", " ", "\\s", "\\", "\\\\", "\r", "\\r", "\n", "\\n", } var tagEncoder = strings.NewReplacer(tagEncode...) // Get returns the unescaped value of given tag key. Note that this is not // concurrent safe. func (t Tags) Get(key string) (tag string, success bool) { if _, ok := t[key]; ok { tag = tagDecoder.Replace(t[key]) success = true } return tag, success } // Set escapes given value and saves it as the value for given key. Note that // this is not concurrent safe. func (t Tags) Set(key, value string) error { if !validTag(key) { return fmt.Errorf("tag %q is invalid", key) } value = tagEncoder.Replace(value) // Check to make sure it's not too long here. if (t.Len() + len(key) + len(value) + 2) > maxTagLength { return fmt.Errorf("unable to set tag %q [value %q]: tags too long for message", key, value) } t[key] = value return nil } // Remove deletes the tag frwom the tag map. func (t Tags) Remove(key string) (success bool) { if _, success = t[key]; success { delete(t, key) } return success } // validTag validates an IRC tag. func validTag(name string) bool { if len(name) < 1 { return false } for i := 0; i < len(name); i++ { // A-Z, a-z, 0-9, -/._ if (name[i] < 0x41 || name[i] > 0x5A) && (name[i] < 0x61 || name[i] > 0x7A) && (name[i] < 0x2D || name[i] > 0x39) && name[i] != 0x5F { return false } } return true }