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.5KB

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