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
6package modes
7
8import (
9	"fmt"
10	"sort"
11	"strings"
12
13	"github.com/ergochat/ergo/irc/utils"
14)
15
16var (
17	// SupportedUserModes are the user modes that we actually support (modifying).
18	SupportedUserModes = Modes{
19		Bot, Invisible, Operator, RegisteredOnly, ServerNotice, UserRoleplaying,
20		UserNoCTCP,
21	}
22
23	// SupportedChannelModes are the channel modes that we support.
24	SupportedChannelModes = Modes{
25		BanMask, ChanRoleplaying, ExceptMask, InviteMask, InviteOnly, Key,
26		Moderated, NoOutside, OpOnlyTopic, RegisteredOnly, RegisteredOnlySpeak,
27		Secret, UserLimit, NoCTCP, Auditorium, OpModerated, Forward,
28	}
29)
30
31// ModeOp is an operation performed with modes
32type ModeOp rune
33
34const (
35	// Add is used when adding the given key.
36	Add ModeOp = '+'
37	// List is used when listing modes (for instance, listing the current bans on a channel).
38	List ModeOp = '='
39	// Remove is used when taking away the given key.
40	Remove ModeOp = '-'
41)
42
43// Mode represents a user/channel/server mode
44type Mode rune
45
46func (mode Mode) String() string {
47	return string(mode)
48}
49
50// ModeChange is a single mode changing
51type ModeChange struct {
52	Mode Mode
53	Op   ModeOp
54	Arg  string
55}
56
57// ModeChanges are a collection of 'ModeChange's
58type ModeChanges []ModeChange
59
60func (changes ModeChanges) Strings() (result []string) {
61	if len(changes) == 0 {
62		return
63	}
64
65	var builder strings.Builder
66
67	op := changes[0].Op
68	builder.WriteRune(rune(op))
69
70	for _, change := range changes {
71		if change.Op != op {
72			op = change.Op
73			builder.WriteRune(rune(op))
74		}
75		builder.WriteRune(rune(change.Mode))
76	}
77
78	result = append(result, builder.String())
79
80	for _, change := range changes {
81		if change.Arg == "" {
82			continue
83		}
84		result = append(result, change.Arg)
85	}
86	return
87}
88
89// Modes is just a raw list of modes
90type Modes []Mode
91
92func (modes Modes) String() string {
93	var builder strings.Builder
94	for _, m := range modes {
95		builder.WriteRune(rune(m))
96	}
97	return builder.String()
98}
99
100// User Modes
101const (
102	Bot             Mode = 'B'
103	Invisible       Mode = 'i'
104	Operator        Mode = 'o'
105	Restricted      Mode = 'r'
106	RegisteredOnly  Mode = 'R'
107	ServerNotice    Mode = 's'
108	TLS             Mode = 'Z'
109	UserNoCTCP      Mode = 'T'
110	UserRoleplaying Mode = 'E'
111	WallOps         Mode = 'w'
112)
113
114// Channel Modes
115const (
116	Auditorium      Mode = 'u' // flag
117	BanMask         Mode = 'b' // arg
118	ChanRoleplaying Mode = 'E' // flag
119	ExceptMask      Mode = 'e' // arg
120	InviteMask      Mode = 'I' // arg
121	InviteOnly      Mode = 'i' // flag
122	Key             Mode = 'k' // flag arg
123	Moderated       Mode = 'm' // flag
124	NoOutside       Mode = 'n' // flag
125	OpOnlyTopic     Mode = 't' // flag
126	// RegisteredOnly mode is reused here from umode definition
127	RegisteredOnlySpeak Mode = 'M' // flag
128	Secret              Mode = 's' // flag
129	UserLimit           Mode = 'l' // flag arg
130	NoCTCP              Mode = 'C' // flag
131	OpModerated         Mode = 'U' // flag
132	Forward             Mode = 'f' // flag arg
133)
134
135var (
136	ChannelFounder  Mode = 'q' // arg
137	ChannelAdmin    Mode = 'a' // arg
138	ChannelOperator Mode = 'o' // arg
139	Halfop          Mode = 'h' // arg
140	Voice           Mode = 'v' // arg
141
142	// ChannelUserModes holds the list of all modes that can be applied to a user in a channel,
143	// including Voice, in descending order of precedence
144	ChannelUserModes = Modes{
145		ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice,
146	}
147
148	ChannelModePrefixes = map[Mode]string{
149		ChannelFounder:  "~",
150		ChannelAdmin:    "&",
151		ChannelOperator: "@",
152		Halfop:          "%",
153		Voice:           "+",
154	}
155)
156
157//
158// channel membership prefixes
159//
160
161// SplitChannelMembershipPrefixes takes a target and returns the prefixes on it, then the name.
162func SplitChannelMembershipPrefixes(target string) (prefixes string, name string) {
163	name = target
164	for i := 0; i < len(name); i++ {
165		switch name[i] {
166		case '~', '&', '@', '%', '+':
167			prefixes = target[:i+1]
168			name = target[i+1:]
169		default:
170			return
171		}
172	}
173
174	return
175}
176
177// GetLowestChannelModePrefix returns the lowest channel prefix mode out of the given prefixes.
178func GetLowestChannelModePrefix(prefixes string) (lowest Mode) {
179	for i, mode := range ChannelUserModes {
180		if strings.Contains(prefixes, ChannelModePrefixes[mode]) {
181			lowest = ChannelUserModes[i]
182		}
183	}
184	return
185}
186
187//
188// commands
189//
190
191// ParseUserModeChanges returns the valid changes, and the list of unknown chars.
192func ParseUserModeChanges(params ...string) (ModeChanges, map[rune]bool) {
193	changes := make(ModeChanges, 0)
194	unknown := make(map[rune]bool)
195
196	op := List
197
198	if 0 < len(params) {
199		modeArg := params[0]
200		skipArgs := 1
201
202		for _, mode := range modeArg {
203			if mode == '-' || mode == '+' {
204				op = ModeOp(mode)
205				continue
206			}
207			change := ModeChange{
208				Mode: Mode(mode),
209				Op:   op,
210			}
211
212			// put arg into modechange if needed
213			switch Mode(mode) {
214			case ServerNotice:
215				// arg is optional for ServerNotice (we accept bare `-s`)
216				if len(params) > skipArgs {
217					change.Arg = params[skipArgs]
218					skipArgs++
219				}
220			}
221
222			var isKnown bool
223			for _, supportedMode := range SupportedUserModes {
224				if rune(supportedMode) == mode {
225					isKnown = true
226					break
227				}
228			}
229			if !isKnown {
230				unknown[mode] = true
231				continue
232			}
233
234			changes = append(changes, change)
235		}
236	}
237
238	return changes, unknown
239}
240
241// ParseChannelModeChanges returns the valid changes, and the list of unknown chars.
242func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
243	changes := make(ModeChanges, 0)
244	unknown := make(map[rune]bool)
245
246	op := List
247
248	if 0 < len(params) {
249		modeArg := params[0]
250		skipArgs := 1
251
252		for _, mode := range modeArg {
253			if mode == '-' || mode == '+' {
254				op = ModeOp(mode)
255				continue
256			}
257			change := ModeChange{
258				Mode: Mode(mode),
259				Op:   op,
260			}
261
262			// put arg into modechange if needed
263			switch Mode(mode) {
264			case BanMask, ExceptMask, InviteMask:
265				if len(params) > skipArgs {
266					change.Arg = params[skipArgs]
267					skipArgs++
268				} else {
269					change.Op = List
270				}
271			case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice:
272				if len(params) > skipArgs {
273					change.Arg = params[skipArgs]
274					skipArgs++
275				} else {
276					continue
277				}
278			case UserLimit, Forward:
279				// don't require value when removing
280				if change.Op == Add {
281					if len(params) > skipArgs {
282						change.Arg = params[skipArgs]
283						skipArgs++
284					} else {
285						continue
286					}
287				}
288			case Key:
289				// #874: +k is technically a type B mode, requiring a parameter
290				// both for add and remove. so attempt to consume a parameter,
291				// but allow remove (but not add) even if no parameter is available.
292				// however, the remove parameter should always display as "*", matching
293				// the freenode behavior.
294				if len(params) > skipArgs {
295					if change.Op == Add {
296						change.Arg = params[skipArgs]
297					}
298					skipArgs++
299				} else if change.Op == Add {
300					continue
301				}
302				if change.Op == Remove {
303					change.Arg = "*"
304				}
305			}
306
307			var isKnown bool
308			for _, supportedMode := range SupportedChannelModes {
309				if rune(supportedMode) == mode {
310					isKnown = true
311					break
312				}
313			}
314			for _, supportedMode := range ChannelUserModes {
315				if rune(supportedMode) == mode {
316					isKnown = true
317					break
318				}
319			}
320			if !isKnown {
321				unknown[mode] = true
322				continue
323			}
324
325			changes = append(changes, change)
326		}
327	}
328
329	return changes, unknown
330}
331
332// ModeSet holds a set of modes.
333type ModeSet [2]uint32
334
335// valid modes go from 65 ('A') to 122 ('z'), making at most 58 possible values;
336// subtract 65 from the mode value and use that bit of the uint32 to represent it
337const (
338	minMode = 65  // 'A'
339	maxMode = 122 // 'z'
340)
341
342// returns a pointer to a new ModeSet
343func NewModeSet() *ModeSet {
344	var set ModeSet
345	return &set
346}
347
348// test whether `mode` is set
349func (set *ModeSet) HasMode(mode Mode) bool {
350	if set == nil {
351		return false
352	}
353
354	return utils.BitsetGet(set[:], uint(mode)-minMode)
355}
356
357// set `mode` to be on or off, return whether the value actually changed
358func (set *ModeSet) SetMode(mode Mode, on bool) (applied bool) {
359	return utils.BitsetSet(set[:], uint(mode)-minMode, on)
360}
361
362// copy the contents of another modeset on top of this one
363func (set *ModeSet) Copy(other *ModeSet) {
364	utils.BitsetCopy(set[:], other[:])
365}
366
367// return the modes in the set as a slice
368func (set *ModeSet) AllModes() (result []Mode) {
369	if set == nil {
370		return
371	}
372
373	var i Mode
374	for i = minMode; i <= maxMode; i++ {
375		if set.HasMode(i) {
376			result = append(result, i)
377		}
378	}
379	return
380}
381
382// String returns the modes in this set.
383func (set *ModeSet) String() (result string) {
384	if set == nil {
385		return
386	}
387
388	var buf strings.Builder
389	for _, mode := range set.AllModes() {
390		buf.WriteRune(rune(mode))
391	}
392	return buf.String()
393}
394
395// Prefixes returns a list of prefixes for the given set of channel modes.
396func (set *ModeSet) Prefixes(isMultiPrefix bool) (prefixes string) {
397	if set == nil {
398		return
399	}
400
401	// add prefixes in order from highest to lowest privs
402	for _, mode := range ChannelUserModes {
403		if set.HasMode(mode) {
404			prefixes += ChannelModePrefixes[mode]
405		}
406	}
407
408	if !isMultiPrefix && len(prefixes) > 1 {
409		prefixes = string(prefixes[0])
410	}
411
412	return prefixes
413}
414
415// HighestChannelUserMode returns the most privileged channel-user mode
416// (e.g., ChannelFounder, Halfop, Voice) present in the ModeSet.
417// If no such modes are present, or `set` is nil, returns the zero mode.
418func (set *ModeSet) HighestChannelUserMode() (result Mode) {
419	for _, mode := range ChannelUserModes {
420		if set.HasMode(mode) {
421			return mode
422		}
423	}
424	return
425}
426
427type ByCodepoint Modes
428
429func (a ByCodepoint) Len() int           { return len(a) }
430func (a ByCodepoint) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
431func (a ByCodepoint) Less(i, j int) bool { return a[i] < a[j] }
432
433func RplMyInfo() (param1, param2, param3 string) {
434	userModes := make(Modes, len(SupportedUserModes), len(SupportedUserModes)+1)
435	copy(userModes, SupportedUserModes)
436	// TLS is not in SupportedUserModes because it can't be modified
437	userModes = append(userModes, TLS)
438	sort.Sort(ByCodepoint(userModes))
439
440	channelModes := make(Modes, len(SupportedChannelModes)+len(ChannelUserModes))
441	copy(channelModes, SupportedChannelModes)
442	copy(channelModes[len(SupportedChannelModes):], ChannelUserModes)
443	sort.Sort(ByCodepoint(channelModes))
444
445	// XXX enumerate these by hand, i can't see any way to DRY this
446	channelParametrizedModes := Modes{BanMask, ExceptMask, InviteMask, Key, UserLimit, Forward}
447	channelParametrizedModes = append(channelParametrizedModes, ChannelUserModes...)
448	sort.Sort(ByCodepoint(channelParametrizedModes))
449
450	return userModes.String(), channelModes.String(), channelParametrizedModes.String()
451}
452
453func ChanmodesToken() (result string) {
454	// https://modern.ircdocs.horse#chanmodes-parameter
455	// type A: listable modes with parameters
456	A := Modes{BanMask, ExceptMask, InviteMask}
457	// type B: modes with parameters
458	B := Modes{Key}
459	// type C: modes that take a parameter only when set, never when unset
460	C := Modes{UserLimit, Forward}
461	// type D: modes without parameters
462	D := Modes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, ChanRoleplaying, Secret, NoCTCP, RegisteredOnly, RegisteredOnlySpeak, Auditorium, OpModerated}
463
464	sort.Sort(ByCodepoint(A))
465	sort.Sort(ByCodepoint(B))
466	sort.Sort(ByCodepoint(C))
467	sort.Sort(ByCodepoint(D))
468
469	return fmt.Sprintf("%s,%s,%s,%s", A.String(), B.String(), C.String(), D.String())
470}
471