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 4.7KB

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