Skip to content

Commit e3aa4ad

Browse files
committed
language: allow variable number of types per key in -u- extension
This also fixes CVE-2020-28851. This was an off-by one error, but is fixed by handling all cases according to the spec. These valid case seem to be not used in practice much, if at all, but the main benefit is that it makes all valid BCP 47 language tags also valid -u extensions. Fixing the code to handle BCP 47 results in cleaner and seemingly more robust code. The main difference is as follows. The old impementation assumed a -u- extension of the form: <tag> "-u" { "-" <attr> } { "-" <key> "-" <type> } [ <otherExtensions> ] where <attr> and <type> are of length 3-8 and a <key> is of length 2. According to the spec, though, the format is <tag> "-u" { "-" <attr> } { "-" <key> { "-" <type> } } [ <otherExtensions> ] So every key may be associated with zero or more types, instead of exactly one. The new code now handles this. The language.Tag.TypeForKey method is now defined to only return the first entry or nothing at all. This is for backwards compatibilty reasons. Fixes golang/go#42535 Change-Id: I23aec4e1c4d8807fc2ffc0eb3a08de2d8150219f Reviewed-on: https://go-review.googlesource.com/c/text/+/293549 Trust: Marcel van Lohuizen <[email protected]> Run-TryBot: Marcel van Lohuizen <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Russ Cox <[email protected]>
1 parent 8f690f2 commit e3aa4ad

File tree

7 files changed

+108
-86
lines changed

7 files changed

+108
-86
lines changed

internal/language/language.go

