You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

capability.go 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. // Copyright (c) 2012-2014 Jeremy Latt
  2. // Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
  3. // released under the MIT license
  4. package irc
  5. import (
  6. "strings"
  7. "github.com/goshuirc/irc-go/ircmsg"
  8. )
  9. // Capability represents an optional feature that a client may request from the server.
  10. type Capability string
  11. const (
  12. // AccountNotify is this IRCv3 capability: http://ircv3.net/specs/extensions/account-notify-3.1.html
  13. AccountNotify Capability = "account-notify"
  14. // AccountTag is this IRCv3 capability: http://ircv3.net/specs/extensions/account-tag-3.2.html
  15. AccountTag Capability = "account-tag"
  16. // AwayNotify is this IRCv3 capability: http://ircv3.net/specs/extensions/away-notify-3.1.html
  17. AwayNotify Capability = "away-notify"
  18. // CapNotify is this IRCv3 capability: http://ircv3.net/specs/extensions/cap-notify-3.2.html
  19. CapNotify Capability = "cap-notify"
  20. // ChgHost is this IRCv3 capability: http://ircv3.net/specs/extensions/chghost-3.2.html
  21. ChgHost Capability = "chghost"
  22. // EchoMessage is this IRCv3 capability: http://ircv3.net/specs/extensions/echo-message-3.2.html
  23. EchoMessage Capability = "echo-message"
  24. // ExtendedJoin is this IRCv3 capability: http://ircv3.net/specs/extensions/extended-join-3.1.html
  25. ExtendedJoin Capability = "extended-join"
  26. // InviteNotify is this IRCv3 capability: http://ircv3.net/specs/extensions/invite-notify-3.2.html
  27. InviteNotify Capability = "invite-notify"
  28. // MaxLine is this proposed capability: https://github.com/DanielOaks/ircv3-specifications/blob/master+line-lengths/extensions/line-lengths.md
  29. MaxLine Capability = "draft/maxline"
  30. // MessageIDs is this draft IRCv3 capability: http://ircv3.net/specs/extensions/message-ids.html
  31. MessageIDs Capability = "draft/message-ids"
  32. // MessageTags is this draft IRCv3 capability: http://ircv3.net/specs/core/message-tags-3.3.html
  33. MessageTags Capability = "draft/message-tags-0.2"
  34. // MultiPrefix is this IRCv3 capability: http://ircv3.net/specs/extensions/multi-prefix-3.1.html
  35. MultiPrefix Capability = "multi-prefix"
  36. // Rename is this proposed capability: https://github.com/SaberUK/ircv3-specifications/blob/rename/extensions/rename.md
  37. Rename Capability = "draft/rename"
  38. // SASL is this IRCv3 capability: http://ircv3.net/specs/extensions/sasl-3.2.html
  39. SASL Capability = "sasl"
  40. // ServerTime is this IRCv3 capability: http://ircv3.net/specs/extensions/server-time-3.2.html
  41. ServerTime Capability = "server-time"
  42. // STS is this draft IRCv3 capability: http://ircv3.net/specs/core/sts-3.3.html
  43. STS Capability = "draft/sts"
  44. // UserhostInNames is this IRCv3 capability: http://ircv3.net/specs/extensions/userhost-in-names-3.2.html
  45. UserhostInNames Capability = "userhost-in-names"
  46. )
  47. var (
  48. // SupportedCapabilities are the caps we advertise.
  49. SupportedCapabilities = CapabilitySet{
  50. AccountTag: true,
  51. AccountNotify: true,
  52. AwayNotify: true,
  53. CapNotify: true,
  54. ChgHost: true,
  55. EchoMessage: true,
  56. ExtendedJoin: true,
  57. InviteNotify: true,
  58. MessageIDs: true,
  59. // MaxLine is set during server startup
  60. MessageTags: true,
  61. MultiPrefix: true,
  62. Rename: true,
  63. // SASL is set during server startup
  64. ServerTime: true,
  65. // STS is set during server startup
  66. UserhostInNames: true,
  67. }
  68. // CapValues are the actual values we advertise to v3.2 clients.
  69. CapValues = map[Capability]string{
  70. SASL: "PLAIN,EXTERNAL",
  71. }
  72. )
  73. func (capability Capability) String() string {
  74. return string(capability)
  75. }
  76. // CapState shows whether we're negotiating caps, finished, etc for connection registration.
  77. type CapState uint
  78. const (
  79. // CapNone means CAP hasn't been negotiated at all.
  80. CapNone CapState = iota
  81. // CapNegotiating means CAP is being negotiated and registration should be paused.
  82. CapNegotiating CapState = iota
  83. // CapNegotiated means CAP negotiation has been successfully ended and reg should complete.
  84. CapNegotiated CapState = iota
  85. )
  86. // CapVersion is used to select which max version of CAP the client supports.
  87. type CapVersion uint
  88. const (
  89. // Cap301 refers to the base CAP spec.
  90. Cap301 CapVersion = 301
  91. // Cap302 refers to the IRCv3.2 CAP spec.
  92. Cap302 CapVersion = 302
  93. )
  94. // CapabilitySet is used to track supported, enabled, and existing caps.
  95. type CapabilitySet map[Capability]bool
  96. func (set CapabilitySet) String(version CapVersion) string {
  97. strs := make([]string, len(set))
  98. index := 0
  99. for capability := range set {
  100. capString := string(capability)
  101. if version == Cap302 {
  102. val, exists := CapValues[capability]
  103. if exists {
  104. capString += "=" + val
  105. }
  106. }
  107. strs[index] = capString
  108. index++
  109. }
  110. return strings.Join(strs, " ")
  111. }
  112. // CAP <subcmd> [<caps>]
  113. func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
  114. subCommand := strings.ToUpper(msg.Params[0])
  115. capabilities := make(CapabilitySet)
  116. var capString string
  117. if len(msg.Params) > 1 {
  118. capString = msg.Params[1]
  119. strs := strings.Split(capString, " ")
  120. for _, str := range strs {
  121. if len(str) > 0 {
  122. capabilities[Capability(str)] = true
  123. }
  124. }
  125. }
  126. switch subCommand {
  127. case "LS":
  128. if !client.registered {
  129. client.capState = CapNegotiating
  130. }
  131. if len(msg.Params) > 1 && msg.Params[1] == "302" {
  132. client.capVersion = 302
  133. }
  134. // weechat 1.4 has a bug here where it won't accept the CAP reply unless it contains
  135. // the server.name source... otherwise it doesn't respond to the CAP message with
  136. // anything and just hangs on connection.
  137. //TODO(dan): limit number of caps and send it multiline in 3.2 style as appropriate.
  138. client.Send(nil, server.name, "CAP", client.nick, subCommand, SupportedCapabilities.String(client.capVersion))
  139. case "LIST":
  140. client.Send(nil, server.name, "CAP", client.nick, subCommand, client.capabilities.String(Cap301)) // values not sent on LIST so force 3.1
  141. case "REQ":
  142. // make sure all capabilities actually exist
  143. for capability := range capabilities {
  144. if !SupportedCapabilities[capability] {
  145. client.Send(nil, server.name, "CAP", client.nick, "NAK", capString)
  146. return false
  147. }
  148. }
  149. for capability := range capabilities {
  150. client.capabilities[capability] = true
  151. }
  152. client.Send(nil, server.name, "CAP", client.nick, "ACK", capString)
  153. case "END":
  154. if !client.registered {
  155. client.capState = CapNegotiated
  156. server.tryRegister(client)
  157. }
  158. default:
  159. client.Send(nil, server.name, ERR_INVALIDCAPCMD, client.nick, subCommand, "Invalid CAP subcommand")
  160. }
  161. return false
  162. }