@@ -2,53 +2,164 @@ package bidichk
2
2
3
3
import (
4
4
"bytes"
5
+ "flag"
6
+ "fmt"
5
7
"go/token"
6
8
"os"
9
+ "sort"
7
10
"strings"
8
11
"unicode/utf8"
9
12
10
13
"golang.org/x/tools/go/analysis"
11
14
)
12
15
13
- var Analyzer = & analysis.Analyzer {
14
- Name : "bidichk" ,
15
- Doc : "Checks for dangerous unicode character sequences" ,
16
- Run : run ,
16
+ const (
17
+ doc = "bidichk detects dangerous unicode character sequences"
18
+ disallowedDoc = `coma separated list of disallowed runes (full name or short name)
19
+
20
+ Supported runes
21
+
22
+ LEFT-TO-RIGHT-EMBEDDING, LRE (u+202A)
23
+ RIGHT-TO-LEFT-EMBEDDING, RLE (u+202B)
24
+ POP-DIRECTIONAL-FORMATTING, PDF (u+202C)
25
+ LEFT-TO-RIGHT-OVERRIDE, LRO (u+202D)
26
+ RIGHT-TO-LEFT-OVERRIDE, RLO (u+202E)
27
+ LEFT-TO-RIGHT-ISOLATE, LRI (u+2066)
28
+ RIGHT-TO-LEFT-ISOLATE, RLI (u+2067)
29
+ FIRST-STRONG-ISOLATE, FSI (u+2068)
30
+ POP-DIRECTIONAL-ISOLATE, PDI (u+2069)
31
+ `
32
+ )
33
+
34
+ type disallowedRunes map [string ]rune
35
+
36
+ func (m disallowedRunes ) String () string {
37
+ ss := make ([]string , 0 , len (m ))
38
+ for s := range m {
39
+ ss = append (ss , s )
40
+ }
41
+ sort .Strings (ss )
42
+ return strings .Join (ss , "," )
17
43
}
18
44
19
- func run (pass * analysis.Pass ) (interface {}, error ) {
45
+ func (m disallowedRunes ) Set (s string ) error {
46
+ ss := strings .FieldsFunc (s , func (c rune ) bool { return c == ',' })
47
+ if len (ss ) == 0 {
48
+ return nil
49
+ }
50
+
51
+ for k := range m {
52
+ delete (m , k )
53
+ }
54
+
55
+ for _ , v := range ss {
56
+ switch v {
57
+ case runeShortNameLRE , runeShortNameRLE , runeShortNamePDF ,
58
+ runeShortNameLRO , runeShortNameRLO , runeShortNameLRI ,
59
+ runeShortNameRLI , runeShortNameFSI , runeShortNamePDI :
60
+ v = shortNameLookup [v ]
61
+ fallthrough
62
+ case runeNameLRE , runeNameRLE , runeNamePDF ,
63
+ runeNameLRO , runeNameRLO , runeNameLRI ,
64
+ runeNameRLI , runeNameFSI , runeNamePDI :
65
+ m [v ] = runeLookup [v ]
66
+ default :
67
+ return fmt .Errorf ("unknown check name %q (see help for full list)" , v )
68
+ }
69
+ }
70
+ return nil
71
+ }
72
+
73
+ const (
74
+ runeNameLRE = "LEFT-TO-RIGHT-EMBEDDING"
75
+ runeNameRLE = "RIGHT-TO-LEFT-EMBEDDING"
76
+ runeNamePDF = "POP-DIRECTIONAL-FORMATTING"
77
+ runeNameLRO = "LEFT-TO-RIGHT-OVERRIDE"
78
+ runeNameRLO = "RIGHT-TO-LEFT-OVERRIDE"
79
+ runeNameLRI = "LEFT-TO-RIGHT-ISOLATE"
80
+ runeNameRLI = "RIGHT-TO-LEFT-ISOLATE"
81
+ runeNameFSI = "FIRST-STRONG-ISOLATE"
82
+ runeNamePDI = "POP-DIRECTIONAL-ISOLATE"
83
+
84
+ runeShortNameLRE = "LRE" // LEFT-TO-RIGHT-EMBEDDING
85
+ runeShortNameRLE = "RLE" // RIGHT-TO-LEFT-EMBEDDING
86
+ runeShortNamePDF = "PDF" // POP-DIRECTIONAL-FORMATTING
87
+ runeShortNameLRO = "LRO" // LEFT-TO-RIGHT-OVERRIDE
88
+ runeShortNameRLO = "RLO" // RIGHT-TO-LEFT-OVERRIDE
89
+ runeShortNameLRI = "LRI" // LEFT-TO-RIGHT-ISOLATE
90
+ runeShortNameRLI = "RLI" // RIGHT-TO-LEFT-ISOLATE
91
+ runeShortNameFSI = "FSI" // FIRST-STRONG-ISOLATE
92
+ runeShortNamePDI = "PDI" // POP-DIRECTIONAL-ISOLATE
93
+ )
94
+
95
+ var runeLookup = map [string ]rune {
96
+ runeNameLRE : '\u202A' , // LEFT-TO-RIGHT-EMBEDDING
97
+ runeNameRLE : '\u202B' , // RIGHT-TO-LEFT-EMBEDDING
98
+ runeNamePDF : '\u202C' , // POP-DIRECTIONAL-FORMATTING
99
+ runeNameLRO : '\u202D' , // LEFT-TO-RIGHT-OVERRIDE
100
+ runeNameRLO : '\u202E' , // RIGHT-TO-LEFT-OVERRIDE
101
+ runeNameLRI : '\u2066' , // LEFT-TO-RIGHT-ISOLATE
102
+ runeNameRLI : '\u2067' , // RIGHT-TO-LEFT-ISOLATE
103
+ runeNameFSI : '\u2068' , // FIRST-STRONG-ISOLATE
104
+ runeNamePDI : '\u2069' , // POP-DIRECTIONAL-ISOLATE
105
+ }
106
+
107
+ var shortNameLookup = map [string ]string {
108
+ runeShortNameLRE : runeNameLRE ,
109
+ runeShortNameRLE : runeNameRLE ,
110
+ runeShortNamePDF : runeNamePDF ,
111
+ runeShortNameLRO : runeNameLRO ,
112
+ runeShortNameRLO : runeNameRLO ,
113
+ runeShortNameLRI : runeNameLRI ,
114
+ runeShortNameRLI : runeNameRLI ,
115
+ runeShortNameFSI : runeNameFSI ,
116
+ runeShortNamePDI : runeNamePDI ,
117
+ }
118
+
119
+ type bidichk struct {
120
+ disallowedRunes disallowedRunes
121
+ }
122
+
123
+ func NewAnalyzer () * analysis.Analyzer {
124
+ bidichk := bidichk {}
125
+ bidichk .disallowedRunes = make (map [string ]rune , len (runeLookup ))
126
+ for k , v := range runeLookup {
127
+ bidichk .disallowedRunes [k ] = v
128
+ }
129
+
130
+ a := & analysis.Analyzer {
131
+ Name : "bidichk" ,
132
+ Doc : doc ,
133
+ Run : bidichk .run ,
134
+ }
135
+
136
+ a .Flags .Init ("bidichk" , flag .ExitOnError )
137
+ a .Flags .Var (& bidichk .disallowedRunes , "disallowed-runes" , disallowedDoc )
138
+
139
+ return a
140
+ }
141
+
142
+ func (b bidichk ) run (pass * analysis.Pass ) (interface {}, error ) {
20
143
var err error
21
144
22
145
pass .Fset .Iterate (func (f * token.File ) bool {
23
146
if strings .HasPrefix (f .Name (), "$GOROOT" ) {
24
147
return true
25
148
}
26
149
27
- return check (f .Name (), f .Pos (0 ), pass ) == nil
150
+ return b . check (f .Name (), f .Pos (0 ), pass ) == nil
28
151
})
29
152
30
153
return nil , err
31
154
}
32
155
33
- var disallowedRunes = map [string ]rune {
34
- "LEFT-TO-RIGHT-EMBEDDING" : '\u202A' ,
35
- "RIGHT-TO-LEFT-EMBEDDING" : '\u202B' ,
36
- "POP-DIRECTIONAL-FORMATTING" : '\u202C' ,
37
- "LEFT-TO-RIGHT-OVERRIDE" : '\u202D' ,
38
- "RIGHT-TO-LEFT-OVERRIDE" : '\u202E' ,
39
- "LEFT-TO-RIGHT-ISOLATE" : '\u2066' ,
40
- "RIGHT-TO-LEFT-ISOLATE" : '\u2067' ,
41
- "FIRST-STRONG-ISOLATE" : '\u2068' ,
42
- "POP-DIRECTIONAL-ISOLATE" : '\u2069' ,
43
- }
44
-
45
- func check (filename string , pos token.Pos , pass * analysis.Pass ) error {
156
+ func (b bidichk ) check (filename string , pos token.Pos , pass * analysis.Pass ) error {
46
157
body , err := os .ReadFile (filename )
47
158
if err != nil {
48
159
return err
49
160
}
50
161
51
- for name , r := range disallowedRunes {
162
+ for name , r := range b . disallowedRunes {
52
163
start := 0
53
164
for {
54
165
idx := bytes .IndexRune (body [start :], r )
0 commit comments