OSDN Git Service

new repo
[bytom/vapor.git] / vendor / golang.org / x / text / secure / bidirule / bidirule.go
1 // Copyright 2016 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Package bidirule implements the Bidi Rule defined by RFC 5893.
6 //
7 // This package is under development. The API may change without notice and
8 // without preserving backward compatibility.
9 package bidirule
10
11 import (
12         "errors"
13         "unicode/utf8"
14
15         "golang.org/x/text/transform"
16         "golang.org/x/text/unicode/bidi"
17 )
18
19 // This file contains an implementation of RFC 5893: Right-to-Left Scripts for
20 // Internationalized Domain Names for Applications (IDNA)
21 //
22 // A label is an individual component of a domain name.  Labels are usually
23 // shown separated by dots; for example, the domain name "www.example.com" is
24 // composed of three labels: "www", "example", and "com".
25 //
26 // An RTL label is a label that contains at least one character of class R, AL,
27 // or AN. An LTR label is any label that is not an RTL label.
28 //
29 // A "Bidi domain name" is a domain name that contains at least one RTL label.
30 //
31 //  The following guarantees can be made based on the above:
32 //
33 //  o  In a domain name consisting of only labels that satisfy the rule,
34 //     the requirements of Section 3 are satisfied.  Note that even LTR
35 //     labels and pure ASCII labels have to be tested.
36 //
37 //  o  In a domain name consisting of only LDH labels (as defined in the
38 //     Definitions document [RFC5890]) and labels that satisfy the rule,
39 //     the requirements of Section 3 are satisfied as long as a label
40 //     that starts with an ASCII digit does not come after a
41 //     right-to-left label.
42 //
43 //  No guarantee is given for other combinations.
44
45 // ErrInvalid indicates a label is invalid according to the Bidi Rule.
46 var ErrInvalid = errors.New("bidirule: failed Bidi Rule")
47
48 type ruleState uint8
49
50 const (
51         ruleInitial ruleState = iota
52         ruleLTR
53         ruleLTRFinal
54         ruleRTL
55         ruleRTLFinal
56         ruleInvalid
57 )
58
59 type ruleTransition struct {
60         next ruleState
61         mask uint16
62 }
63
64 var transitions = [...][2]ruleTransition{
65         // [2.1] The first character must be a character with Bidi property L, R, or
66         // AL. If it has the R or AL property, it is an RTL label; if it has the L
67         // property, it is an LTR label.
68         ruleInitial: {
69                 {ruleLTRFinal, 1 << bidi.L},
70                 {ruleRTLFinal, 1<<bidi.R | 1<<bidi.AL},
71         },
72         ruleRTL: {
73                 // [2.3] In an RTL label, the end of the label must be a character with
74                 // Bidi property R, AL, EN, or AN, followed by zero or more characters
75                 // with Bidi property NSM.
76                 {ruleRTLFinal, 1<<bidi.R | 1<<bidi.AL | 1<<bidi.EN | 1<<bidi.AN},
77
78                 // [2.2] In an RTL label, only characters with the Bidi properties R,
79                 // AL, AN, EN, ES, CS, ET, ON, BN, or NSM are allowed.
80                 // We exclude the entries from [2.3]
81                 {ruleRTL, 1<<bidi.ES | 1<<bidi.CS | 1<<bidi.ET | 1<<bidi.ON | 1<<bidi.BN | 1<<bidi.NSM},
82         },
83         ruleRTLFinal: {
84                 // [2.3] In an RTL label, the end of the label must be a character with
85                 // Bidi property R, AL, EN, or AN, followed by zero or more characters
86                 // with Bidi property NSM.
87                 {ruleRTLFinal, 1<<bidi.R | 1<<bidi.AL | 1<<bidi.EN | 1<<bidi.AN | 1<<bidi.NSM},
88
89                 // [2.2] In an RTL label, only characters with the Bidi properties R,
90                 // AL, AN, EN, ES, CS, ET, ON, BN, or NSM are allowed.
91                 // We exclude the entries from [2.3] and NSM.
92                 {ruleRTL, 1<<bidi.ES | 1<<bidi.CS | 1<<bidi.ET | 1<<bidi.ON | 1<<bidi.BN},
93         },
94         ruleLTR: {
95                 // [2.6] In an LTR label, the end of the label must be a character with
96                 // Bidi property L or EN, followed by zero or more characters with Bidi
97                 // property NSM.
98                 {ruleLTRFinal, 1<<bidi.L | 1<<bidi.EN},
99
100                 // [2.5] In an LTR label, only characters with the Bidi properties L,
101                 // EN, ES, CS, ET, ON, BN, or NSM are allowed.
102                 // We exclude the entries from [2.6].
103                 {ruleLTR, 1<<bidi.ES | 1<<bidi.CS | 1<<bidi.ET | 1<<bidi.ON | 1<<bidi.BN | 1<<bidi.NSM},
104         },
105         ruleLTRFinal: {
106                 // [2.6] In an LTR label, the end of the label must be a character with
107                 // Bidi property L or EN, followed by zero or more characters with Bidi
108                 // property NSM.
109                 {ruleLTRFinal, 1<<bidi.L | 1<<bidi.EN | 1<<bidi.NSM},
110
111                 // [2.5] In an LTR label, only characters with the Bidi properties L,
112                 // EN, ES, CS, ET, ON, BN, or NSM are allowed.
113                 // We exclude the entries from [2.6].
114                 {ruleLTR, 1<<bidi.ES | 1<<bidi.CS | 1<<bidi.ET | 1<<bidi.ON | 1<<bidi.BN},
115         },
116         ruleInvalid: {
117                 {ruleInvalid, 0},
118                 {ruleInvalid, 0},
119         },
120 }
121
122 // [2.4] In an RTL label, if an EN is present, no AN may be present, and
123 // vice versa.
124 const exclusiveRTL = uint16(1<<bidi.EN | 1<<bidi.AN)
125
126 // From RFC 5893
127 // An RTL label is a label that contains at least one character of type
128 // R, AL, or AN.
129 //
130 // An LTR label is any label that is not an RTL label.
131
132 // Direction reports the direction of the given label as defined by RFC 5893.
133 // The Bidi Rule does not have to be applied to labels of the category
134 // LeftToRight.
135 func Direction(b []byte) bidi.Direction {
136         for i := 0; i < len(b); {
137                 e, sz := bidi.Lookup(b[i:])
138                 if sz == 0 {
139                         i++
140                 }
141                 c := e.Class()
142                 if c == bidi.R || c == bidi.AL || c == bidi.AN {
143                         return bidi.RightToLeft
144                 }
145                 i += sz
146         }
147         return bidi.LeftToRight
148 }
149
150 // DirectionString reports the direction of the given label as defined by RFC
151 // 5893. The Bidi Rule does not have to be applied to labels of the category
152 // LeftToRight.
153 func DirectionString(s string) bidi.Direction {
154         for i := 0; i < len(s); {
155                 e, sz := bidi.LookupString(s[i:])
156                 if sz == 0 {
157                         i++
158                 }
159                 c := e.Class()
160                 if c == bidi.R || c == bidi.AL || c == bidi.AN {
161                         return bidi.RightToLeft
162                 }
163                 i += sz
164         }
165         return bidi.LeftToRight
166 }
167
168 // Valid reports whether b conforms to the BiDi rule.
169 func Valid(b []byte) bool {
170         var t Transformer
171         if n, ok := t.advance(b); !ok || n < len(b) {
172                 return false
173         }
174         return t.isFinal()
175 }
176
177 // ValidString reports whether s conforms to the BiDi rule.
178 func ValidString(s string) bool {
179         var t Transformer
180         if n, ok := t.advanceString(s); !ok || n < len(s) {
181                 return false
182         }
183         return t.isFinal()
184 }
185
186 // New returns a Transformer that verifies that input adheres to the Bidi Rule.
187 func New() *Transformer {
188         return &Transformer{}
189 }
190
191 // Transformer implements transform.Transform.
192 type Transformer struct {
193         state  ruleState
194         hasRTL bool
195         seen   uint16
196 }
197
198 // A rule can only be violated for "Bidi Domain names", meaning if one of the
199 // following categories has been observed.
200 func (t *Transformer) isRTL() bool {
201         const isRTL = 1<<bidi.R | 1<<bidi.AL | 1<<bidi.AN
202         return t.seen&isRTL != 0
203 }
204
205 func (t *Transformer) isFinal() bool {
206         if !t.isRTL() {
207                 return true
208         }
209         return t.state == ruleLTRFinal || t.state == ruleRTLFinal || t.state == ruleInitial
210 }
211
212 // Reset implements transform.Transformer.
213 func (t *Transformer) Reset() { *t = Transformer{} }
214
215 // Transform implements transform.Transformer. This Transformer has state and
216 // needs to be reset between uses.
217 func (t *Transformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
218         if len(dst) < len(src) {
219                 src = src[:len(dst)]
220                 atEOF = false
221                 err = transform.ErrShortDst
222         }
223         n, err1 := t.Span(src, atEOF)
224         copy(dst, src[:n])
225         if err == nil || err1 != nil && err1 != transform.ErrShortSrc {
226                 err = err1
227         }
228         return n, n, err
229 }
230
231 // Span returns the first n bytes of src that conform to the Bidi rule.
232 func (t *Transformer) Span(src []byte, atEOF bool) (n int, err error) {
233         if t.state == ruleInvalid && t.isRTL() {
234                 return 0, ErrInvalid
235         }
236         n, ok := t.advance(src)
237         switch {
238         case !ok:
239                 err = ErrInvalid
240         case n < len(src):
241                 if !atEOF {
242                         err = transform.ErrShortSrc
243                         break
244                 }
245                 err = ErrInvalid
246         case !t.isFinal():
247                 err = ErrInvalid
248         }
249         return n, err
250 }
251
252 // Precomputing the ASCII values decreases running time for the ASCII fast path
253 // by about 30%.
254 var asciiTable [128]bidi.Properties
255
256 func init() {
257         for i := range asciiTable {
258                 p, _ := bidi.LookupRune(rune(i))
259                 asciiTable[i] = p
260         }
261 }
262
263 func (t *Transformer) advance(s []byte) (n int, ok bool) {
264         var e bidi.Properties
265         var sz int
266         for n < len(s) {
267                 if s[n] < utf8.RuneSelf {
268                         e, sz = asciiTable[s[n]], 1
269                 } else {
270                         e, sz = bidi.Lookup(s[n:])
271                         if sz <= 1 {
272                                 if sz == 1 {
273                                         // We always consider invalid UTF-8 to be invalid, even if
274                                         // the string has not yet been determined to be RTL.
275                                         // TODO: is this correct?
276                                         return n, false
277                                 }
278                                 return n, true // incomplete UTF-8 encoding
279                         }
280                 }
281                 // TODO: using CompactClass would result in noticeable speedup.
282                 // See unicode/bidi/prop.go:Properties.CompactClass.
283                 c := uint16(1 << e.Class())
284                 t.seen |= c
285                 if t.seen&exclusiveRTL == exclusiveRTL {
286                         t.state = ruleInvalid
287                         return n, false
288                 }
289                 switch tr := transitions[t.state]; {
290                 case tr[0].mask&c != 0:
291                         t.state = tr[0].next
292                 case tr[1].mask&c != 0:
293                         t.state = tr[1].next
294                 default:
295                         t.state = ruleInvalid
296                         if t.isRTL() {
297                                 return n, false
298                         }
299                 }
300                 n += sz
301         }
302         return n, true
303 }
304
305 func (t *Transformer) advanceString(s string) (n int, ok bool) {
306         var e bidi.Properties
307         var sz int
308         for n < len(s) {
309                 if s[n] < utf8.RuneSelf {
310                         e, sz = asciiTable[s[n]], 1
311                 } else {
312                         e, sz = bidi.LookupString(s[n:])
313                         if sz <= 1 {
314                                 if sz == 1 {
315                                         return n, false // invalid UTF-8
316                                 }
317                                 return n, true // incomplete UTF-8 encoding
318                         }
319                 }
320                 // TODO: using CompactClass results in noticeable speedup.
321                 // See unicode/bidi/prop.go:Properties.CompactClass.
322                 c := uint16(1 << e.Class())
323                 t.seen |= c
324                 if t.seen&exclusiveRTL == exclusiveRTL {
325                         t.state = ruleInvalid
326                         return n, false
327                 }
328                 switch tr := transitions[t.state]; {
329                 case tr[0].mask&c != 0:
330                         t.state = tr[0].next
331                 case tr[1].mask&c != 0:
332                         t.state = tr[1].next
333                 default:
334                         t.state = ruleInvalid
335                         if t.isRTL() {
336                                 return n, false
337                         }
338                 }
339                 n += sz
340         }
341         return n, true
342 }