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