Source file src/vendor/golang.org/x/net/idna/idna.go

     1  // Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
     2  
     3  // Copyright 2016 The Go Authors. All rights reserved.
     4  // Use of this source code is governed by a BSD-style
     5  // license that can be found in the LICENSE file.
     6  
     7  // Package idna implements IDNA2008 using the compatibility processing
     8  // defined by UTS (Unicode Technical Standard) #46, which defines a standard to
     9  // deal with the transition from IDNA2003.
    10  //
    11  // IDNA2008 (Internationalized Domain Names for Applications), is defined in RFC
    12  // 5890, RFC 5891, RFC 5892, RFC 5893 and RFC 5894.
    13  // UTS #46 is defined in https://www.unicode.org/reports/tr46.
    14  // See https://unicode.org/cldr/utility/idna.jsp for a visualization of the
    15  // differences between these two standards.
    16  package idna // import "golang.org/x/net/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  // NOTE: Unlike common practice in Go APIs, the functions will return a
    32  // sanitized domain name in case of errors. Browsers sometimes use a partially
    33  // evaluated string as lookup.
    34  // TODO: the current error handling is, in my opinion, the least opinionated.
    35  // Other strategies are also viable, though:
    36  // Option 1) Return an empty string in case of error, but allow the user to
    37  //    specify explicitly which errors to ignore.
    38  // Option 2) Return the partially evaluated string if it is itself a valid
    39  //    string, otherwise return the empty string in case of error.
    40  // Option 3) Option 1 and 2.
    41  // Option 4) Always return an empty string for now and implement Option 1 as
    42  //    needed, and document that the return string may not be empty in case of
    43  //    error in the future.
    44  // I think Option 1 is best, but it is quite opinionated.
    45  
    46  // ToASCII is a wrapper for Punycode.ToASCII.
    47  func ToASCII(s string) (string, error) {
    48  	return Punycode.process(s, true)
    49  }
    50  
    51  // ToUnicode is a wrapper for Punycode.ToUnicode.
    52  func ToUnicode(s string) (string, error) {
    53  	return Punycode.process(s, false)
    54  }
    55  
    56  // An Option configures a Profile at creation time.
    57  type Option func(*options)
    58  
    59  // Transitional sets a Profile to use the Transitional mapping as defined in UTS
    60  // #46. This will cause, for example, "ß" to be mapped to "ss". Using the
    61  // transitional mapping provides a compromise between IDNA2003 and IDNA2008
    62  // compatibility. It is used by some browsers when resolving domain names. This
    63  // option is only meaningful if combined with MapForLookup.
    64  func Transitional(transitional bool) Option {
    65  	return func(o *options) { o.transitional = transitional }
    66  }
    67  
    68  // VerifyDNSLength sets whether a Profile should fail if any of the IDN parts
    69  // are longer than allowed by the RFC.
    70  //
    71  // This option corresponds to the VerifyDnsLength flag in UTS #46.
    72  func VerifyDNSLength(verify bool) Option {
    73  	return func(o *options) { o.verifyDNSLength = verify }
    74  }
    75  
    76  // RemoveLeadingDots removes leading label separators. Leading runes that map to
    77  // dots, such as U+3002 IDEOGRAPHIC FULL STOP, are removed as well.
    78  func RemoveLeadingDots(remove bool) Option {
    79  	return func(o *options) { o.removeLeadingDots = remove }
    80  }
    81  
    82  // ValidateLabels sets whether to check the mandatory label validation criteria
    83  // as defined in Section 5.4 of RFC 5891. This includes testing for correct use
    84  // of hyphens ('-'), normalization, validity of runes, and the context rules.
    85  // In particular, ValidateLabels also sets the CheckHyphens and CheckJoiners flags
    86  // in UTS #46.
    87  func ValidateLabels(enable bool) Option {
    88  	return func(o *options) {
    89  		// Don't override existing mappings, but set one that at least checks
    90  		// normalization if it is not set.
    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  // validateLabels reports whether the ValidateLabels option is enabled.
   106  func (p *Profile) validateLabels() bool {
   107  	return p.fromPuny != nil
   108  }
   109  
   110  // CheckHyphens sets whether to check for correct use of hyphens ('-') in
   111  // labels. Most web browsers do not have this option set, since labels such as
   112  // "r3---sn-apo3qvuoxuxbt-j5pe" are in common use.
   113  //
   114  // This option corresponds to the CheckHyphens flag in UTS #46.
   115  func CheckHyphens(enable bool) Option {
   116  	return func(o *options) { o.checkHyphens = enable }
   117  }
   118  
   119  // CheckJoiners sets whether to check the ContextJ rules as defined in Appendix
   120  // A of RFC 5892, concerning the use of joiner runes.
   121  //
   122  // This option corresponds to the CheckJoiners flag in UTS #46.
   123  func CheckJoiners(enable bool) Option {
   124  	return func(o *options) {
   125  		o.trie = trie
   126  		o.checkJoiners = enable
   127  	}
   128  }
   129  
   130  // StrictDomainName limits the set of permissible ASCII characters to those
   131  // allowed in domain names as defined in RFC 1034 (A-Z, a-z, 0-9 and the
   132  // hyphen). This is set by default for MapForLookup and ValidateForRegistration,
   133  // but is only useful if ValidateLabels is set.
   134  //
   135  // This option is useful, for instance, for browsers that allow characters
   136  // outside this range, for example a '_' (U+005F LOW LINE). See
   137  // http://www.rfc-editor.org/std/std3.txt for more details.
   138  //
   139  // This option corresponds to the UseSTD3ASCIIRules flag in UTS #46.
   140  func StrictDomainName(use bool) Option {
   141  	return func(o *options) { o.useSTD3Rules = use }
   142  }
   143  
   144  // NOTE: the following options pull in tables. The tables should not be linked
   145  // in as long as the options are not used.
   146  
   147  // BidiRule enables the Bidi rule as defined in RFC 5893. Any application
   148  // that relies on proper validation of labels should include this rule.
   149  //
   150  // This option corresponds to the CheckBidi flag in UTS #46.
   151  func BidiRule() Option {
   152  	return func(o *options) { o.bidirule = bidirule.ValidString }
   153  }
   154  
   155  // ValidateForRegistration sets validation options to verify that a given IDN is
   156  // properly formatted for registration as defined by Section 4 of RFC 5891.
   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  // MapForLookup sets validation and mapping options such that a given IDN is
   168  // transformed for domain name lookup according to the requirements set out in
   169  // Section 5 of RFC 5891. The mappings follow the recommendations of RFC 5894,
   170  // RFC 5895 and UTS 46. It does not add the Bidi Rule. Use the BidiRule option
   171  // to add this check.
   172  //
   173  // The mappings include normalization and mapping case, width and other
   174  // compatibility mappings.
   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  	// fromPuny calls validation rules when converting A-labels to U-labels.
   194  	fromPuny func(p *Profile, s string) error
   195  
   196  	// mapping implements a validation and mapping step as defined in RFC 5895
   197  	// or UTS 46, tailored to, for example, domain registration or lookup.
   198  	mapping func(p *Profile, s string) (mapped string, isBidi bool, err error)
   199  
   200  	// bidirule, if specified, checks whether s conforms to the Bidi Rule
   201  	// defined in RFC 5893.
   202  	bidirule func(s string) bool
   203  }
   204  
   205  // A Profile defines the configuration of an IDNA mapper.
   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  // New creates a new Profile.
   217  //
   218  // With no options, the returned Profile is the most permissive and equals the
   219  // Punycode Profile. Options can be passed to further restrict the Profile. The
   220  // MapForLookup and ValidateForRegistration options set a collection of options,
   221  // for lookup and registration purposes respectively, which can be tailored by
   222  // adding more fine-grained options, where later options override earlier
   223  // options.
   224  func New(o ...Option) *Profile {
   225  	p := &Profile{}
   226  	apply(&p.options, o)
   227  	return p
   228  }
   229  
   230  // ToASCII converts a domain or domain label to its ASCII form. For example,
   231  // ToASCII("bücher.example.com") is "xn--bcher-kva.example.com", and
   232  // ToASCII("golang") is "golang". If an error is encountered it will return
   233  // an error and a (partially) processed result.
   234  func (p *Profile) ToASCII(s string) (string, error) {
   235  	return p.process(s, true)
   236  }
   237  
   238  // ToUnicode converts a domain or domain label to its Unicode form. For example,
   239  // ToUnicode("xn--bcher-kva.example.com") is "bücher.example.com", and
   240  // ToUnicode("golang") is "golang". If an error is encountered it will return
   241  // an error and a (partially) processed result.
   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  // String reports a string with a description of the profile for debugging
   249  // purposes. The string format may change with different versions.
   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  // Transitional processing is disabled by default as of Go 1.18.
   273  // https://golang.org/issue/47510
   274  const transitionalLookup = false
   275  
   276  var (
   277  	// Punycode is a Profile that does raw punycode processing with a minimum
   278  	// of validation.
   279  	Punycode *Profile = punycode
   280  
   281  	// Lookup is the recommended profile for looking up domain names, according
   282  	// to Section 5 of RFC 5891. The exact configuration of this profile may
   283  	// change over time.
   284  	Lookup *Profile = lookup
   285  
   286  	// Display is the recommended profile for displaying domain names.
   287  	// The configuration of this profile may change over time.
   288  	Display *Profile = display
   289  
   290  	// Registration is the recommended profile for checking whether a given
   291  	// IDN is valid for registration, according to Section 4 of RFC 5891.
   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  	// TODO: profiles
   326  	// Register: recommended for approving domain names: don't do any mappings
   327  	// but rather reject on invalid input. Bundle or block deviation characters.
   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  // code16 returns old for Unicode < 16, new for Unicode >= 16.
   348  func code16(old, new string) string {
   349  	if unicode16 {
   350  		return new
   351  	}
   352  	return old
   353  }
   354  
   355  // process10 implements the algorithm described in section 4 of UTS #46.
   356  // It implements both the Unicode 10 algorithm
   357  // (https://www.unicode.org/reports/tr46/tr46-19.html)
   358  // and the Unicode 16 algorithm
   359  // (https://www.unicode.org/reports/tr46/tr46-35.html)
   360  // depending on unicode16, which in turn depends on unicode.Version.
   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  	// Remove leading empty labels.
   368  	if p.removeLeadingDots {
   369  		for ; len(s) > 0 && s[0] == '.'; s = s[1:] {
   370  		}
   371  	}
   372  	// TODO: allow for a quick check of the tables data.
   373  	// It seems like we should only create this error on ToASCII, but the
   374  	// UTS 46 conformance tests suggests we should always check this.
   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  			// Empty labels are not okay. The label iterator skips the last
   387  			// label if it is empty.
   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  				// Spec says keep the old label.
   401  				continue
   402  			}
   403  			if err == nil && len(u) > 0 && isASCII(u) {
   404  				// UTS 43 pre-revision 33 doesn't classify a xn-- label
   405  				// which contains only ASCII characters as an error,
   406  				// but that's a specification bug and a security issue.
   407  				// Always return an error in this case.
   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  				// This should be called on NonTransitional, according to the
   417  				// spec, but that currently does not have any effect. Use the
   418  				// original profile to preserve options.
   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  		// Compute the length of the domain name minus the root label and its dot.
   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  	// TODO: consider first doing a quick check to see if any of these checks
   478  	// need to be done. This will make it slower in the general case, but
   479  	// faster in the common case.
   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  	// TODO: filter need for normalization in loop below.
   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  		// Copy bytes not copied so far.
   497  		switch p.simplify(info(v).category()) {
   498  		// TODO: handle the NV8 defined in the Unicode idna data set to allow
   499  		// for strict conformance to IDNA2008.
   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  	// TODO: also store bidi info for mapped data. This is possible, but a bit
   518  	// cumbersome and not for the common case.
   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  	// combinedInfoBits contains the or-ed bits of all runes. We use this
   533  	// to derive the mayNeedNorm bit later. This may trigger normalization
   534  	// overeagerly, but it will not do so in the common case. The end result
   535  	// is another 10% saving on BenchmarkProfile for the common case.
   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  		// Copy bytes not copied so far.
   553  		switch p.simplify(info(v).category()) {
   554  		case valid:
   555  			continue
   556  		case disallowed:
   557  			// Unicode 16 delays the error until validateLabels.
   558  			// Unicode 10 gave an error now.
   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  			// Unicode 16 requires a special case to handle ẞ -> ss in transitional mode.
   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  			// drop the rune
   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  		// No changes so far.
   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  		// TODO: the punycode converters require strings as input.
   597  		s = string(b)
   598  	}
   599  	return s, bidi, err
   600  }
   601  
   602  // A labelIter allows iterating over domain name labels.
   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  // next sets the value to the next label. It skips the last label if it is empty.
   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  // acePrefix is the ASCII Compatible Encoding prefix.
   663  const acePrefix = "xn--"
   664  
   665  func (p *Profile) simplify(cat category) category {
   666  	switch cat {
   667  	case disallowedSTD3Mapped: // only happens for pre-Unicode 16
   668  		if p.useSTD3Rules {
   669  			cat = disallowed
   670  		} else {
   671  			cat = mapped
   672  		}
   673  	case disallowedSTD3Valid: // only happens for pre-Unicode 16
   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  		// TODO: handle V2008
   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  	// TODO: detect whether string may have to be normalized in the following
   695  	// loop.
   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, // no-op as we can't accept joiners here
   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  // allowedSTD3 reports whether r is a rune that can appear in a domain name
   773  // according to STD3. We allow all non-ASCII runes and then letters, digits, hyphens.
   774  // We also add dot so that this can be run against the whole name and not just
   775  // a single name element (label). The surrounding code checks dots well enough.
   776  func allowedSTD3(r rune) bool {
   777  	return r >= 0x80 || 'a' <= r && r <= 'z' || '0' <= r && r <= '9' || r == '-' || r == '.'
   778  }
   779  
   780  // validateLabel validates the criteria from Section 4.1. Item 1, 4, and 6 are
   781  // already implicitly satisfied by the overall implementation.
   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  	// Unicode 16's TR 46 delays the rune validity checks until after the label is decoded.
   799  	// (validateAndMap did not reject them earlier.)
   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 // p.checkJoiners is only set if trie is set.
   821  	// TODO: merge the use of this in the trie.
   822  	v, sz := trie.lookupString(s)
   823  	x := info(v)
   824  	if x.isModifier() {
   825  		return labelError{s, code16("V5", "V6")}
   826  	}
   827  	// Quickly return in the absence of zero-width (non) joiners.
   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  // appendMapping appends the mapping for the respective rune. isMapped must be
   866  // true. A mapping is a categorization of a rune as defined in UTS #46.
   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  		// TODO: support and handle two-byte inline masks
   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