Skip to content

Commit fa5e7eb

Browse files
author
Sergio Andres Virviescas Santana
committed
important hotfix: golang's strings.ToLower is not backward compatible
1 parent 492bc22 commit fa5e7eb

File tree

4 files changed

+120
-5
lines changed

4 files changed

+120
-5
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go:
44
- 1.9.x
55
- 1.10.x
66
- 1.11.x
7+
- 1.12.x
78
- tip
89

910
os:

tolower.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//+build !go1.12
2+
3+
package router
4+
5+
import "strings"
6+
7+
func toLower(s string) string {
8+
return strings.ToLower(s)
9+
}

tolower_go112.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright 2009 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the "third_party_licenses/Go (golang)" file.
4+
5+
//+build go1.12
6+
7+
package router
8+
9+
import (
10+
"strings"
11+
"unicode"
12+
"unicode/utf8"
13+
)
14+
15+
// toLower returns a copy of the string s with all Unicode letters mapped to their lower case.
16+
func toLower(s string) string {
17+
isASCII, hasUpper := true, false
18+
for i := 0; i < len(s); i++ {
19+
c := s[i]
20+
if c >= utf8.RuneSelf {
21+
isASCII = false
22+
break
23+
}
24+
hasUpper = hasUpper || (c >= 'A' && c <= 'Z')
25+
}
26+
27+
if isASCII { // optimize for ASCII-only strings.
28+
if !hasUpper {
29+
return s
30+
}
31+
var b strings.Builder
32+
b.Grow(len(s))
33+
for i := 0; i < len(s); i++ {
34+
c := s[i]
35+
if c >= 'A' && c <= 'Z' {
36+
c += 'a' - 'A'
37+
}
38+
b.WriteByte(c)
39+
}
40+
return b.String()
41+
}
42+
return stringsMap(unicode.ToLower, s)
43+
}
44+
45+
// Map returns a copy of the string s with all its characters modified
46+
// according to the mapping function. If mapping returns a negative value, the character is
47+
// dropped from the string with no replacement.
48+
func stringsMap(mapping func(rune) rune, s string) string {
49+
// In the worst case, the string can grow when mapped, making
50+
// things unpleasant. But it's so rare we barge in assuming it's
51+
// fine. It could also shrink but that falls out naturally.
52+
53+
// The output buffer b is initialized on demand, the first
54+
// time a character differs.
55+
var b strings.Builder
56+
57+
for i, c := range s {
58+
r := mapping(c)
59+
if r == c {
60+
continue
61+
}
62+
63+
b.Grow(len(s) + utf8.UTFMax)
64+
b.WriteString(s[:i])
65+
if r >= 0 {
66+
b.WriteRune(r)
67+
}
68+
69+
if c == utf8.RuneError {
70+
// RuneError is the result of either decoding
71+
// an invalid sequence or '\uFFFD'. Determine
72+
// the correct number of bytes we need to advance.
73+
_, w := utf8.DecodeRuneInString(s[i:])
74+
i += w
75+
} else {
76+
i += utf8.RuneLen(c)
77+
}
78+
79+
s = s[i:]
80+
break
81+
}
82+
83+
// Fast path for unchanged input
84+
if b.Cap() == 0 { // didn't call b.Grow above
85+
return s
86+
}
87+
88+
for _, c := range s {
89+
r := mapping(c)
90+
91+
if r >= 0 {
92+
// common case
93+
// Due to inlining, it is more performant to determine if WriteByte should be
94+
// invoked rather than always call WriteRune
95+
if r < utf8.RuneSelf {
96+
b.WriteByte(byte(r))
97+
} else {
98+
// r is not a ASCII rune.
99+
b.WriteRune(r)
100+
}
101+
}
102+
}
103+
104+
return b.String()
105+
}

tree.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) ([]by
471471

472472
fixedPath, found := n.findCaseInsensitivePathRec(
473473
path,
474-
strings.ToLower(path),
474+
toLower(path),
475475
buff.b[:0],
476476
[4]byte{}, // empty rune buffer
477477
fixTrailingSlash,
@@ -500,7 +500,7 @@ func shiftNRuneBytes(rb [4]byte, n int) [4]byte {
500500

501501
// recursive case-insensitive lookup function used by n.findCaseInsensitivePath
502502
func (n *node) findCaseInsensitivePathRec(path, loPath string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) ([]byte, bool) {
503-
loNPath := strings.ToLower(n.path)
503+
loNPath := toLower(n.path)
504504

505505
walk: // outer loop for walking the tree
506506
for len(loPath) >= len(loNPath) && (len(loNPath) == 0 || loPath[1:len(loNPath)] == loNPath[1:]) {
@@ -524,7 +524,7 @@ walk: // outer loop for walking the tree
524524
if n.indices[i] == rb[0] {
525525
// continue with child node
526526
n = n.children[i]
527-
loNPath = strings.ToLower(n.path)
527+
loNPath = toLower(n.path)
528528
continue walk
529529
}
530530
}
@@ -574,7 +574,7 @@ walk: // outer loop for walking the tree
574574
if n.indices[i] == rb[0] {
575575
// continue with child node
576576
n = n.children[i]
577-
loNPath = strings.ToLower(n.path)
577+
loNPath = toLower(n.path)
578578
continue walk
579579
}
580580
}
@@ -603,7 +603,7 @@ walk: // outer loop for walking the tree
603603
if len(n.children) > 0 {
604604
// continue with child node
605605
n = n.children[0]
606-
loNPath = strings.ToLower(n.path)
606+
loNPath = toLower(n.path)
607607
loPath = loPath[k:]
608608
path = path[k:]
609609
continue

0 commit comments

Comments
 (0)