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/bidi"
27 "golang.org/x/text/unicode/norm"
28 )
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 func ToASCII(s string) (string, error) {
47 return Punycode.process(s, true)
48 }
49
50
51 func ToUnicode(s string) (string, error) {
52 return Punycode.process(s, false)
53 }
54
55
56 type Option func(*options)
57
58
59
60
61
62
63 func Transitional(transitional bool) Option {
64 return func(o *options) { o.transitional = transitional }
65 }
66
67
68
69
70
71 func VerifyDNSLength(verify bool) Option {
72 return func(o *options) { o.verifyDNSLength = verify }
73 }
74
75
76
77 func RemoveLeadingDots(remove bool) Option {
78 return func(o *options) { o.removeLeadingDots = remove }
79 }
80
81
82
83
84
85
86 func ValidateLabels(enable bool) Option {
87 return func(o *options) {
88
89
90 if o.mapping == nil && enable {
91 o.mapping = normalize
92 }
93 o.trie = trie
94 o.checkJoiners = enable
95 o.checkHyphens = enable
96 if enable {
97 o.fromPuny = validateFromPunycode
98 } else {
99 o.fromPuny = nil
100 }
101 }
102 }
103
104
105
106
107
108
109 func CheckHyphens(enable bool) Option {
110 return func(o *options) { o.checkHyphens = enable }
111 }
112
113
114
115
116
117 func CheckJoiners(enable bool) Option {
118 return func(o *options) {
119 o.trie = trie
120 o.checkJoiners = enable
121 }
122 }
123
124
125
126
127
128
129
130
131
132
133
134 func StrictDomainName(use bool) Option {
135 return func(o *options) { o.useSTD3Rules = use }
136 }
137
138
139
140
141
142
143
144
145 func BidiRule() Option {
146 return func(o *options) { o.bidirule = bidirule.ValidString }
147 }
148
149
150
151 func ValidateForRegistration() Option {
152 return func(o *options) {
153 o.mapping = validateRegistration
154 StrictDomainName(true)(o)
155 ValidateLabels(true)(o)
156 VerifyDNSLength(true)(o)
157 BidiRule()(o)
158 }
159 }
160
161
162
163
164
165
166
167
168
169 func MapForLookup() Option {
170 return func(o *options) {
171 o.mapping = validateAndMap
172 StrictDomainName(true)(o)
173 ValidateLabels(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) (mapped string, isBidi bool, err 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: transitionalLookup,
287 useSTD3Rules: true,
288 checkHyphens: true,
289 checkJoiners: true,
290 trie: trie,
291 fromPuny: validateFromPunycode,
292 mapping: validateAndMap,
293 bidirule: bidirule.ValidString,
294 }}
295 display = &Profile{options{
296 useSTD3Rules: true,
297 checkHyphens: true,
298 checkJoiners: true,
299 trie: trie,
300 fromPuny: validateFromPunycode,
301 mapping: validateAndMap,
302 bidirule: bidirule.ValidString,
303 }}
304 registration = &Profile{options{
305 useSTD3Rules: true,
306 verifyDNSLength: true,
307 checkHyphens: true,
308 checkJoiners: true,
309 trie: trie,
310 fromPuny: validateFromPunycode,
311 mapping: validateRegistration,
312 bidirule: bidirule.ValidString,
313 }}
314
315
316
317
318 )
319
320 type labelError struct{ label, code_ string }
321
322 func (e labelError) code() string { return e.code_ }
323 func (e labelError) Error() string {
324 return fmt.Sprintf("idna: invalid label %q", e.label)
325 }
326
327 type runeError rune
328
329 func (e runeError) code() string { return "P1" }
330 func (e runeError) Error() string {
331 return fmt.Sprintf("idna: disallowed rune %U", e)
332 }
333
334
335
336 func (p *Profile) process(s string, toASCII bool) (string, error) {
337 var err error
338 var isBidi bool
339 if p.mapping != nil {
340 s, isBidi, err = p.mapping(p, s)
341 }
342
343 if p.removeLeadingDots {
344 for ; len(s) > 0 && s[0] == '.'; s = s[1:] {
345 }
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 enc := label[len(acePrefix):]
366 u, err2 := decode(enc)
367 if err2 != nil {
368 if err == nil {
369 err = err2
370 }
371
372 continue
373 }
374 if err == nil && len(u) > 0 && isASCII(u) {
375 err = punyError(enc)
376 }
377 isBidi = isBidi || bidirule.DirectionString(u) != bidi.LeftToRight
378 labels.set(u)
379 if err == nil && p.fromPuny != nil {
380 err = p.fromPuny(p, u)
381 }
382 if err == nil {
383
384
385
386 err = p.validateLabel(u)
387 }
388 } else if err == nil {
389 err = p.validateLabel(label)
390 }
391 }
392 if isBidi && p.bidirule != nil && err == nil {
393 for labels.reset(); !labels.done(); labels.next() {
394 if !p.bidirule(labels.label()) {
395 err = &labelError{s, "B"}
396 break
397 }
398 }
399 }
400 if toASCII {
401 for labels.reset(); !labels.done(); labels.next() {
402 label := labels.label()
403 if !ascii(label) {
404 a, err2 := encode(acePrefix, label)
405 if err == nil {
406 err = err2
407 }
408 label = a
409 labels.set(a)
410 }
411 n := len(label)
412 if p.verifyDNSLength && err == nil && (n == 0 || n > 63) {
413 err = &labelError{label, "A4"}
414 }
415 }
416 }
417 s = labels.result()
418 if toASCII && p.verifyDNSLength && err == nil {
419
420 n := len(s)
421 if n > 0 && s[n-1] == '.' {
422 n--
423 }
424 if len(s) < 1 || n > 253 {
425 err = &labelError{s, "A4"}
426 }
427 }
428 return s, err
429 }
430
431 func isASCII(s string) bool {
432 for _, c := range []byte(s) {
433 if c >= 0x80 {
434 return false
435 }
436 }
437 return true
438 }
439
440 func normalize(p *Profile, s string) (mapped string, isBidi bool, err error) {
441
442
443
444 mapped = norm.NFC.String(s)
445 isBidi = bidirule.DirectionString(mapped) == bidi.RightToLeft
446 return mapped, isBidi, nil
447 }
448
449 func validateRegistration(p *Profile, s string) (idem string, bidi bool, err error) {
450
451 if !norm.NFC.IsNormalString(s) {
452 return s, false, &labelError{s, "V1"}
453 }
454 for i := 0; i < len(s); {
455 v, sz := trie.lookupString(s[i:])
456 if sz == 0 {
457 return s, bidi, runeError(utf8.RuneError)
458 }
459 bidi = bidi || info(v).isBidi(s[i:])
460
461 switch p.simplify(info(v).category()) {
462
463
464 case valid, deviation:
465 case disallowed, mapped, unknown, ignored:
466 r, _ := utf8.DecodeRuneInString(s[i:])
467 return s, bidi, runeError(r)
468 }
469 i += sz
470 }
471 return s, bidi, nil
472 }
473
474 func (c info) isBidi(s string) bool {
475 if !c.isMapped() {
476 return c&attributesMask == rtl
477 }
478
479
480 p, _ := bidi.LookupString(s)
481 switch p.Class() {
482 case bidi.R, bidi.AL, bidi.AN:
483 return true
484 }
485 return false
486 }
487
488 func validateAndMap(p *Profile, s string) (vm string, bidi bool, err error) {
489 var (
490 b []byte
491 k int
492 )
493
494
495
496
497 var combinedInfoBits info
498 for i := 0; i < len(s); {
499 v, sz := trie.lookupString(s[i:])
500 if sz == 0 {
501 b = append(b, s[k:i]...)
502 b = append(b, "\ufffd"...)
503 k = len(s)
504 if err == nil {
505 err = runeError(utf8.RuneError)
506 }
507 break
508 }
509 combinedInfoBits |= info(v)
510 bidi = bidi || info(v).isBidi(s[i:])
511 start := i
512 i += sz
513
514 switch p.simplify(info(v).category()) {
515 case valid:
516 continue
517 case disallowed:
518 if err == nil {
519 r, _ := utf8.DecodeRuneInString(s[start:])
520 err = runeError(r)
521 }
522 continue
523 case mapped, deviation:
524 b = append(b, s[k:start]...)
525 b = info(v).appendMapping(b, s[start:i])
526 case ignored:
527 b = append(b, s[k:start]...)
528
529 case unknown:
530 b = append(b, s[k:start]...)
531 b = append(b, "\ufffd"...)
532 }
533 k = i
534 }
535 if k == 0 {
536
537 if combinedInfoBits&mayNeedNorm != 0 {
538 s = norm.NFC.String(s)
539 }
540 } else {
541 b = append(b, s[k:]...)
542 if norm.NFC.QuickSpan(b) != len(b) {
543 b = norm.NFC.Bytes(b)
544 }
545
546 s = string(b)
547 }
548 return s, bidi, err
549 }
550
551
552 type labelIter struct {
553 orig string
554 slice []string
555 curStart int
556 curEnd int
557 i int
558 }
559
560 func (l *labelIter) reset() {
561 l.curStart = 0
562 l.curEnd = 0
563 l.i = 0
564 }
565
566 func (l *labelIter) done() bool {
567 return l.curStart >= len(l.orig)
568 }
569
570 func (l *labelIter) result() string {
571 if l.slice != nil {
572 return strings.Join(l.slice, ".")
573 }
574 return l.orig
575 }
576
577 func (l *labelIter) label() string {
578 if l.slice != nil {
579 return l.slice[l.i]
580 }
581 p := strings.IndexByte(l.orig[l.curStart:], '.')
582 l.curEnd = l.curStart + p
583 if p == -1 {
584 l.curEnd = len(l.orig)
585 }
586 return l.orig[l.curStart:l.curEnd]
587 }
588
589
590 func (l *labelIter) next() {
591 l.i++
592 if l.slice != nil {
593 if l.i >= len(l.slice) || l.i == len(l.slice)-1 && l.slice[l.i] == "" {
594 l.curStart = len(l.orig)
595 }
596 } else {
597 l.curStart = l.curEnd + 1
598 if l.curStart == len(l.orig)-1 && l.orig[l.curStart] == '.' {
599 l.curStart = len(l.orig)
600 }
601 }
602 }
603
604 func (l *labelIter) set(s string) {
605 if l.slice == nil {
606 l.slice = strings.Split(l.orig, ".")
607 }
608 l.slice[l.i] = s
609 }
610
611
612 const acePrefix = "xn--"
613
614 func (p *Profile) simplify(cat category) category {
615 switch cat {
616 case disallowedSTD3Mapped:
617 if p.useSTD3Rules {
618 cat = disallowed
619 } else {
620 cat = mapped
621 }
622 case disallowedSTD3Valid:
623 if p.useSTD3Rules {
624 cat = disallowed
625 } else {
626 cat = valid
627 }
628 case deviation:
629 if !p.transitional {
630 cat = valid
631 }
632 case validNV8, validXV8:
633
634 cat = valid
635 }
636 return cat
637 }
638
639 func validateFromPunycode(p *Profile, s string) error {
640 if !norm.NFC.IsNormalString(s) {
641 return &labelError{s, "V1"}
642 }
643
644
645 for i := 0; i < len(s); {
646 v, sz := trie.lookupString(s[i:])
647 if sz == 0 {
648 return runeError(utf8.RuneError)
649 }
650 if c := p.simplify(info(v).category()); c != valid && c != deviation {
651 return &labelError{s, "V6"}
652 }
653 i += sz
654 }
655 return nil
656 }
657
658 const (
659 zwnj = "\u200c"
660 zwj = "\u200d"
661 )
662
663 type joinState int8
664
665 const (
666 stateStart joinState = iota
667 stateVirama
668 stateBefore
669 stateBeforeVirama
670 stateAfter
671 stateFAIL
672 )
673
674 var joinStates = [][numJoinTypes]joinState{
675 stateStart: {
676 joiningL: stateBefore,
677 joiningD: stateBefore,
678 joinZWNJ: stateFAIL,
679 joinZWJ: stateFAIL,
680 joinVirama: stateVirama,
681 },
682 stateVirama: {
683 joiningL: stateBefore,
684 joiningD: stateBefore,
685 },
686 stateBefore: {
687 joiningL: stateBefore,
688 joiningD: stateBefore,
689 joiningT: stateBefore,
690 joinZWNJ: stateAfter,
691 joinZWJ: stateFAIL,
692 joinVirama: stateBeforeVirama,
693 },
694 stateBeforeVirama: {
695 joiningL: stateBefore,
696 joiningD: stateBefore,
697 joiningT: stateBefore,
698 },
699 stateAfter: {
700 joiningL: stateFAIL,
701 joiningD: stateBefore,
702 joiningT: stateAfter,
703 joiningR: stateStart,
704 joinZWNJ: stateFAIL,
705 joinZWJ: stateFAIL,
706 joinVirama: stateAfter,
707 },
708 stateFAIL: {
709 0: stateFAIL,
710 joiningL: stateFAIL,
711 joiningD: stateFAIL,
712 joiningT: stateFAIL,
713 joiningR: stateFAIL,
714 joinZWNJ: stateFAIL,
715 joinZWJ: stateFAIL,
716 joinVirama: stateFAIL,
717 },
718 }
719
720
721
722 func (p *Profile) validateLabel(s string) (err error) {
723 if s == "" {
724 if p.verifyDNSLength {
725 return &labelError{s, "A4"}
726 }
727 return nil
728 }
729 if p.checkHyphens {
730 if len(s) > 4 && s[2] == '-' && s[3] == '-' {
731 return &labelError{s, "V2"}
732 }
733 if s[0] == '-' || s[len(s)-1] == '-' {
734 return &labelError{s, "V3"}
735 }
736 }
737 if !p.checkJoiners {
738 return nil
739 }
740 trie := p.trie
741
742 v, sz := trie.lookupString(s)
743 x := info(v)
744 if x.isModifier() {
745 return &labelError{s, "V5"}
746 }
747
748 if strings.Index(s, zwj) == -1 && strings.Index(s, zwnj) == -1 {
749 return nil
750 }
751 st := stateStart
752 for i := 0; ; {
753 jt := x.joinType()
754 if s[i:i+sz] == zwj {
755 jt = joinZWJ
756 } else if s[i:i+sz] == zwnj {
757 jt = joinZWNJ
758 }
759 st = joinStates[st][jt]
760 if x.isViramaModifier() {
761 st = joinStates[st][joinVirama]
762 }
763 if i += sz; i == len(s) {
764 break
765 }
766 v, sz = trie.lookupString(s[i:])
767 x = info(v)
768 }
769 if st == stateFAIL || st == stateAfter {
770 return &labelError{s, "C"}
771 }
772 return nil
773 }
774
775 func ascii(s string) bool {
776 for i := 0; i < len(s); i++ {
777 if s[i] >= utf8.RuneSelf {
778 return false
779 }
780 }
781 return true
782 }
783
View as plain text