Home | History | Annotate | Download | only in idna
      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 http://www.unicode.org/reports/tr46.
     14 // See http://unicode.org/cldr/utility/idna.jsp for a visualization of the
     15 // differences between these two standards.
     16 package idna // import "golang_org/x/text/internal/export/idna"
     17 
     18 import (
     19 	"fmt"
     20 	"strings"
     21 	"unicode/utf8"
     22 
     23 	"golang_org/x/text/secure/bidirule"
     24 	"golang_org/x/text/unicode/bidi"
     25 	"golang_org/x/text/unicode/norm"
     26 )
     27 
     28 // NOTE: Unlike common practice in Go APIs, the functions will return a
     29 // sanitized domain name in case of errors. Browsers sometimes use a partially
     30 // evaluated string as lookup.
     31 // TODO: the current error handling is, in my opinion, the least opinionated.
     32 // Other strategies are also viable, though:
     33 // Option 1) Return an empty string in case of error, but allow the user to
     34 //    specify explicitly which errors to ignore.
     35 // Option 2) Return the partially evaluated string if it is itself a valid
     36 //    string, otherwise return the empty string in case of error.
     37 // Option 3) Option 1 and 2.
     38 // Option 4) Always return an empty string for now and implement Option 1 as
     39 //    needed, and document that the return string may not be empty in case of
     40 //    error in the future.
     41 // I think Option 1 is best, but it is quite opinionated.
     42 
     43 // ToASCII is a wrapper for Punycode.ToASCII.
     44 func ToASCII(s string) (string, error) {
     45 	return Punycode.process(s, true)
     46 }
     47 
     48 // ToUnicode is a wrapper for Punycode.ToUnicode.
     49 func ToUnicode(s string) (string, error) {
     50 	return Punycode.process(s, false)
     51 }
     52 
     53 // An Option configures a Profile at creation time.
     54 type Option func(*options)
     55 
     56 // Transitional sets a Profile to use the Transitional mapping as defined in UTS
     57 // #46. This will cause, for example, "" to be mapped to "ss". Using the
     58 // transitional mapping provides a compromise between IDNA2003 and IDNA2008
     59 // compatibility. It is used by most browsers when resolving domain names. This
     60 // option is only meaningful if combined with MapForLookup.
     61 func Transitional(transitional bool) Option {
     62 	return func(o *options) { o.transitional = true }
     63 }
     64 
     65 // VerifyDNSLength sets whether a Profile should fail if any of the IDN parts
     66 // are longer than allowed by the RFC.
     67 func VerifyDNSLength(verify bool) Option {
     68 	return func(o *options) { o.verifyDNSLength = verify }
     69 }
     70 
     71 // RemoveLeadingDots removes leading label separators. Leading runes that map to
     72 // dots, such as U+3002 IDEOGRAPHIC FULL STOP, are removed as well.
     73 //
     74 // This is the behavior suggested by the UTS #46 and is adopted by some
     75 // browsers.
     76 func RemoveLeadingDots(remove bool) Option {
     77 	return func(o *options) { o.removeLeadingDots = remove }
     78 }
     79 
     80 // ValidateLabels sets whether to check the mandatory label validation criteria
     81 // as defined in Section 5.4 of RFC 5891. This includes testing for correct use
     82 // of hyphens ('-'), normalization, validity of runes, and the context rules.
     83 func ValidateLabels(enable bool) Option {
     84 	return func(o *options) {
     85 		// Don't override existing mappings, but set one that at least checks
     86 		// normalization if it is not set.
     87 		if o.mapping == nil && enable {
     88 			o.mapping = normalize
     89 		}
     90 		o.trie = trie
     91 		o.validateLabels = enable
     92 		o.fromPuny = validateFromPunycode
     93 	}
     94 }
     95 
     96 // StrictDomainName limits the set of permissible ASCII characters to those
     97 // allowed in domain names as defined in RFC 1034 (A-Z, a-z, 0-9 and the
     98 // hyphen). This is set by default for MapForLookup and ValidateForRegistration.
     99 //
    100 // This option is useful, for instance, for browsers that allow characters
    101 // outside this range, for example a '_' (U+005F LOW LINE). See
    102 // http://www.rfc-editor.org/std/std3.txt for more details This option
    103 // corresponds to the UseSTD3ASCIIRules option in UTS #46.
    104 func StrictDomainName(use bool) Option {
    105 	return func(o *options) {
    106 		o.trie = trie
    107 		o.useSTD3Rules = use
    108 		o.fromPuny = validateFromPunycode
    109 	}
    110 }
    111 
    112 // NOTE: the following options pull in tables. The tables should not be linked
    113 // in as long as the options are not used.
    114 
    115 // BidiRule enables the Bidi rule as defined in RFC 5893. Any application
    116 // that relies on proper validation of labels should include this rule.
    117 func BidiRule() Option {
    118 	return func(o *options) { o.bidirule = bidirule.ValidString }
    119 }
    120 
    121 // ValidateForRegistration sets validation options to verify that a given IDN is
    122 // properly formatted for registration as defined by Section 4 of RFC 5891.
    123 func ValidateForRegistration() Option {
    124 	return func(o *options) {
    125 		o.mapping = validateRegistration
    126 		StrictDomainName(true)(o)
    127 		ValidateLabels(true)(o)
    128 		VerifyDNSLength(true)(o)
    129 		BidiRule()(o)
    130 	}
    131 }
    132 
    133 // MapForLookup sets validation and mapping options such that a given IDN is
    134 // transformed for domain name lookup according to the requirements set out in
    135 // Section 5 of RFC 5891. The mappings follow the recommendations of RFC 5894,
    136 // RFC 5895 and UTS 46. It does not add the Bidi Rule. Use the BidiRule option
    137 // to add this check.
    138 //
    139 // The mappings include normalization and mapping case, width and other
    140 // compatibility mappings.
    141 func MapForLookup() Option {
    142 	return func(o *options) {
    143 		o.mapping = validateAndMap
    144 		StrictDomainName(true)(o)
    145 		ValidateLabels(true)(o)
    146 	}
    147 }
    148 
    149 type options struct {
    150 	transitional      bool
    151 	useSTD3Rules      bool
    152 	validateLabels    bool
    153 	verifyDNSLength   bool
    154 	removeLeadingDots bool
    155 
    156 	trie *idnaTrie
    157 
    158 	// fromPuny calls validation rules when converting A-labels to U-labels.
    159 	fromPuny func(p *Profile, s string) error
    160 
    161 	// mapping implements a validation and mapping step as defined in RFC 5895
    162 	// or UTS 46, tailored to, for example, domain registration or lookup.
    163 	mapping func(p *Profile, s string) (mapped string, isBidi bool, err error)
    164 
    165 	// bidirule, if specified, checks whether s conforms to the Bidi Rule
    166 	// defined in RFC 5893.
    167 	bidirule func(s string) bool
    168 }
    169 
    170 // A Profile defines the configuration of an IDNA mapper.
    171 type Profile struct {
    172 	options
    173 }
    174 
    175 func apply(o *options, opts []Option) {
    176 	for _, f := range opts {
    177 		f(o)
    178 	}
    179 }
    180 
    181 // New creates a new Profile.
    182 //
    183 // With no options, the returned Profile is the most permissive and equals the
    184 // Punycode Profile. Options can be passed to further restrict the Profile. The
    185 // MapForLookup and ValidateForRegistration options set a collection of options,
    186 // for lookup and registration purposes respectively, which can be tailored by
    187 // adding more fine-grained options, where later options override earlier
    188 // options.
    189 func New(o ...Option) *Profile {
    190 	p := &Profile{}
    191 	apply(&p.options, o)
    192 	return p
    193 }
    194 
    195 // ToASCII converts a domain or domain label to its ASCII form. For example,
    196 // ToASCII("bcher.example.com") is "xn--bcher-kva.example.com", and
    197 // ToASCII("golang") is "golang". If an error is encountered it will return
    198 // an error and a (partially) processed result.
    199 func (p *Profile) ToASCII(s string) (string, error) {
    200 	return p.process(s, true)
    201 }
    202 
    203 // ToUnicode converts a domain or domain label to its Unicode form. For example,
    204 // ToUnicode("xn--bcher-kva.example.com") is "bcher.example.com", and
    205 // ToUnicode("golang") is "golang". If an error is encountered it will return
    206 // an error and a (partially) processed result.
    207 func (p *Profile) ToUnicode(s string) (string, error) {
    208 	pp := *p
    209 	pp.transitional = false
    210 	return pp.process(s, false)
    211 }
    212 
    213 // String reports a string with a description of the profile for debugging
    214 // purposes. The string format may change with different versions.
    215 func (p *Profile) String() string {
    216 	s := ""
    217 	if p.transitional {
    218 		s = "Transitional"
    219 	} else {
    220 		s = "NonTransitional"
    221 	}
    222 	if p.useSTD3Rules {
    223 		s += ":UseSTD3Rules"
    224 	}
    225 	if p.validateLabels {
    226 		s += ":ValidateLabels"
    227 	}
    228 	if p.verifyDNSLength {
    229 		s += ":VerifyDNSLength"
    230 	}
    231 	return s
    232 }
    233 
    234 var (
    235 	// Punycode is a Profile that does raw punycode processing with a minimum
    236 	// of validation.
    237 	Punycode *Profile = punycode
    238 
    239 	// Lookup is the recommended profile for looking up domain names, according
    240 	// to Section 5 of RFC 5891. The exact configuration of this profile may
    241 	// change over time.
    242 	Lookup *Profile = lookup
    243 
    244 	// Display is the recommended profile for displaying domain names.
    245 	// The configuration of this profile may change over time.
    246 	Display *Profile = display
    247 
    248 	// Registration is the recommended profile for checking whether a given
    249 	// IDN is valid for registration, according to Section 4 of RFC 5891.
    250 	Registration *Profile = registration
    251 
    252 	punycode = &Profile{}
    253 	lookup   = &Profile{options{
    254 		transitional:   true,
    255 		useSTD3Rules:   true,
    256 		validateLabels: true,
    257 		trie:           trie,
    258 		fromPuny:       validateFromPunycode,
    259 		mapping:        validateAndMap,
    260 		bidirule:       bidirule.ValidString,
    261 	}}
    262 	display = &Profile{options{
    263 		useSTD3Rules:   true,
    264 		validateLabels: true,
    265 		trie:           trie,
    266 		fromPuny:       validateFromPunycode,
    267 		mapping:        validateAndMap,
    268 		bidirule:       bidirule.ValidString,
    269 	}}
    270 	registration = &Profile{options{
    271 		useSTD3Rules:    true,
    272 		validateLabels:  true,
    273 		verifyDNSLength: true,
    274 		trie:            trie,
    275 		fromPuny:        validateFromPunycode,
    276 		mapping:         validateRegistration,
    277 		bidirule:        bidirule.ValidString,
    278 	}}
    279 
    280 	// TODO: profiles
    281 	// Register: recommended for approving domain names: don't do any mappings
    282 	// but rather reject on invalid input. Bundle or block deviation characters.
    283 )
    284 
    285 type labelError struct{ label, code_ string }
    286 
    287 func (e labelError) code() string { return e.code_ }
    288 func (e labelError) Error() string {
    289 	return fmt.Sprintf("idna: invalid label %q", e.label)
    290 }
    291 
    292 type runeError rune
    293 
    294 func (e runeError) code() string { return "P1" }
    295 func (e runeError) Error() string {
    296 	return fmt.Sprintf("idna: disallowed rune %U", e)
    297 }
    298 
    299 // process implements the algorithm described in section 4 of UTS #46,
    300 // see http://www.unicode.org/reports/tr46.
    301 func (p *Profile) process(s string, toASCII bool) (string, error) {
    302 	var err error
    303 	var isBidi bool
    304 	if p.mapping != nil {
    305 		s, isBidi, err = p.mapping(p, s)
    306 	}
    307 	// Remove leading empty labels.
    308 	if p.removeLeadingDots {
    309 		for ; len(s) > 0 && s[0] == '.'; s = s[1:] {
    310 		}
    311 	}
    312 	// TODO: allow for a quick check of the tables data.
    313 	// It seems like we should only create this error on ToASCII, but the
    314 	// UTS 46 conformance tests suggests we should always check this.
    315 	if err == nil && p.verifyDNSLength && s == "" {
    316 		err = &labelError{s, "A4"}
    317 	}
    318 	labels := labelIter{orig: s}
    319 	for ; !labels.done(); labels.next() {
    320 		label := labels.label()
    321 		if label == "" {
    322 			// Empty labels are not okay. The label iterator skips the last
    323 			// label if it is empty.
    324 			if err == nil && p.verifyDNSLength {
    325 				err = &labelError{s, "A4"}
    326 			}
    327 			continue
    328 		}
    329 		if strings.HasPrefix(label, acePrefix) {
    330 			u, err2 := decode(label[len(acePrefix):])
    331 			if err2 != nil {
    332 				if err == nil {
    333 					err = err2
    334 				}
    335 				// Spec says keep the old label.
    336 				continue
    337 			}
    338 			isBidi = isBidi || bidirule.DirectionString(u) != bidi.LeftToRight
    339 			labels.set(u)
    340 			if err == nil && p.validateLabels {
    341 				err = p.fromPuny(p, u)
    342 			}
    343 			if err == nil {
    344 				// This should be called on NonTransitional, according to the
    345 				// spec, but that currently does not have any effect. Use the
    346 				// original profile to preserve options.
    347 				err = p.validateLabel(u)
    348 			}
    349 		} else if err == nil {
    350 			err = p.validateLabel(label)
    351 		}
    352 	}
    353 	if isBidi && p.bidirule != nil && err == nil {
    354 		for labels.reset(); !labels.done(); labels.next() {
    355 			if !p.bidirule(labels.label()) {
    356 				err = &labelError{s, "B"}
    357 				break
    358 			}
    359 		}
    360 	}
    361 	if toASCII {
    362 		for labels.reset(); !labels.done(); labels.next() {
    363 			label := labels.label()
    364 			if !ascii(label) {
    365 				a, err2 := encode(acePrefix, label)
    366 				if err == nil {
    367 					err = err2
    368 				}
    369 				label = a
    370 				labels.set(a)
    371 			}
    372 			n := len(label)
    373 			if p.verifyDNSLength && err == nil && (n == 0 || n > 63) {
    374 				err = &labelError{label, "A4"}
    375 			}
    376 		}
    377 	}
    378 	s = labels.result()
    379 	if toASCII && p.verifyDNSLength && err == nil {
    380 		// Compute the length of the domain name minus the root label and its dot.
    381 		n := len(s)
    382 		if n > 0 && s[n-1] == '.' {
    383 			n--
    384 		}
    385 		if len(s) < 1 || n > 253 {
    386 			err = &labelError{s, "A4"}
    387 		}
    388 	}
    389 	return s, err
    390 }
    391 
    392 func normalize(p *Profile, s string) (mapped string, isBidi bool, err error) {
    393 	// TODO: consider first doing a quick check to see if any of these checks
    394 	// need to be done. This will make it slower in the general case, but
    395 	// faster in the common case.
    396 	mapped = norm.NFC.String(s)
    397 	isBidi = bidirule.DirectionString(mapped) == bidi.RightToLeft
    398 	return mapped, isBidi, nil
    399 }
    400 
    401 func validateRegistration(p *Profile, s string) (idem string, bidi bool, err error) {
    402 	// TODO: filter need for normalization in loop below.
    403 	if !norm.NFC.IsNormalString(s) {
    404 		return s, false, &labelError{s, "V1"}
    405 	}
    406 	for i := 0; i < len(s); {
    407 		v, sz := trie.lookupString(s[i:])
    408 		if sz == 0 {
    409 			return s, bidi, runeError(utf8.RuneError)
    410 		}
    411 		bidi = bidi || info(v).isBidi(s[i:])
    412 		// Copy bytes not copied so far.
    413 		switch p.simplify(info(v).category()) {
    414 		// TODO: handle the NV8 defined in the Unicode idna data set to allow
    415 		// for strict conformance to IDNA2008.
    416 		case valid, deviation:
    417 		case disallowed, mapped, unknown, ignored:
    418 			r, _ := utf8.DecodeRuneInString(s[i:])
    419 			return s, bidi, runeError(r)
    420 		}
    421 		i += sz
    422 	}
    423 	return s, bidi, nil
    424 }
    425 
    426 func (c info) isBidi(s string) bool {
    427 	if !c.isMapped() {
    428 		return c&attributesMask == rtl
    429 	}
    430 	// TODO: also store bidi info for mapped data. This is possible, but a bit
    431 	// cumbersome and not for the common case.
    432 	p, _ := bidi.LookupString(s)
    433 	switch p.Class() {
    434 	case bidi.R, bidi.AL, bidi.AN:
    435 		return true
    436 	}
    437 	return false
    438 }
    439 
    440 func validateAndMap(p *Profile, s string) (vm string, bidi bool, err error) {
    441 	var (
    442 		b []byte
    443 		k int
    444 	)
    445 	// combinedInfoBits contains the or-ed bits of all runes. We use this
    446 	// to derive the mayNeedNorm bit later. This may trigger normalization
    447 	// overeagerly, but it will not do so in the common case. The end result
    448 	// is another 10% saving on BenchmarkProfile for the common case.
    449 	var combinedInfoBits info
    450 	for i := 0; i < len(s); {
    451 		v, sz := trie.lookupString(s[i:])
    452 		if sz == 0 {
    453 			b = append(b, s[k:i]...)
    454 			b = append(b, "\ufffd"...)
    455 			k = len(s)
    456 			if err == nil {
    457 				err = runeError(utf8.RuneError)
    458 			}
    459 			break
    460 		}
    461 		combinedInfoBits |= info(v)
    462 		bidi = bidi || info(v).isBidi(s[i:])
    463 		start := i
    464 		i += sz
    465 		// Copy bytes not copied so far.
    466 		switch p.simplify(info(v).category()) {
    467 		case valid:
    468 			continue
    469 		case disallowed:
    470 			if err == nil {
    471 				r, _ := utf8.DecodeRuneInString(s[start:])
    472 				err = runeError(r)
    473 			}
    474 			continue
    475 		case mapped, deviation:
    476 			b = append(b, s[k:start]...)
    477 			b = info(v).appendMapping(b, s[start:i])
    478 		case ignored:
    479 			b = append(b, s[k:start]...)
    480 			// drop the rune
    481 		case unknown:
    482 			b = append(b, s[k:start]...)
    483 			b = append(b, "\ufffd"...)
    484 		}
    485 		k = i
    486 	}
    487 	if k == 0 {
    488 		// No changes so far.
    489 		if combinedInfoBits&mayNeedNorm != 0 {
    490 			s = norm.NFC.String(s)
    491 		}
    492 	} else {
    493 		b = append(b, s[k:]...)
    494 		if norm.NFC.QuickSpan(b) != len(b) {
    495 			b = norm.NFC.Bytes(b)
    496 		}
    497 		// TODO: the punycode converters require strings as input.
    498 		s = string(b)
    499 	}
    500 	return s, bidi, err
    501 }
    502 
    503 // A labelIter allows iterating over domain name labels.
    504 type labelIter struct {
    505 	orig     string
    506 	slice    []string
    507 	curStart int
    508 	curEnd   int
    509 	i        int
    510 }
    511 
    512 func (l *labelIter) reset() {
    513 	l.curStart = 0
    514 	l.curEnd = 0
    515 	l.i = 0
    516 }
    517 
    518 func (l *labelIter) done() bool {
    519 	return l.curStart >= len(l.orig)
    520 }
    521 
    522 func (l *labelIter) result() string {
    523 	if l.slice != nil {
    524 		return strings.Join(l.slice, ".")
    525 	}
    526 	return l.orig
    527 }
    528 
    529 func (l *labelIter) label() string {
    530 	if l.slice != nil {
    531 		return l.slice[l.i]
    532 	}
    533 	p := strings.IndexByte(l.orig[l.curStart:], '.')
    534 	l.curEnd = l.curStart + p
    535 	if p == -1 {
    536 		l.curEnd = len(l.orig)
    537 	}
    538 	return l.orig[l.curStart:l.curEnd]
    539 }
    540 
    541 // next sets the value to the next label. It skips the last label if it is empty.
    542 func (l *labelIter) next() {
    543 	l.i++
    544 	if l.slice != nil {
    545 		if l.i >= len(l.slice) || l.i == len(l.slice)-1 && l.slice[l.i] == "" {
    546 			l.curStart = len(l.orig)
    547 		}
    548 	} else {
    549 		l.curStart = l.curEnd + 1
    550 		if l.curStart == len(l.orig)-1 && l.orig[l.curStart] == '.' {
    551 			l.curStart = len(l.orig)
    552 		}
    553 	}
    554 }
    555 
    556 func (l *labelIter) set(s string) {
    557 	if l.slice == nil {
    558 		l.slice = strings.Split(l.orig, ".")
    559 	}
    560 	l.slice[l.i] = s
    561 }
    562 
    563 // acePrefix is the ASCII Compatible Encoding prefix.
    564 const acePrefix = "xn--"
    565 
    566 func (p *Profile) simplify(cat category) category {
    567 	switch cat {
    568 	case disallowedSTD3Mapped:
    569 		if p.useSTD3Rules {
    570 			cat = disallowed
    571 		} else {
    572 			cat = mapped
    573 		}
    574 	case disallowedSTD3Valid:
    575 		if p.useSTD3Rules {
    576 			cat = disallowed
    577 		} else {
    578 			cat = valid
    579 		}
    580 	case deviation:
    581 		if !p.transitional {
    582 			cat = valid
    583 		}
    584 	case validNV8, validXV8:
    585 		// TODO: handle V2008
    586 		cat = valid
    587 	}
    588 	return cat
    589 }
    590 
    591 func validateFromPunycode(p *Profile, s string) error {
    592 	if !norm.NFC.IsNormalString(s) {
    593 		return &labelError{s, "V1"}
    594 	}
    595 	// TODO: detect whether string may have to be normalized in the following
    596 	// loop.
    597 	for i := 0; i < len(s); {
    598 		v, sz := trie.lookupString(s[i:])
    599 		if sz == 0 {
    600 			return runeError(utf8.RuneError)
    601 		}
    602 		if c := p.simplify(info(v).category()); c != valid && c != deviation {
    603 			return &labelError{s, "V6"}
    604 		}
    605 		i += sz
    606 	}
    607 	return nil
    608 }
    609 
    610 const (
    611 	zwnj = "\u200c"
    612 	zwj  = "\u200d"
    613 )
    614 
    615 type joinState int8
    616 
    617 const (
    618 	stateStart joinState = iota
    619 	stateVirama
    620 	stateBefore
    621 	stateBeforeVirama
    622 	stateAfter
    623 	stateFAIL
    624 )
    625 
    626 var joinStates = [][numJoinTypes]joinState{
    627 	stateStart: {
    628 		joiningL:   stateBefore,
    629 		joiningD:   stateBefore,
    630 		joinZWNJ:   stateFAIL,
    631 		joinZWJ:    stateFAIL,
    632 		joinVirama: stateVirama,
    633 	},
    634 	stateVirama: {
    635 		joiningL: stateBefore,
    636 		joiningD: stateBefore,
    637 	},
    638 	stateBefore: {
    639 		joiningL:   stateBefore,
    640 		joiningD:   stateBefore,
    641 		joiningT:   stateBefore,
    642 		joinZWNJ:   stateAfter,
    643 		joinZWJ:    stateFAIL,
    644 		joinVirama: stateBeforeVirama,
    645 	},
    646 	stateBeforeVirama: {
    647 		joiningL: stateBefore,
    648 		joiningD: stateBefore,
    649 		joiningT: stateBefore,
    650 	},
    651 	stateAfter: {
    652 		joiningL:   stateFAIL,
    653 		joiningD:   stateBefore,
    654 		joiningT:   stateAfter,
    655 		joiningR:   stateStart,
    656 		joinZWNJ:   stateFAIL,
    657 		joinZWJ:    stateFAIL,
    658 		joinVirama: stateAfter, // no-op as we can't accept joiners here
    659 	},
    660 	stateFAIL: {
    661 		0:          stateFAIL,
    662 		joiningL:   stateFAIL,
    663 		joiningD:   stateFAIL,
    664 		joiningT:   stateFAIL,
    665 		joiningR:   stateFAIL,
    666 		joinZWNJ:   stateFAIL,
    667 		joinZWJ:    stateFAIL,
    668 		joinVirama: stateFAIL,
    669 	},
    670 }
    671 
    672 // validateLabel validates the criteria from Section 4.1. Item 1, 4, and 6 are
    673 // already implicitly satisfied by the overall implementation.
    674 func (p *Profile) validateLabel(s string) (err error) {
    675 	if s == "" {
    676 		if p.verifyDNSLength {
    677 			return &labelError{s, "A4"}
    678 		}
    679 		return nil
    680 	}
    681 	if !p.validateLabels {
    682 		return nil
    683 	}
    684 	trie := p.trie // p.validateLabels is only set if trie is set.
    685 	if len(s) > 4 && s[2] == '-' && s[3] == '-' {
    686 		return &labelError{s, "V2"}
    687 	}
    688 	if s[0] == '-' || s[len(s)-1] == '-' {
    689 		return &labelError{s, "V3"}
    690 	}
    691 	// TODO: merge the use of this in the trie.
    692 	v, sz := trie.lookupString(s)
    693 	x := info(v)
    694 	if x.isModifier() {
    695 		return &labelError{s, "V5"}
    696 	}
    697 	// Quickly return in the absence of zero-width (non) joiners.
    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