|
@@ -8,18 +8,43 @@ package irc
|
8
|
8
|
import (
|
9
|
9
|
"fmt"
|
10
|
10
|
"strings"
|
|
11
|
+ "unicode"
|
11
|
12
|
|
12
|
13
|
"github.com/oragono/confusables"
|
13
|
14
|
"golang.org/x/text/cases"
|
14
|
15
|
"golang.org/x/text/language"
|
15
|
16
|
"golang.org/x/text/secure/precis"
|
|
17
|
+ "golang.org/x/text/unicode/norm"
|
16
|
18
|
"golang.org/x/text/width"
|
17
|
19
|
)
|
18
|
20
|
|
19
|
21
|
const (
|
20
|
|
- casemappingName = "rfc8265"
|
|
22
|
+ precisUTF8MappingToken = "rfc8265"
|
21
|
23
|
)
|
22
|
24
|
|
|
25
|
+type Casemapping uint
|
|
26
|
+
|
|
27
|
+const (
|
|
28
|
+ // "precis" is the default / zero value:
|
|
29
|
+ // casefolding/validation: PRECIS + ircd restrictions (like no *)
|
|
30
|
+ // confusables detection: standard skeleton algorithm
|
|
31
|
+ CasemappingPRECIS Casemapping = iota
|
|
32
|
+ // "ascii" is the traditional ircd behavior:
|
|
33
|
+ // casefolding/validation: must be pure ASCII and follow ircd restrictions, ASCII lowercasing
|
|
34
|
+ // confusables detection: none
|
|
35
|
+ CasemappingASCII
|
|
36
|
+ // "permissive" is an insecure mode:
|
|
37
|
+ // casefolding/validation: arbitrary unicodes that follow ircd restrictions, unicode casefolding
|
|
38
|
+ // confusables detection: standard skeleton algorithm (which may be ineffective
|
|
39
|
+ // over the larger set of permitted identifiers)
|
|
40
|
+ CasemappingPermissive
|
|
41
|
+)
|
|
42
|
+
|
|
43
|
+// XXX this is a global variable without explicit synchronization.
|
|
44
|
+// it gets set during the initial Server.applyConfig and cannot be changed by rehash:
|
|
45
|
+// this happens-before all IRC connections and all casefolding operations.
|
|
46
|
+var globalCasemappingSetting Casemapping = CasemappingPRECIS
|
|
47
|
+
|
23
|
48
|
// Each pass of PRECIS casefolding is a composition of idempotent operations,
|
24
|
49
|
// but not idempotent itself. Therefore, the spec says "do it four times and hope
|
25
|
50
|
// it converges" (lolwtf). Golang's PRECIS implementation has a "repeat" option,
|
|
@@ -46,7 +71,14 @@ func iterateFolding(profile *precis.Profile, oldStr string) (str string, err err
|
46
|
71
|
|
47
|
72
|
// Casefold returns a casefolded string, without doing any name or channel character checks.
|
48
|
73
|
func Casefold(str string) (string, error) {
|
49
|
|
- return iterateFolding(precis.UsernameCaseMapped, str)
|
|
74
|
+ switch globalCasemappingSetting {
|
|
75
|
+ default:
|
|
76
|
+ return iterateFolding(precis.UsernameCaseMapped, str)
|
|
77
|
+ case CasemappingASCII:
|
|
78
|
+ return foldASCII(str)
|
|
79
|
+ case CasemappingPermissive:
|
|
80
|
+ return foldPermissive(str)
|
|
81
|
+ }
|
50
|
82
|
}
|
51
|
83
|
|
52
|
84
|
// CasefoldChannel returns a casefolded version of a channel name.
|
|
@@ -144,6 +176,16 @@ func isIdent(name string) bool {
|
144
|
176
|
// from the original (unfolded) identifier and stored/tracked separately from the
|
145
|
177
|
// casefolded identifier.
|
146
|
178
|
func Skeleton(name string) (string, error) {
|
|
179
|
+ switch globalCasemappingSetting {
|
|
180
|
+ default:
|
|
181
|
+ return realSkeleton(name)
|
|
182
|
+ case CasemappingASCII:
|
|
183
|
+ // identity function is fine because we independently case-normalize in Casefold
|
|
184
|
+ return name, nil
|
|
185
|
+ }
|
|
186
|
+}
|
|
187
|
+
|
|
188
|
+func realSkeleton(name string) (string, error) {
|
147
|
189
|
// XXX the confusables table includes some, but not all, fullwidth->standard
|
148
|
190
|
// mappings for latin characters. do a pass of explicit width folding,
|
149
|
191
|
// same as PRECIS:
|
|
@@ -212,3 +254,27 @@ func CanonicalizeMaskWildcard(userhost string) (expanded string, err error) {
|
212
|
254
|
}
|
213
|
255
|
return fmt.Sprintf("%s!%s@%s", nick, user, host), nil
|
214
|
256
|
}
|
|
257
|
+
|
|
258
|
+func foldASCII(str string) (result string, err error) {
|
|
259
|
+ if !IsPureASCII(str) {
|
|
260
|
+ return "", errInvalidCharacter
|
|
261
|
+ }
|
|
262
|
+ return strings.ToLower(str), nil
|
|
263
|
+}
|
|
264
|
+
|
|
265
|
+func IsPureASCII(str string) bool {
|
|
266
|
+ for i := 0; i < len(str); i++ {
|
|
267
|
+ if unicode.MaxASCII < str[i] {
|
|
268
|
+ return false
|
|
269
|
+ }
|
|
270
|
+ }
|
|
271
|
+ return true
|
|
272
|
+}
|
|
273
|
+
|
|
274
|
+func foldPermissive(str string) (result string, err error) {
|
|
275
|
+ // YOLO
|
|
276
|
+ str = norm.NFD.String(str)
|
|
277
|
+ str = cases.Fold().String(str)
|
|
278
|
+ str = norm.NFD.String(str)
|
|
279
|
+ return str, nil
|
|
280
|
+}
|