summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/lrstanley/girc/cap.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/lrstanley/girc/cap.go')
-rw-r--r--vendor/github.com/lrstanley/girc/cap.go439
1 files changed, 19 insertions, 420 deletions
diff --git a/vendor/github.com/lrstanley/girc/cap.go b/vendor/github.com/lrstanley/girc/cap.go
index 1d50460f..e7037f9f 100644
--- a/vendor/github.com/lrstanley/girc/cap.go
+++ b/vendor/github.com/lrstanley/girc/cap.go
@@ -5,14 +5,11 @@
package girc
import (
- "bytes"
- "encoding/base64"
- "fmt"
- "io"
- "sort"
"strings"
)
+// Something not in the list? Depending on the type of capability, you can
+// enable it using Config.SupportedCaps.
var possibleCap = map[string][]string{
"account-notify": nil,
"account-tag": nil,
@@ -22,11 +19,25 @@ var possibleCap = map[string][]string{
"chghost": nil,
"extended-join": nil,
"invite-notify": nil,
- "message-tags": nil,
"multi-prefix": nil,
+ "server-time": nil,
"userhost-in-names": nil,
+
+ "draft/message-tags-0.2": nil,
+ "draft/msgid": nil,
+
+ // "echo-message" is supported, but it's not enabled by default. This is
+ // to prevent unwanted confusion and utilize less traffic if it's not needed.
+ // echo messages aren't sent to girc.PRIVMSG and girc.NOTICE handlers,
+ // rather they are only sent to girc.ALL_EVENTS handlers (this is to prevent
+ // each handler to have to check these types of things for each message).
+ // You can compare events using Event.Equals() to see if they are the same.
}
+// https://ircv3.net/specs/extensions/server-time-3.2.html
+// <value> ::= YYYY-MM-DDThh:mm:ss.sssZ
+const capServerTimeFormat = "2006-01-02T15:04:05.999Z"
+
func (c *Client) listCAP() {
if !c.Config.disableTracking {
c.write(&Event{Command: CAP, Params: []string{CAP_LS, "302"}})
@@ -74,8 +85,8 @@ func parseCap(raw string) map[string][]string {
}
// handleCAP attempts to find out what IRCv3 capabilities the server supports.
-// This will lock further registration until we have acknowledged the
-// capabilities.
+// This will lock further registration until we have acknowledged (or denied)
+// the capabilities.
func handleCAP(c *Client, e Event) {
if len(e.Params) >= 2 && (e.Params[1] == CAP_NEW || e.Params[1] == CAP_DEL) {
c.listCAP()
@@ -172,133 +183,6 @@ func handleCAP(c *Client, e Event) {
}
}
-// SASLMech is an representation of what a SASL mechanism should support.
-// See SASLExternal and SASLPlain for implementations of this.
-type SASLMech interface {
- // Method returns the uppercase version of the SASL mechanism name.
- Method() string
- // Encode returns the response that the SASL mechanism wants to use. If
- // the returned string is empty (e.g. the mechanism gives up), the handler
- // will attempt to panic, as expectation is that if SASL authentication
- // fails, the client will disconnect.
- Encode(params []string) (output string)
-}
-
-// SASLExternal implements the "EXTERNAL" SASL type.
-type SASLExternal struct {
- // Identity is an optional field which allows the client to specify
- // pre-authentication identification. This means that EXTERNAL will
- // supply this in the initial response. This usually isn't needed (e.g.
- // CertFP).
- Identity string `json:"identity"`
-}
-
-// Method identifies what type of SASL this implements.
-func (sasl *SASLExternal) Method() string {
- return "EXTERNAL"
-}
-
-// Encode for external SALS authentication should really only return a "+",
-// unless the user has specified pre-authentication or identification data.
-// See https://tools.ietf.org/html/rfc4422#appendix-A for more info.
-func (sasl *SASLExternal) Encode(params []string) string {
- if len(params) != 1 || params[0] != "+" {
- return ""
- }
-
- if sasl.Identity != "" {
- return sasl.Identity
- }
-
- return "+"
-}
-
-// SASLPlain contains the user and password needed for PLAIN SASL authentication.
-type SASLPlain struct {
- User string `json:"user"` // User is the username for SASL.
- Pass string `json:"pass"` // Pass is the password for SASL.
-}
-
-// Method identifies what type of SASL this implements.
-func (sasl *SASLPlain) Method() string {
- return "PLAIN"
-}
-
-// Encode encodes the plain user+password into a SASL PLAIN implementation.
-// See https://tools.ietf.org/rfc/rfc4422.txt for more info.
-func (sasl *SASLPlain) Encode(params []string) string {
- if len(params) != 1 || params[0] != "+" {
- return ""
- }
-
- in := []byte(sasl.User)
-
- in = append(in, 0x0)
- in = append(in, []byte(sasl.User)...)
- in = append(in, 0x0)
- in = append(in, []byte(sasl.Pass)...)
-
- return base64.StdEncoding.EncodeToString(in)
-}
-
-const saslChunkSize = 400
-
-func handleSASL(c *Client, e Event) {
- if e.Command == RPL_SASLSUCCESS || e.Command == ERR_SASLALREADY {
- // Let the server know that we're done.
- c.write(&Event{Command: CAP, Params: []string{CAP_END}})
- return
- }
-
- // Assume they want us to handle sending auth.
- auth := c.Config.SASL.Encode(e.Params)
-
- if auth == "" {
- // Assume the SASL authentication method doesn't want to respond for
- // some reason. The SASL spec and IRCv3 spec do not define a clear
- // way to abort a SASL exchange, other than to disconnect, or proceed
- // with CAP END.
- c.rx <- &Event{Command: ERROR, Trailing: fmt.Sprintf(
- "closing connection: invalid %s SASL configuration provided: %s",
- c.Config.SASL.Method(), e.Trailing,
- )}
- return
- }
-
- // Send in "saslChunkSize"-length byte chunks. If the last chuck is
- // exactly "saslChunkSize" bytes, send a "AUTHENTICATE +" 0-byte
- // acknowledgement response to let the server know that we're done.
- for {
- if len(auth) > saslChunkSize {
- c.write(&Event{Command: AUTHENTICATE, Params: []string{auth[0 : saslChunkSize-1]}, Sensitive: true})
- auth = auth[saslChunkSize:]
- continue
- }
-
- if len(auth) <= saslChunkSize {
- c.write(&Event{Command: AUTHENTICATE, Params: []string{auth}, Sensitive: true})
-
- if len(auth) == 400 {
- c.write(&Event{Command: AUTHENTICATE, Params: []string{"+"}})
- }
- break
- }
- }
- return
-}
-
-func handleSASLError(c *Client, e Event) {
- if c.Config.SASL == nil {
- c.write(&Event{Command: CAP, Params: []string{CAP_END}})
- return
- }
-
- // Authentication failed. The SASL spec and IRCv3 spec do not define a
- // clear way to abort a SASL exchange, other than to disconnect, or
- // proceed with CAP END.
- c.rx <- &Event{Command: ERROR, Trailing: "closing connection: " + e.Trailing}
-}
-
// handleCHGHOST handles incoming IRCv3 hostname change events. CHGHOST is
// what occurs (when enabled) when a servers services change the hostname of
// a user. Traditionally, this was simply resolved with a quick QUIT and JOIN,
@@ -352,288 +236,3 @@ func handleACCOUNT(c *Client, e Event) {
c.state.Unlock()
c.state.notify(c, UPDATE_STATE)
}
-
-// handleTags handles any messages that have tags that will affect state. (e.g.
-// 'account' tags.)
-func handleTags(c *Client, e Event) {
- if len(e.Tags) == 0 {
- return
- }
-
- account, ok := e.Tags.Get("account")
- if !ok {
- return
- }
-
- c.state.Lock()
- user := c.state.lookupUser(e.Source.Name)
- if user != nil {
- user.Extras.Account = account
- }
- c.state.Unlock()
- c.state.notify(c, UPDATE_STATE)
-}
-
-const (
- prefixTag byte = '@'
- prefixTagValue byte = '='
- prefixUserTag byte = '+'
- tagSeparator byte = ';'
- 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)
-
- if len(raw) > 0 && raw[0] == prefixTag {
- raw = raw[1:]
- }
-
- parts := strings.Split(raw, string(tagSeparator))
- var hasValue int
-
- for i := 0; i < len(parts); i++ {
- hasValue = strings.IndexByte(parts[i], prefixTagValue)
-
- // The tag doesn't contain a value or has a splitter with no value.
- if hasValue < 1 || len(parts[i]) < hasValue+1 {
- if !validTag(parts[i]) {
- continue
- }
-
- t[parts[i]] = ""
- continue
- }
-
- // Check if tag key or decoded value are invalid.
- if !validTag(parts[i][:hasValue]) || !validTagValue(tagDecoder.Replace(parts[i][hasValue+1:])) {
- continue
- }
-
- t[parts[i][:hasValue]] = parts[i][hasValue+1:]
- }
-
- return t
-}
-
-// Len determines the length of the bytes representation of this tag map. This
-// does not include the trailing space required when creating an event, but
-// does include the tag prefix ("@").
-func (t Tags) Len() (length int) {
- if t == nil {
- return 0
- }
-
- return len(t.Bytes())
-}
-
-// Count finds how many total tags that there are.
-func (t Tags) Count() int {
- if t == nil {
- return 0
- }
-
- return len(t)
-}
-
-// Bytes returns a []byte representation of this tag map, including the tag
-// prefix ("@"). Note that this will return the tags sorted, regardless of
-// the order of how they were originally parsed.
-func (t Tags) Bytes() []byte {
- if t == nil {
- return []byte{}
- }
-
- max := len(t)
- if max == 0 {
- return nil
- }
-
- buffer := new(bytes.Buffer)
- buffer.WriteByte(prefixTag)
-
- var current int
-
- // Sort the writing of tags so we can at least guarantee that they will
- // be in order, and testable.
- var names []string
- for tagName := range t {
- names = append(names, tagName)
- }
- sort.Strings(names)
-
- for i := 0; i < len(names); i++ {
- // Trim at max allowed chars.
- if (buffer.Len() + len(names[i]) + len(t[names[i]]) + 2) > maxTagLength {
- return buffer.Bytes()
- }
-
- buffer.WriteString(names[i])
-
- // Write the value as necessary.
- if len(t[names[i]]) > 0 {
- buffer.WriteByte(prefixTagValue)
- buffer.WriteString(t[names[i]])
- }
-
- // add the separator ";" between tags.
- if current < max-1 {
- buffer.WriteByte(tagSeparator)
- }
-
- current++
- }
-
- return buffer.Bytes()
-}
-
-// String returns a string representation of this tag map.
-func (t Tags) String() string {
- if t == nil {
- return ""
- }
-
- 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 t == nil {
- return "", false
- }
-
- 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 t == nil {
- t = make(Tags)
- }
-
- if !validTag(key) {
- return fmt.Errorf("tag key %q is invalid", key)
- }
-
- value = tagEncoder.Replace(value)
-
- if len(value) > 0 && !validTagValue(value) {
- return fmt.Errorf("tag value %q of key %q is invalid", value, key)
- }
-
- // 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 t == nil {
- return false
- }
-
- 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
- }
-
- // Allow user tags to be passed to validTag.
- if len(name) >= 2 && name[0] == prefixUserTag {
- name = name[1:]
- }
-
- for i := 0; i < len(name); i++ {
- // A-Z, a-z, 0-9, -/._
- if (name[i] < 'A' || name[i] > 'Z') && (name[i] < 'a' || name[i] > 'z') && (name[i] < '-' || name[i] > '9') && name[i] != '_' {
- return false
- }
- }
-
- return true
-}
-
-// validTagValue valids a decoded IRC tag value. If the value is not decoded
-// with tagDecoder first, it may be seen as invalid.
-func validTagValue(value string) bool {
- for i := 0; i < len(value); i++ {
- // Don't allow any invisible chars within the tag, or semicolons.
- if value[i] < '!' || value[i] > '~' || value[i] == ';' {
- return false
- }
- }
- return true
-}