Lines changed: 43 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -303,9 +303,17 @@ func (t Tag) Extensions() []string {
303303
// are of the allowed values defined for the Unicode locale extension ('u') in
304304
// https://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers.
305305
// TypeForKey will traverse the inheritance chain to get the correct value.
306+
//
307+
// If there are multiple types associated with a key, only the first will be
308+
// returned. If there is no type associated with a key, it returns the empty
309+
// string.
306310
func (t Tag) TypeForKey(key string) string {
307-
if start, end, _ := t.findTypeForKey(key); end != start {
308-
return t.str[start:end]
311+
if _, start, end, _ := t.findTypeForKey(key); end != start {
312+
s := t.str[start:end]
313+
if p := strings.IndexByte(s, '-'); p >= 0 {
314+
s = s[:p]
315+
}
316+
return s
309317
}
310318
return ""
311319
}
@@ -329,13 +337,13 @@ func (t Tag) SetTypeForKey(key, value string) (Tag, error) {
329337

330338
// Remove the setting if value is "".
331339
if value == "" {
332-
start, end, _ := t.findTypeForKey(key)
333-
if start != end {
334-
// Remove key tag and leading '-'.
335-
start -= 4
336-
340+
start, sep, end, _ := t.findTypeForKey(key)
341+
if start != sep {
337342
// Remove a possible empty extension.
338-
if (end == len(t.str) || t.str[end+2] == '-') && t.str[start-2] == '-' {
343+
switch {
344+
case t.str[start-2] != '-': // has previous elements.
345+
case end == len(t.str), // end of string
346+
end+2 < len(t.str) && t.str[end+2] == '-': // end of extension
339347
start -= 2
340348
}
341349
if start == int(t.pVariant) && end == len(t.str) {
@@ -381,14 +389,14 @@ func (t Tag) SetTypeForKey(key, value string) (Tag, error) {
381389
t.str = string(buf[:uStart+len(b)])
382390
} else {
383391
s := t.str
384-
start, end, hasExt := t.findTypeForKey(key)
385-
if start == end {
392+
start, sep, end, hasExt := t.findTypeForKey(key)
393+
if start == sep {
386394
if hasExt {
387395
b = b[2:]
388396
}
389-
t.str = fmt.Sprintf("%s-%s%s", s[:start], b, s[end:])
397+
t.str = fmt.Sprintf("%s-%s%s", s[:sep], b, s[end:])
390398
} else {
391-
t.str = fmt.Sprintf("%s%s%s", s[:start], value, s[end:])
399+
t.str = fmt.Sprintf("%s-%s%s", s[:start+3], value, s[end:])
392400
}
393401
}
394402
return t, nil
@@ -399,21 +407,21 @@ func (t Tag) SetTypeForKey(key, value string) (Tag, error) {
399407
// wasn't found. The hasExt return value reports whether an -u extension was present.
400408
// Note: the extensions are typically very small and are likely to contain
401409
// only one key-type pair.
402-
func (t Tag) findTypeForKey(key string) (start, end int, hasExt bool) {
410+
func (t Tag) findTypeForKey(key string) (start, sep, end int, hasExt bool) {
403411
p := int(t.pExt)
404412
if len(key) != 2 || p == len(t.str) || p == 0 {
405-
return p, p, false
413+
return p, p, p, false
406414
}
407415
s := t.str
408416

409417
// Find the correct extension.
410418
for p++; s[p] != 'u'; p++ {
411419
if s[p] > 'u' {
412420
p--
413-
return p, p, false
421+
return p, p, p, false
414422
}
415423
if p = nextExtension(s, p); p == len(s) {
416-
return len(s), len(s), false
424+
return len(s), len(s), len(s), false
417425
}
418426
}
419427
// Proceed to the hyphen following the extension name.
@@ -424,40 +432,28 @@ func (t Tag) findTypeForKey(key string) (start, end int, hasExt bool) {
424432

425433
// Iterate over keys until we get the end of a section.
426434
for {
427-
// p points to the hyphen preceding the current token.
428-
if p3 := p + 3; s[p3] == '-' {
429-
// Found a key.
430-
// Check whether we just processed the key that was requested.
431-
if curKey == key {
432-
return start, p, true
433-
}
434-
// Set to the next key and continue scanning type tokens.
435-
curKey = s[p+1 : p3]
436-
if curKey > key {
437-
return p, p, true
438-
}
439-
// Start of the type token sequence.
440-
start = p + 4
441-
// A type is at least 3 characters long.
442-
p += 7 // 4 + 3
443-
} else {
444-
// Attribute or type, which is at least 3 characters long.
445-
p += 4
446-
}
447-
// p points past the third character of a type or attribute.
448-
max := p + 5 // maximum length of token plus hyphen.
449-
if len(s) < max {
450-
max = len(s)
435+
end = p
436+
for p++; p < len(s) && s[p] != '-'; p++ {
451437
}
452-
for ; p < max && s[p] != '-'; p++ {
438+
n := p - end - 1
439+
if n <= 2 && curKey == key {
440+
if sep < end {
441+
sep++
442+
}
443+
return start, sep, end, true
453444
}
454-
// Bail if we have exhausted all tokens or if the next token starts
455-
// a new extension.
456-
if p == len(s) || s[p+2] == '-' {
457-
if curKey == key {
458-
return start, p, true
445+
switch n {
446+
case 0, // invalid string
447+
1: // next extension
448+
return end, end, end, true
449+
case 2:
450+
// next key
451+
curKey = s[end+1 : p]
452+
if curKey > key {
453+
return end, end, end, true
459454
}
460-
return p, p, true
455+
start = end
456+
sep = p
461457
}
462458
}
463459
}

internal/language/language_test.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,9 @@ func TestSetTypeForKey(t *testing.T) {
432432
{"co", "pinyin", "en-u-co-phonebk-cu-xau", "en-u-co-pinyin-cu-xau", false},
433433
{"co", "pinyin", "en-u-co-phonebk-v-xx", "en-u-co-pinyin-v-xx", false},
434434
{"co", "pinyin", "en-u-co-phonebk-x-x", "en-u-co-pinyin-x-x", false},
435+
{"co", "pinyin", "en-u-co-x-x", "en-u-co-pinyin-x-x", false},
435436
{"nu", "arabic", "en-u-co-phonebk-nu-vaai", "en-u-co-phonebk-nu-arabic", false},
437+
{"nu", "arabic", "en-u-co-phonebk-nu", "en-u-co-phonebk-nu-arabic", false},
436438
// add to existing -u extension
437439
{"co", "pinyin", "en-u-ca-gregory", "en-u-ca-gregory-co-pinyin", false},
438440
{"co", "pinyin", "en-u-ca-gregory-nu-vaai", "en-u-ca-gregory-co-pinyin-nu-vaai", false},
@@ -441,8 +443,12 @@ func TestSetTypeForKey(t *testing.T) {
441443
{"ca", "gregory", "en-u-co-pinyin", "en-u-ca-gregory-co-pinyin", false},
442444
// remove pair
443445
{"co", "", "en-u-co-phonebk", "en", false},
446+
{"co", "", "en-u-co", "en", false},
447+
{"co", "", "en-u-co-v", "en", false},
448+
{"co", "", "en-u-co-v-", "en", false},
444449
{"co", "", "en-u-ca-gregory-co-phonebk", "en-u-ca-gregory", false},
445450
{"co", "", "en-u-co-phonebk-nu-arabic", "en-u-nu-arabic", false},
451+
{"co", "", "en-u-co-nu-arabic", "en-u-nu-arabic", false},
446452
{"co", "", "en", "en", false},
447453
// add -u extension
448454
{"co", "pinyin", "en", "en-u-co-pinyin", false},
@@ -504,6 +510,8 @@ func TestFindKeyAndType(t *testing.T) {
504510
{"cu", false, "en-a-va-v-va", "en-a-va"},
505511
{"cu", false, "en-x-a", "en"},
506512
// Tags with the -u extension.
513+
{"nu", true, "en-u-cu-nu", "en-u-cu"},
514+
{"cu", true, "en-u-cu-nu", "en-u"},
507515
{"co", true, "en-u-co-standard", "standard"},
508516
{"co", true, "yue-u-co-pinyin", "pinyin"},
509517
{"co", true, "en-u-co-abc", "abc"},
@@ -519,9 +527,9 @@ func TestFindKeyAndType(t *testing.T) {
519527
{"cu", true, "en-u-co-abc-def-nu-arabic", "en-u-co-abc-def"},
520528
}
521529
for i, tt := range tests {
522-
start, end, hasExt := Make(tt.in).findTypeForKey(tt.key)
523-
if start != end {
524-
res := tt.in[start:end]
530+
start, sep, end, hasExt := Make(tt.in).findTypeForKey(tt.key)
531+
if sep != end {
532+
res := tt.in[sep:end]
525533
if res != tt.out {
526534
t.Errorf("%d:%s: was %q; want %q", i, tt.in, res, tt.out)
527535
}

internal/language/parse.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ func (s *scanner) resizeRange(oldStart, oldEnd, newSize int) {
138138
b = make([]byte, n)
139139
copy(b, s.b[:oldStart])
140140
} else {
141-
b = s.b[:n:n]
141+
b = s.b[:n]
142142
}
143143
copy(b[end:], s.b[oldEnd:])
144144
s.b = b
@@ -483,7 +483,7 @@ func parseExtensions(scan *scanner) int {
483483
func parseExtension(scan *scanner) int {
484484
start, end := scan.start, scan.end
485485
switch scan.token[0] {
486-
case 'u':
486+
case 'u': // https://www.ietf.org/rfc/rfc6067.txt
487487
attrStart := end
488488
scan.scan()
489489
for last := []byte{}; len(scan.token) > 2; scan.scan() {
@@ -503,27 +503,29 @@ func parseExtension(scan *scanner) int {
503503
last = scan.token
504504
end = scan.end
505505
}
506+
// Scan key-type sequences. A key is of length 2 and may be followed
507+
// by 0 or more "type" subtags from 3 to the maximum of 8 letters.
506508
var last, key []byte
507509
for attrEnd := end; len(scan.token) == 2; last = key {
508510
key = scan.token
509-
keyEnd := scan.end
510-
end = scan.acceptMinSize(3)
511+
end = scan.end
512+
for scan.scan(); end < scan.end && len(scan.token) > 2; scan.scan() {
513+
end = scan.end
514+
}
511515
// TODO: check key value validity
512-
if keyEnd == end || bytes.Compare(key, last) != 1 {
516+
if bytes.Compare(key, last) != 1 || scan.err != nil {
513517
// We have an invalid key or the keys are not sorted.
514518
// Start scanning keys from scratch and reorder.
515519
p := attrEnd + 1
516520
scan.next = p
517521
keys := [][]byte{}
518522
for scan.scan(); len(scan.token) == 2; {
519-
keyStart, keyEnd := scan.start, scan.end
520-
end = scan.acceptMinSize(3)
521-
if keyEnd != end {
522-
keys = append(keys, scan.b[keyStart:end])
523-
} else {
524-
scan.setError(ErrSyntax)
525-
end = keyStart
523+
keyStart := scan.start
524+
end = scan.end
525+
for scan.scan(); end < scan.end && len(scan.token) > 2; scan.scan() {
526+
end = scan.end
526527
}
528+
keys = append(keys, scan.b[keyStart:end])
527529
}
528530
sort.Stable(bytesSort{keys, 2})
529531
if n := len(keys); n > 0 {
@@ -547,7 +549,7 @@ func parseExtension(scan *scanner) int {
547549
break
548550
}
549551
}
550-
case 't':
552+
case 't': // https://www.ietf.org/rfc/rfc6497.txt
551553
scan.scan()
552554
if n := len(scan.token); n >= 2 && n <= 3 && isAlpha(scan.token[1]) {
553555
_, end = parseTag(scan)

internal/language/parse_test.go

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,13 @@ func parseTests() []parseTest {
164164
{in: "en-9-aa-0-aa-z-bb-x-a", lang: "en", extList: []string{"0-aa", "9-aa", "z-bb", "x-a"}, changed: true},
165165
{in: "en-u-c", lang: "en", ext: "", invalid: true},
166166
{in: "en-u-co-phonebk", lang: "en", ext: "u-co-phonebk"},
167-
{in: "en-u-co-phonebk-ca", lang: "en", ext: "u-co-phonebk", invalid: true},
168-
{in: "en-u-nu-arabic-co-phonebk-ca", lang: "en", ext: "u-co-phonebk-nu-arabic", invalid: true, changed: true},
169-
{in: "en-u-nu-arabic-co-phonebk-ca-x", lang: "en", ext: "u-co-phonebk-nu-arabic", invalid: true, changed: true},
170-
{in: "en-u-nu-arabic-co-phonebk-ca-s", lang: "en", ext: "u-co-phonebk-nu-arabic", invalid: true, changed: true},
171-
{in: "en-u-nu-arabic-co-phonebk-ca-a12345678", lang: "en", ext: "u-co-phonebk-nu-arabic", invalid: true, changed: true},
172-
{in: "en-u-co-phonebook", lang: "en", ext: "", invalid: true},
173-
{in: "en-u-co-phonebook-cu-xau", lang: "en", ext: "u-cu-xau", invalid: true, changed: true},
167+
{in: "en-u-co-phonebk-ca", lang: "en", ext: "u-ca-co-phonebk", changed: true},
168+
{in: "en-u-nu-arabic-co-phonebk-ca", lang: "en", ext: "u-ca-co-phonebk-nu-arabic", changed: true},
169+
{in: "en-u-nu-arabic-co-phonebk-ca-x", lang: "en", ext: "u-ca-co-phonebk-nu-arabic", invalid: true, changed: true},
170+
{in: "en-u-nu-arabic-co-phonebk-ca-s", lang: "en", ext: "u-ca-co-phonebk-nu-arabic", invalid: true, changed: true},
171+
{in: "en-u-nu-arabic-co-phonebk-ca-a12345678", lang: "en", ext: "u-ca-co-phonebk-nu-arabic", invalid: true, changed: true},
172+
{in: "en-u-co-phonebook", lang: "en", ext: "u-co", invalid: true},
173+
{in: "en-u-co-phonebook-cu-xau", lang: "en", ext: "u-co-cu-xau", invalid: true, changed: true},
174174
{in: "en-Cyrl-u-co-phonebk", lang: "en", script: "Cyrl", ext: "u-co-phonebk"},
175175
{in: "en-US-u-co-phonebk", lang: "en", region: "US", ext: "u-co-phonebk"},
176176
{in: "en-US-u-co-phonebk-cu-xau", lang: "en", region: "US", ext: "u-co-phonebk-cu-xau"},
@@ -179,9 +179,8 @@ func parseTests() []parseTest {
179179
{in: "en-u-def-abc-cu-xua-co-phonebk", lang: "en", ext: "u-abc-def-co-phonebk-cu-xua", changed: true},
180180
{in: "en-u-def-abc", lang: "en", ext: "u-abc-def", changed: true},
181181
{in: "en-u-cu-xua-co-phonebk-a-cd", lang: "en", extList: []string{"a-cd", "u-co-phonebk-cu-xua"}, changed: true},
182-
// Invalid "u" extension. Drop invalid parts.
183-
{in: "en-u-cu-co-phonebk", lang: "en", extList: []string{"u-co-phonebk"}, invalid: true, changed: true},
184-
{in: "en-u-cu-xau-co", lang: "en", extList: []string{"u-cu-xau"}, invalid: true},
182+
{in: "en-u-cu-co-phonebk", lang: "en", extList: []string{"u-co-phonebk-cu"}, changed: true},
183+
{in: "en-u-cu-xau-co", lang: "en", extList: []string{"u-co-cu-xau"}, changed: true},
185184
// LDML spec is not specific about it, but remove duplicates and return an error if the values differ.
186185
{in: "en-u-cu-xau-co-phonebk-cu-xau", lang: "en", ext: "u-co-phonebk-cu-xau", changed: true},
187186
// No change as the result is a substring of the original!
@@ -351,8 +350,8 @@ func TestErrors(t *testing.T) {
351350
{"aa-AB", mkInvalid("AB")},
352351
// ill-formed wins over invalid.
353352
{"ac-u", ErrSyntax},
354-
{"ac-u-ca", ErrSyntax},
355-
{"ac-u-ca-co-pinyin", ErrSyntax},
353+
{"ac-u-ca", mkInvalid("ac")},
354+
{"ac-u-ca-co-pinyin", mkInvalid("ac")},
356355
{"noob", ErrSyntax},
357356
}
358357
for _, tt := range tests {

language/language.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,10 @@ func (t Tag) Extensions() []Extension {
412412
// are of the allowed values defined for the Unicode locale extension ('u') in
413413
// https://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers.
414414
// TypeForKey will traverse the inheritance chain to get the correct value.
415+
//
416+
// If there are multiple types associated with a key, only the first will be
417+
// returned. If there is no type associated with a key, it returns the empty
418+
// string.
415419
func (t Tag) TypeForKey(key string) string {
416420
if !compact.Tag(t).MayHaveExtensions() {
417421
if key != "rg" && key != "va" {

language/language_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,13 @@ func TestCanonicalize(t *testing.T) {
523523
{"en-GB-u-rg-usz", "en-GB-u-rg-usz", Raw},
524524
{"en-GB-u-rg-usz-va-posix", "en-GB-u-rg-usz-va-posix", Raw},
525525
{"en-GB-u-rg-usz-co-phonebk", "en-GB-u-co-phonebk-rg-usz", Raw},
526+
527+
// CVE-2020-28851
528+
// invalid key-value pair of -u- extension.
529+
{"ES-u-000-00", "es-u-000-00", Raw},
530+
{"ES-u-000-00-v-00", "es-u-000-00-v-00", Raw},
531+
// reordered and unknown extension.
532+
{"ES-v-00-u-000-00", "es-u-000-00-v-00", Raw},
526533
}
527534
for i, tt := range tests {
528535
in, _ := Raw.Parse(tt.in)
@@ -553,6 +560,12 @@ func TestTypeForKey(t *testing.T) {
553560
{"rg", "en-u-rg-gbzzzz", "gbzzzz"},
554561
{"nu", "en-u-co-phonebk-nu-arabic", "arabic"},
555562
{"kc", "cmn-u-co-stroke", ""},
563+
{"rg", "cmn-u-rg", ""},
564+
{"rg", "cmn-u-rg-co-stroke", ""},
565+
{"co", "cmn-u-rg-co-stroke", "stroke"},
566+
{"co", "cmn-u-co-rg-gbzzzz", ""},
567+
{"rg", "cmn-u-co-rg-gbzzzz", "gbzzzz"},
568+
{"rg", "cmn-u-rg-gbzzzz-nlzzzz", "gbzzzz"},
556569
}
557570
for _, tt := range tests {
558571
if v := Make(tt.in).TypeForKey(tt.key); v != tt.out {

language/parse_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,13 @@ func parseTests() []parseTest {
101101
{in: "en-9-aa-0-aa-z-bb-x-a", lang: "en", extList: []string{"0-aa", "9-aa", "z-bb", "x-a"}, changed: true},
102102
{in: "en-u-c", lang: "en", ext: "", invalid: true},
103103
{in: "en-u-co-phonebk", lang: "en", ext: "u-co-phonebk"},
104-
{in: "en-u-co-phonebk-ca", lang: "en", ext: "u-co-phonebk", invalid: true},
105-
{in: "en-u-nu-arabic-co-phonebk-ca", lang: "en", ext: "u-co-phonebk-nu-arabic", invalid: true, changed: true},
106-
{in: "en-u-nu-arabic-co-phonebk-ca-x", lang: "en", ext: "u-co-phonebk-nu-arabic", invalid: true, changed: true},
107-
{in: "en-u-nu-arabic-co-phonebk-ca-s", lang: "en", ext: "u-co-phonebk-nu-arabic", invalid: true, changed: true},
108-
{in: "en-u-nu-arabic-co-phonebk-ca-a12345678", lang: "en", ext: "u-co-phonebk-nu-arabic", invalid: true, changed: true},
109-
{in: "en-u-co-phonebook", lang: "en", ext: "", invalid: true},
110-
{in: "en-u-co-phonebook-cu-xau", lang: "en", ext: "u-cu-xau", invalid: true, changed: true},
104+
{in: "en-u-co-phonebk-ca", lang: "en", ext: "u-ca-co-phonebk", invalid: true},
105+
{in: "en-u-nu-arabic-co-phonebk-ca", lang: "en", ext: "u-ca-co-phonebk-nu-arabic", invalid: true, changed: true},
106+
{in: "en-u-nu-arabic-co-phonebk-ca-x", lang: "en", ext: "u-ca-co-phonebk-nu-arabic", invalid: true, changed: true},
107+
{in: "en-u-nu-arabic-co-phonebk-ca-s", lang: "en", ext: "u-ca-co-phonebk-nu-arabic", invalid: true, changed: true},
108+
{in: "en-u-nu-arabic-co-phonebk-ca-a12345678", lang: "en", ext: "u-ca-co-phonebk-nu-arabic", invalid: true, changed: true},
109+
{in: "en-u-co-phonebook", lang: "en", ext: "u-co", invalid: true},
110+
{in: "en-u-co-phonebook-cu-xau", lang: "en", ext: "u-co-cu-xau", invalid: true, changed: true},
111111
{in: "en-Cyrl-u-co-phonebk", lang: "en", script: "Cyrl", ext: "u-co-phonebk"},
112112
{in: "en-US-u-co-phonebk", lang: "en", region: "US", ext: "u-co-phonebk"},
113113
{in: "en-US-u-co-phonebk-cu-xau", lang: "en", region: "US", ext: "u-co-phonebk-cu-xau"},
@@ -117,8 +117,8 @@ func parseTests() []parseTest {
117117
{in: "en-u-def-abc", lang: "en", ext: "u-abc-def", changed: true},
118118
{in: "en-u-cu-xua-co-phonebk-a-cd", lang: "en", extList: []string{"a-cd", "u-co-phonebk-cu-xua"}, changed: true},
119119
// Invalid "u" extension. Drop invalid parts.
120-
{in: "en-u-cu-co-phonebk", lang: "en", extList: []string{"u-co-phonebk"}, invalid: true, changed: true},
121-
{in: "en-u-cu-xau-co", lang: "en", extList: []string{"u-cu-xau"}, invalid: true},
120+
{in: "en-u-cu-co-phonebk", lang: "en", extList: []string{"u-co-phonebk-cu"}, invalid: true, changed: true},
121+
{in: "en-u-cu-xau-co", lang: "en", extList: []string{"u-co-cu-xau"}, invalid: true},
122122
// We allow duplicate keys as the LDML spec does not explicitly prohibit it.
123123
// TODO: Consider eliminating duplicates and returning an error.
124124
{in: "en-u-cu-xau-co-phonebk-cu-xau", lang: "en", ext: "u-co-phonebk-cu-xau", changed: true},
@@ -219,8 +219,8 @@ func TestErrors(t *testing.T) {
219219
{"aa-AB", mkInvalid("AB")},
220220
// ill-formed wins over invalid.
221221
{"ac-u", errSyntax},
222-
{"ac-u-ca", errSyntax},
223-
{"ac-u-ca-co-pinyin", errSyntax},
222+
{"ac-u-ca", mkInvalid("ac")},
223+
{"ac-u-ca-co-pinyin", mkInvalid("ac")},
224224
{"noob", errSyntax},
225225
}
226226
for _, tt := range tests {

0 commit comments

Comments
 (0)