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

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