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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. // Copyright (c) 2012-2014 Jeremy Latt
  2. // Copyright (c) 2014-2015 Edmund Huber
  3. // Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
  4. // released under the MIT license
  5. package modes
  6. import (
  7. "strings"
  8. "github.com/oragono/oragono/irc/utils"
  9. )
  10. var (
  11. // SupportedUserModes are the user modes that we actually support (modifying).
  12. SupportedUserModes = Modes{
  13. Bot, Invisible, Operator, RegisteredOnly, ServerNotice, UserRoleplaying,
  14. }
  15. // SupportedChannelModes are the channel modes that we support.
  16. SupportedChannelModes = Modes{
  17. BanMask, ChanRoleplaying, ExceptMask, InviteMask, InviteOnly, Key,
  18. Moderated, NoOutside, OpOnlyTopic, RegisteredOnly, Secret, UserLimit,
  19. NoCTCP,
  20. }
  21. )
  22. // ModeOp is an operation performed with modes
  23. type ModeOp rune
  24. const (
  25. // Add is used when adding the given key.
  26. Add ModeOp = '+'
  27. // List is used when listing modes (for instance, listing the current bans on a channel).
  28. List ModeOp = '='
  29. // Remove is used when taking away the given key.
  30. Remove ModeOp = '-'
  31. )
  32. // Mode represents a user/channel/server mode
  33. type Mode rune
  34. func (mode Mode) String() string {
  35. return string(mode)
  36. }
  37. // ModeChange is a single mode changing
  38. type ModeChange struct {
  39. Mode Mode
  40. Op ModeOp
  41. Arg string
  42. }
  43. // ModeChanges are a collection of 'ModeChange's
  44. type ModeChanges []ModeChange
  45. func (changes ModeChanges) Strings() (result []string) {
  46. if len(changes) == 0 {
  47. return
  48. }
  49. var builder strings.Builder
  50. op := changes[0].Op
  51. builder.WriteRune(rune(op))
  52. for _, change := range changes {
  53. if change.Op != op {
  54. op = change.Op
  55. builder.WriteRune(rune(op))
  56. }
  57. builder.WriteRune(rune(change.Mode))
  58. }
  59. result = append(result, builder.String())
  60. for _, change := range changes {
  61. if change.Arg == "" {
  62. continue
  63. }
  64. result = append(result, change.Arg)
  65. }
  66. return
  67. }
  68. // Modes is just a raw list of modes
  69. type Modes []Mode
  70. func (modes Modes) String() string {
  71. strs := make([]string, len(modes))
  72. for index, mode := range modes {
  73. strs[index] = mode.String()
  74. }
  75. return strings.Join(strs, "")
  76. }
  77. // User Modes
  78. const (
  79. Bot Mode = 'B'
  80. Invisible Mode = 'i'
  81. LocalOperator Mode = 'O'
  82. Operator Mode = 'o'
  83. Restricted Mode = 'r'
  84. RegisteredOnly Mode = 'R'
  85. ServerNotice Mode = 's'
  86. TLS Mode = 'Z'
  87. UserRoleplaying Mode = 'E'
  88. WallOps Mode = 'w'
  89. )
  90. // Channel Modes
  91. const (
  92. BanMask Mode = 'b' // arg
  93. ChanRoleplaying Mode = 'E' // flag
  94. ExceptMask Mode = 'e' // arg
  95. InviteMask Mode = 'I' // arg
  96. InviteOnly Mode = 'i' // flag
  97. Key Mode = 'k' // flag arg
  98. Moderated Mode = 'm' // flag
  99. NoOutside Mode = 'n' // flag
  100. OpOnlyTopic Mode = 't' // flag
  101. // RegisteredOnly mode is reused here from umode definition
  102. Secret Mode = 's' // flag
  103. UserLimit Mode = 'l' // flag arg
  104. NoCTCP Mode = 'C' // flag
  105. )
  106. var (
  107. ChannelFounder Mode = 'q' // arg
  108. ChannelAdmin Mode = 'a' // arg
  109. ChannelOperator Mode = 'o' // arg
  110. Halfop Mode = 'h' // arg
  111. Voice Mode = 'v' // arg
  112. // ChannelUserModes holds the list of all modes that can be applied to a user in a channel,
  113. // including Voice, in descending order of precedence
  114. ChannelUserModes = Modes{
  115. ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice,
  116. }
  117. ChannelModePrefixes = map[Mode]string{
  118. ChannelFounder: "~",
  119. ChannelAdmin: "&",
  120. ChannelOperator: "@",
  121. Halfop: "%",
  122. Voice: "+",
  123. }
  124. )
  125. //
  126. // channel membership prefixes
  127. //
  128. // SplitChannelMembershipPrefixes takes a target and returns the prefixes on it, then the name.
  129. func SplitChannelMembershipPrefixes(target string) (prefixes string, name string) {
  130. name = target
  131. for i := 0; i < len(name); i++ {
  132. switch name[i] {
  133. case '~', '&', '@', '%', '+':
  134. prefixes = target[:i+1]
  135. name = target[i+1:]
  136. default:
  137. return
  138. }
  139. }
  140. return
  141. }
  142. // GetLowestChannelModePrefix returns the lowest channel prefix mode out of the given prefixes.
  143. func GetLowestChannelModePrefix(prefixes string) (lowest Mode) {
  144. for i, mode := range ChannelUserModes {
  145. if strings.Contains(prefixes, ChannelModePrefixes[mode]) {
  146. lowest = ChannelUserModes[i]
  147. }
  148. }
  149. return
  150. }
  151. //
  152. // commands
  153. //
  154. // ParseUserModeChanges returns the valid changes, and the list of unknown chars.
  155. func ParseUserModeChanges(params ...string) (ModeChanges, map[rune]bool) {
  156. changes := make(ModeChanges, 0)
  157. unknown := make(map[rune]bool)
  158. op := List
  159. if 0 < len(params) {
  160. modeArg := params[0]
  161. skipArgs := 1
  162. for _, mode := range modeArg {
  163. if mode == '-' || mode == '+' {
  164. op = ModeOp(mode)
  165. continue
  166. }
  167. change := ModeChange{
  168. Mode: Mode(mode),
  169. Op: op,
  170. }
  171. // put arg into modechange if needed
  172. switch Mode(mode) {
  173. case ServerNotice:
  174. // always require arg
  175. if len(params) > skipArgs {
  176. change.Arg = params[skipArgs]
  177. skipArgs++
  178. } else {
  179. continue
  180. }
  181. }
  182. var isKnown bool
  183. for _, supportedMode := range SupportedUserModes {
  184. if rune(supportedMode) == mode {
  185. isKnown = true
  186. break
  187. }
  188. }
  189. if !isKnown {
  190. unknown[mode] = true
  191. continue
  192. }
  193. changes = append(changes, change)
  194. }
  195. }
  196. return changes, unknown
  197. }
  198. // ParseChannelModeChanges returns the valid changes, and the list of unknown chars.
  199. func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
  200. changes := make(ModeChanges, 0)
  201. unknown := make(map[rune]bool)
  202. op := List
  203. if 0 < len(params) {
  204. modeArg := params[0]
  205. skipArgs := 1
  206. for _, mode := range modeArg {
  207. if mode == '-' || mode == '+' {
  208. op = ModeOp(mode)
  209. continue
  210. }
  211. change := ModeChange{
  212. Mode: Mode(mode),
  213. Op: op,
  214. }
  215. // put arg into modechange if needed
  216. switch Mode(mode) {
  217. case BanMask, ExceptMask, InviteMask:
  218. if len(params) > skipArgs {
  219. change.Arg = params[skipArgs]
  220. skipArgs++
  221. } else {
  222. change.Op = List
  223. }
  224. case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice:
  225. if len(params) > skipArgs {
  226. change.Arg = params[skipArgs]
  227. skipArgs++
  228. } else {
  229. continue
  230. }
  231. case UserLimit:
  232. // don't require value when removing
  233. if change.Op == Add {
  234. if len(params) > skipArgs {
  235. change.Arg = params[skipArgs]
  236. skipArgs++
  237. } else {
  238. continue
  239. }
  240. }
  241. case Key:
  242. // #874: +k is technically a type B mode, requiring a parameter
  243. // both for add and remove. so attempt to consume a parameter,
  244. // but allow remove (but not add) even if no parameter is available.
  245. // however, the remove parameter should always display as "*", matching
  246. // the freenode behavior.
  247. if len(params) > skipArgs {
  248. if change.Op == Add {
  249. change.Arg = params[skipArgs]
  250. }
  251. skipArgs++
  252. } else if change.Op == Add {
  253. continue
  254. }
  255. if change.Op == Remove {
  256. change.Arg = "*"
  257. }
  258. }
  259. var isKnown bool
  260. for _, supportedMode := range SupportedChannelModes {
  261. if rune(supportedMode) == mode {
  262. isKnown = true
  263. break
  264. }
  265. }
  266. for _, supportedMode := range ChannelUserModes {
  267. if rune(supportedMode) == mode {
  268. isKnown = true
  269. break
  270. }
  271. }
  272. if !isKnown {
  273. unknown[mode] = true
  274. continue
  275. }
  276. changes = append(changes, change)
  277. }
  278. }
  279. return changes, unknown
  280. }
  281. // ModeSet holds a set of modes.
  282. type ModeSet [2]uint32
  283. // valid modes go from 65 ('A') to 122 ('z'), making at most 58 possible values;
  284. // subtract 65 from the mode value and use that bit of the uint32 to represent it
  285. const (
  286. minMode = 65 // 'A'
  287. maxMode = 122 // 'z'
  288. )
  289. // returns a pointer to a new ModeSet
  290. func NewModeSet() *ModeSet {
  291. var set ModeSet
  292. return &set
  293. }
  294. // test whether `mode` is set
  295. func (set *ModeSet) HasMode(mode Mode) bool {
  296. if set == nil {
  297. return false
  298. }
  299. return utils.BitsetGet(set[:], uint(mode)-minMode)
  300. }
  301. // set `mode` to be on or off, return whether the value actually changed
  302. func (set *ModeSet) SetMode(mode Mode, on bool) (applied bool) {
  303. return utils.BitsetSet(set[:], uint(mode)-minMode, on)
  304. }
  305. // copy the contents of another modeset on top of this one
  306. func (set *ModeSet) Copy(other *ModeSet) {
  307. utils.BitsetCopy(set[:], other[:])
  308. }
  309. // return the modes in the set as a slice
  310. func (set *ModeSet) AllModes() (result []Mode) {
  311. if set == nil {
  312. return
  313. }
  314. var i Mode
  315. for i = minMode; i <= maxMode; i++ {
  316. if set.HasMode(i) {
  317. result = append(result, i)
  318. }
  319. }
  320. return
  321. }
  322. // String returns the modes in this set.
  323. func (set *ModeSet) String() (result string) {
  324. if set == nil {
  325. return
  326. }
  327. var buf strings.Builder
  328. for _, mode := range set.AllModes() {
  329. buf.WriteRune(rune(mode))
  330. }
  331. return buf.String()
  332. }
  333. // Prefixes returns a list of prefixes for the given set of channel modes.
  334. func (set *ModeSet) Prefixes(isMultiPrefix bool) (prefixes string) {
  335. if set == nil {
  336. return
  337. }
  338. // add prefixes in order from highest to lowest privs
  339. for _, mode := range ChannelUserModes {
  340. if set.HasMode(mode) {
  341. prefixes += ChannelModePrefixes[mode]
  342. }
  343. }
  344. if !isMultiPrefix && len(prefixes) > 1 {
  345. prefixes = string(prefixes[0])
  346. }
  347. return prefixes
  348. }
  349. // HighestChannelUserMode returns the most privileged channel-user mode
  350. // (e.g., ChannelFounder, Halfop, Voice) present in the ModeSet.
  351. // If no such modes are present, or `set` is nil, returns the zero mode.
  352. func (set *ModeSet) HighestChannelUserMode() (result Mode) {
  353. for _, mode := range ChannelUserModes {
  354. if set.HasMode(mode) {
  355. return mode
  356. }
  357. }
  358. return
  359. }