1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package idna
19
20 import (
21 "fmt"
22 "strings"
23 "unicode/utf8"
24
25 "golang.org/x/text/secure/bidirule"
26 "golang.org/x/text/unicode/norm"
27 )
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 func ToASCII(s string) (string, error) {
46 return Punycode.process(s, true)
47 }
48
49
50 func ToUnicode(s string) (string, error) {
51 return Punycode.process(s, false)
52 }
53
54
55 type Option func(*options)
56
57
58
59
60
61
62 func Transitional(transitional bool) Option {
63 return func(o *options) { o.transitional = transitional }
64 }
65
66
67
68
69
70 func VerifyDNSLength(verify bool) Option {
71 return func(o *options) { o.verifyDNSLength = verify }
72 }
73
74
75
76 func RemoveLeadingDots(remove bool) Option {
77 return func(o *options) { o.removeLeadingDots = remove }
78 }
79
80
81
82
83
84
85 func ValidateLabels(enable bool) Option {
86 return func(o *options) {
87
88
89 if o.mapping == nil && enable {
90 o.mapping = normalize
91 }
92 o.trie = trie
93 o.checkJoiners = enable
94 o.checkHyphens = enable
95 if enable {
96 o.fromPuny = validateFromPunycode
97 } else {
98 o.fromPuny = nil
99 }
100 }
101 }
102
103
104
105
106
107
108 func CheckHyphens(enable bool) Option {
109 return func(o *options) { o.checkHyphens = enable }
110 }
111
112
113
114
115
116 func CheckJoiners(enable bool) Option {
117 return func(o *options) {
118 o.trie = trie
119 o.checkJoiners = enable
120 }
121 }
122
123
124
125
126
127
128
129
130
131
132
133 func StrictDomainName(use bool) Option {
134 return func(o *options) { o.useSTD3Rules = use }
135 }
136
137
138
139
140
141
142
143
144 func BidiRule() Option {
145 return func(o *options) { o.bidirule = bidirule.ValidString }
146 }
147
148
149
150 func ValidateForRegistration() Option {
151 return func(o *options) {
152 o.mapping = validateRegistration
153 StrictDomainName(true)(o)
154 ValidateLabels(true)(o)
155 VerifyDNSLength(true)(o)
156 BidiRule()(o)
157 }
158 }
159
160
161
162
163
164
165
166
167
168 func MapForLookup() Option {
169 return func(o *options) {
170 o.mapping = validateAndMap
171 StrictDomainName(true)(o)
172 ValidateLabels(true)(o)
173 RemoveLeadingDots(true)(o)
174 }
175 }
176
177 type options struct {
178 transitional bool
179 useSTD3Rules bool
180 checkHyphens bool
181 checkJoiners bool
182 verifyDNSLength bool
183 removeLeadingDots bool
184
185 trie *idnaTrie
186
187
188 fromPuny func(p *Profile, s string) error
189
190
191
192 mapping func(p *Profile, s string) (string, error)
193
194
195
196 bidirule func(s string) bool
197 }
198
199
200 type Profile struct {
201 options
202 }
203
204 func apply(o *options, opts []Option) {
205 for _, f := range opts {
206 f(o)
207 }
208 }
209
210
211
212
213
214
215
216
217
218 func New(o ...Option) *Profile {
219 p := &Profile{}
220 apply(&p.options, o)
221 return p
222 }
223
224
225
226
227
228 func (p *Profile) ToASCII(s string) (string, error) {
229 return p.process(s, true)
230 }
231
232
233
234
235
236 func (p *Profile) ToUnicode(s string) (string, error) {
237 pp := *p
238 pp.transitional = false
239 return pp.process(s, false)
240 }
241
242
243
244 func (p *Profile) String() string {
245 s := ""
246 if p.transitional {
247 s = "Transitional"
248 } else {
249 s = "NonTransitional"
250 }
251 if p.useSTD3Rules {
252 s += ":UseSTD3Rules"
253 }
254 if p.checkHyphens {
255 s += ":CheckHyphens"
256 }
257 if p.checkJoiners {
258 s += ":CheckJoiners"
259 }
260 if p.verifyDNSLength {
261 s += ":VerifyDNSLength"
262 }
263 return s
264 }
265
266 var (
267
268
269 Punycode *Profile = punycode
270
271
272
273
274 Lookup *Profile = lookup
275
276
277
278 Display *Profile = display
279
280
281
282 Registration *Profile = registration
283
284 punycode = &Profile{}
285 lookup = &Profile{options{
286 transitional: true,
287 removeLeadingDots: true,
288 useSTD3Rules: true,
289 checkHyphens: true,
290 checkJoiners: true,
291 trie: trie,
292 fromPuny: validateFromPunycode,
293 mapping: validateAndMap,
294 bidirule: bidirule.ValidString,
295 }}
296 display = &Profile{options{
297 useSTD3Rules: true,
298 removeLeadingDots: true,
299 checkHyphens: true,
300 checkJoiners: true,
301 trie: trie,
302 fromPuny: validateFromPunycode,
303 mapping: validateAndMap,
304 bidirule: bidirule.ValidString,
305 }}
306 registration = &Profile{options{
307 useSTD3Rules: true,
308 verifyDNSLength: true,
309 checkHyphens: true,
310 checkJoiners: true,
311 trie: trie,
312 fromPuny: validateFromPunycode,
313 mapping: validateRegistration,
314 bidirule: bidirule.ValidString,
315 }}
316
317
318
319
320 )
321
322 type labelError struct{ label, code_ string }
323
324 func (e labelError) code() string { return e.code_ }
325 func (e labelError) Error() string {
326 return fmt.Sprintf("idna: invalid label %q", e.label)
327 }
328
329 type runeError rune
330
331 func (e runeError) code() string { return "P1" }
332 func (e runeError) Error() string {
333 return fmt.Sprintf("idna: disallowed rune %U", e)
334 }
335
336
337
338 func (p *Profile) process(s string, toASCII bool) (string, error) {
339 var err error
340 if p.mapping != nil {
341 s, err = p.mapping(p, s)
342 }
343
344 if p.removeLeadingDots {
345 for ; len(s) > 0 && s[0] == '.'; s = s[1:] {
346 }
347 }
348
349
350 if err == nil && p.verifyDNSLength && s == "" {
351 err = &labelError{s, "A4"}
352 }
353 labels := labelIter{orig: s}
354 for ; !labels.done(); labels.next() {
355 label := labels.label()
356 if label == "" {
357
358
359 if err == nil && p.verifyDNSLength {
360 err = &labelError{s, "A4"}
361 }
362 continue
363 }
364 if strings.HasPrefix(label, acePrefix) {
365 u, err2 := decode(label[len(acePrefix):])
366 if err2 != nil {
367 if err == nil {
368 err = err2
369 }
370
371 continue
372 }
373 labels.set(u)
374 if err == nil && p.fromPuny != nil {
375 err = p.fromPuny(p, u)
376 }
377 if err == nil {
378
379
380
381 err = p.validateLabel(u)
382 }
383 } else if err == nil {
384 err = p.validateLabel(label)
385 }
386 }
387 if toASCII {
388 for labels.reset(); !labels.done(); labels.next() {
389 label := labels.label()
390 if !ascii(label) {
391 a, err2 := encode(acePrefix, label)
392 if err == nil {
393 err = err2
394 }
395 label = a
396 labels.set(a)
397 }
398 n := len(label)
399 if p.verifyDNSLength && err == nil && (n == 0 || n > 63) {
400 err = &labelError{label, "A4"}
401 }
402 }
403 }
404 s = labels.result()
405 if toASCII && p.verifyDNSLength && err == nil {
406
407 n := len(s)
408 if n > 0 && s[n-1] == '.' {
409 n--
410 }
411 if len(s) < 1 || n > 253 {
412 err = &labelError{s, "A4"}
413 }
414 }
415 return s, err
416 }
417
418 func normalize(p *Profile, s string) (string, error) {
419 return norm.NFC.String(s), nil
420 }
421
422 func validateRegistration(p *Profile, s string) (string, error) {
423 if !norm.NFC.IsNormalString(s) {
424 return s, &labelError{s, "V1"}
425 }
426 for i := 0; i < len(s); {
427 v, sz := trie.lookupString(s[i:])
428 if sz == 0 {
429 return s, runeError(utf8.RuneError)
430 }
431
432 switch p.simplify(info(v).category()) {
433
434
435 case valid, deviation:
436 case disallowed, mapped, unknown, ignored:
437 r, _ := utf8.DecodeRuneInString(s[i:])
438 return s, runeError(r)
439 }
440 i += sz
441 }
442 return s, nil
443 }
444
445 func validateAndMap(p *Profile, s string) (string, error) {
446 var (
447 err error
448 b []byte
449 k int
450 )
451 for i := 0; i < len(s); {
452 v, sz := trie.lookupString(s[i:])
453 if sz == 0 {
454 b = append(b, s[k:i]...)
455 b = append(b, "\ufffd"...)
456 k = len(s)
457 if err == nil {
458 err = runeError(utf8.RuneError)
459 }
460 break
461 }
462 start := i
463 i += sz
464
465 switch p.simplify(info(v).category()) {
466 case valid:
467 continue
468 case disallowed:
469 if err == nil {
470 r, _ := utf8.DecodeRuneInString(s[start:])
471 err = runeError(r)
472 }
473 continue
474 case mapped, deviation:
475 b = append(b, s[k:start]...)
476 b = info(v).appendMapping(b, s[start:i])
477 case ignored:
478 b = append(b, s[k:start]...)
479
480 case unknown:
481 b = append(b, s[k:start]...)
482 b = append(b, "\ufffd"...)
483 }
484 k = i
485 }
486 if k == 0 {
487
488 s = norm.NFC.String(s)
489 } else {
490 b = append(b, s[k:]...)
491 if norm.NFC.QuickSpan(b) != len(b) {
492 b = norm.NFC.Bytes(b)
493 }
494
495 s = string(b)
496 }
497 return s, err
498 }
499
500
501 type labelIter struct {
502 orig string
503 slice []string
504 curStart int
505 curEnd int
506 i int
507 }
508
509 func (l *labelIter) reset() {
510 l.curStart = 0
511 l.curEnd = 0
512 l.i = 0
513 }
514
515 func (l *labelIter) done() bool {
516 return l.curStart >= len(l.orig)
517 }
518
519 func (l *labelIter) result() string {
520 if l.slice != nil {
521 return strings.Join(l.slice, ".")
522 }
523 return l.orig
524 }
525
526 func (l *labelIter) label() string {
527 if l.slice != nil {
528 return l.slice[l.i]
529 }
530 p := strings.IndexByte(l.orig[l.curStart:], '.')
531 l.curEnd = l.curStart + p
532 if p == -1 {
533 l.curEnd = len(l.orig)
534 }
535 return l.orig[l.curStart:l.curEnd]
536 }
537
538
539 func (l *labelIter) next() {
540 l.i++
541 if l.slice != nil {
542 if l.i >= len(l.slice) || l.i == len(l.slice)-1 && l.slice[l.i] == "" {
543 l.curStart = len(l.orig)
544 }
545 } else {
546 l.curStart = l.curEnd + 1
547 if l.curStart == len(l.orig)-1 && l.orig[l.curStart] == '.' {
548 l.curStart = len(l.orig)
549 }
550 }
551 }
552
553 func (l *labelIter) set(s string) {
554 if l.slice == nil {
555 l.slice = strings.Split(l.orig, ".")
556 }
557 l.slice[l.i] = s
558 }
559
560
561 const acePrefix = "xn--"
562
563 func (p *Profile) simplify(cat category) category {
564 switch cat {
565 case disallowedSTD3Mapped:
566 if p.useSTD3Rules {
567 cat = disallowed
568 } else {
569 cat = mapped
570 }
571 case disallowedSTD3Valid:
572 if p.useSTD3Rules {
573 cat = disallowed
574 } else {
575 cat = valid
576 }
577 case deviation:
578 if !p.transitional {
579 cat = valid
580 }
581 case validNV8, validXV8:
582
583 cat = valid
584 }
585 return cat
586 }
587
588 func validateFromPunycode(p *Profile, s string) error {
589 if !norm.NFC.IsNormalString(s) {
590 return &labelError{s, "V1"}
591 }
592 for i := 0; i < len(s); {
593 v, sz := trie.lookupString(s[i:])
594 if sz == 0 {
595 return runeError(utf8.RuneError)
596 }
597 if c := p.simplify(info(v).category()); c != valid && c != deviation {
598 return &labelError{s, "V6"}
599 }
600 i += sz
601 }
602 return nil
603 }
604
605 const (
606 zwnj = "\u200c"
607 zwj = "\u200d"
608 )
609
610 type joinState int8
611
612 const (
613 stateStart joinState = iota
614 stateVirama
615 stateBefore
616 stateBeforeVirama
617 stateAfter
618 stateFAIL
619 )
620
621 var joinStates = [][numJoinTypes]joinState{
622 stateStart: {
623 joiningL: stateBefore,
624 joiningD: stateBefore,
625 joinZWNJ: stateFAIL,
626 joinZWJ: stateFAIL,
627 joinVirama: stateVirama,
628 },
629 stateVirama: {
630 joiningL: stateBefore,
631 joiningD: stateBefore,
632 },
633 stateBefore: {
634 joiningL: stateBefore,
635 joiningD: stateBefore,
636 joiningT: stateBefore,
637 joinZWNJ: stateAfter,
638 joinZWJ: stateFAIL,
639 joinVirama: stateBeforeVirama,
640 },
641 stateBeforeVirama: {
642 joiningL: stateBefore,
643 joiningD: stateBefore,
644 joiningT: stateBefore,
645 },
646 stateAfter: {
647 joiningL: stateFAIL,
648 joiningD: stateBefore,
649 joiningT: stateAfter,
650 joiningR: stateStart,
651 joinZWNJ: stateFAIL,
652 joinZWJ: stateFAIL,
653 joinVirama: stateAfter,
654 },
655 stateFAIL: {
656 0: stateFAIL,
657 joiningL: stateFAIL,
658 joiningD: stateFAIL,
659 joiningT: stateFAIL,
660 joiningR: stateFAIL,
661 joinZWNJ: stateFAIL,
662 joinZWJ: stateFAIL,
663 joinVirama: stateFAIL,
664 },
665 }
666
667
668
669 func (p *Profile) validateLabel(s string) error {
670 if s == "" {
671 if p.verifyDNSLength {
672 return &labelError{s, "A4"}
673 }
674 return nil
675 }
676 if p.bidirule != nil && !p.bidirule(s) {
677 return &labelError{s, "B"}
678 }
679 if p.checkHyphens {
680 if len(s) > 4 && s[2] == '-' && s[3] == '-' {
681 return &labelError{s, "V2"}
682 }
683 if s[0] == '-' || s[len(s)-1] == '-' {
684 return &labelError{s, "V3"}
685 }
686 }
687 if !p.checkJoiners {
688 return nil
689 }
690 trie := p.trie
691
692 v, sz := trie.lookupString(s)
693 x := info(v)
694 if x.isModifier() {
695 return &labelError{s, "V5"}
696 }
697
698 if strings.Index(s, zwj) == -1 && strings.Index(s, zwnj) == -1 {
699 return nil
700 }
701 st := stateStart
702 for i := 0; ; {
703 jt := x.joinType()
704 if s[i:i+sz] == zwj {
705 jt = joinZWJ
706 } else if s[i:i+sz] == zwnj {
707 jt = joinZWNJ
708 }
709 st = joinStates[st][jt]
710 if x.isViramaModifier() {
711 st = joinStates[st][joinVirama]
712 }
713 if i += sz; i == len(s) {
714 break
715 }
716 v, sz = trie.lookupString(s[i:])
717 x = info(v)
718 }
719 if st == stateFAIL || st == stateAfter {
720 return &labelError{s, "C"}
721 }
722 return nil
723 }
724
725 func ascii(s string) bool {
726 for i := 0; i < len(s); i++ {
727 if s[i] >= utf8.RuneSelf {
728 return false
729 }
730 }
731 return true
732 }
733
View as plain text