replace strings.Split(N) for strings.Cut() or alternatives
Go 1.18 and up now provides a strings.Cut() which is better suited for splitting key/value pairs (and similar constructs), and performs better: ```go func BenchmarkSplit(b *testing.B) { b.ReportAllocs() data := []string{"12hello=world", "12hello=", "12=hello", "12hello"} for i := 0; i < b.N; i++ { for _, s := range data { _ = strings.SplitN(s, "=", 2)[0] } } } func BenchmarkCut(b *testing.B) { b.ReportAllocs() data := []string{"12hello=world", "12hello=", "12=hello", "12hello"} for i := 0; i < b.N; i++ { for _, s := range data { _, _, _ = strings.Cut(s, "=") } } } ``` BenchmarkSplit BenchmarkSplit-10 8244206 128.0 ns/op 128 B/op 4 allocs/op BenchmarkCut BenchmarkCut-10 54411998 21.80 ns/op 0 B/op 0 allocs/op While looking at occurrences of `strings.Split()`, I also updated some for alternatives, or added some constraints; - for cases where an specific number of items is expected, I used `strings.SplitN()` with a suitable limit. This prevents (theoretical) unlimited splits. - in some cases it we were using `strings.Split()`, but _actually_ were trying to match a prefix; for those I replaced the code to just match (and/or strip) the prefix. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
parent
552b1526c6
commit
3b391d3290
10 changed files with 60 additions and 92 deletions
|
@ -23,7 +23,7 @@ func MajorMinorVersion(major, minor uint) Version {
|
|||
}
|
||||
|
||||
func (version Version) major() (uint, error) {
|
||||
majorPart := strings.Split(string(version), ".")[0]
|
||||
majorPart, _, _ := strings.Cut(string(version), ".")
|
||||
major, err := strconv.ParseUint(majorPart, 10, 0)
|
||||
return uint(major), err
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ func (version Version) Major() uint {
|
|||
}
|
||||
|
||||
func (version Version) minor() (uint, error) {
|
||||
minorPart := strings.Split(string(version), ".")[1]
|
||||
_, minorPart, _ := strings.Cut(string(version), ".")
|
||||
minor, err := strconv.ParseUint(minorPart, 10, 0)
|
||||
return uint(minor), err
|
||||
}
|
||||
|
@ -89,8 +89,8 @@ func NewParser(prefix string, parseInfos []VersionedParseInfo) *Parser {
|
|||
}
|
||||
|
||||
for _, env := range os.Environ() {
|
||||
envParts := strings.SplitN(env, "=", 2)
|
||||
p.env = append(p.env, envVar{envParts[0], envParts[1]})
|
||||
k, v, _ := strings.Cut(env, "=")
|
||||
p.env = append(p.env, envVar{k, v})
|
||||
}
|
||||
|
||||
// We must sort the environment variables lexically by name so that
|
||||
|
|
|
@ -32,14 +32,12 @@ func parseIP(ipStr string) net.IP {
|
|||
// account proxy headers.
|
||||
func RemoteAddr(r *http.Request) string {
|
||||
if prior := r.Header.Get("X-Forwarded-For"); prior != "" {
|
||||
proxies := strings.Split(prior, ",")
|
||||
if len(proxies) > 0 {
|
||||
remoteAddr := strings.Trim(proxies[0], " ")
|
||||
remoteAddr, _, _ := strings.Cut(prior, ",")
|
||||
remoteAddr = strings.Trim(remoteAddr, " ")
|
||||
if parseIP(remoteAddr) != nil {
|
||||
return remoteAddr
|
||||
}
|
||||
}
|
||||
}
|
||||
// X-Real-Ip is less supported, but worth checking in the
|
||||
// absence of X-Forwarded-For
|
||||
if realIP := r.Header.Get("X-Real-Ip"); realIP != "" {
|
||||
|
@ -189,49 +187,37 @@ type httpRequestContext struct {
|
|||
// "request.<component>". For example, r.RequestURI
|
||||
func (ctx *httpRequestContext) Value(key interface{}) interface{} {
|
||||
if keyStr, ok := key.(string); ok {
|
||||
if keyStr == "http.request" {
|
||||
switch keyStr {
|
||||
case "http.request":
|
||||
return ctx.r
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(keyStr, "http.request.") {
|
||||
goto fallback
|
||||
}
|
||||
|
||||
parts := strings.Split(keyStr, ".")
|
||||
|
||||
if len(parts) != 3 {
|
||||
goto fallback
|
||||
}
|
||||
|
||||
switch parts[2] {
|
||||
case "uri":
|
||||
case "http.request.uri":
|
||||
return ctx.r.RequestURI
|
||||
case "remoteaddr":
|
||||
case "http.request.remoteaddr":
|
||||
return RemoteAddr(ctx.r)
|
||||
case "method":
|
||||
case "http.request.method":
|
||||
return ctx.r.Method
|
||||
case "host":
|
||||
case "http.request.host":
|
||||
return ctx.r.Host
|
||||
case "referer":
|
||||
case "http.request.referer":
|
||||
referer := ctx.r.Referer()
|
||||
if referer != "" {
|
||||
return referer
|
||||
}
|
||||
case "useragent":
|
||||
case "http.request.useragent":
|
||||
return ctx.r.UserAgent()
|
||||
case "id":
|
||||
case "http.request.id":
|
||||
return ctx.id
|
||||
case "startedat":
|
||||
case "http.request.startedat":
|
||||
return ctx.startedAt
|
||||
case "contenttype":
|
||||
ct := ctx.r.Header.Get("Content-Type")
|
||||
if ct != "" {
|
||||
case "http.request.contenttype":
|
||||
if ct := ctx.r.Header.Get("Content-Type"); ct != "" {
|
||||
return ct
|
||||
}
|
||||
default:
|
||||
// no match; fall back to standard behavior below
|
||||
}
|
||||
}
|
||||
|
||||
fallback:
|
||||
return ctx.Context.Value(key)
|
||||
}
|
||||
|
||||
|
@ -245,10 +231,9 @@ func (ctx *muxVarsContext) Value(key interface{}) interface{} {
|
|||
if keyStr == "vars" {
|
||||
return ctx.vars
|
||||
}
|
||||
|
||||
keyStr = strings.TrimPrefix(keyStr, "vars.")
|
||||
|
||||
if v, ok := ctx.vars[keyStr]; ok {
|
||||
// TODO(thaJeztah): this considers "vars.FOO" and "FOO" to be equal.
|
||||
// We need to check if that's intentional (could be a bug).
|
||||
if v, ok := ctx.vars[strings.TrimPrefix(keyStr, "vars.")]; ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
@ -300,36 +285,25 @@ func (irw *instrumentedResponseWriter) Flush() {
|
|||
|
||||
func (irw *instrumentedResponseWriter) Value(key interface{}) interface{} {
|
||||
if keyStr, ok := key.(string); ok {
|
||||
if keyStr == "http.response" {
|
||||
switch keyStr {
|
||||
case "http.response":
|
||||
return irw
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(keyStr, "http.response.") {
|
||||
goto fallback
|
||||
}
|
||||
|
||||
parts := strings.Split(keyStr, ".")
|
||||
|
||||
if len(parts) != 3 {
|
||||
goto fallback
|
||||
}
|
||||
|
||||
case "http.response.written":
|
||||
irw.mu.Lock()
|
||||
defer irw.mu.Unlock()
|
||||
|
||||
switch parts[2] {
|
||||
case "written":
|
||||
return irw.written
|
||||
case "status":
|
||||
case "http.response.status":
|
||||
irw.mu.Lock()
|
||||
defer irw.mu.Unlock()
|
||||
return irw.status
|
||||
case "contenttype":
|
||||
contentType := irw.Header().Get("Content-Type")
|
||||
if contentType != "" {
|
||||
return contentType
|
||||
case "http.response.contenttype":
|
||||
if ct := irw.Header().Get("Content-Type"); ct != "" {
|
||||
return ct
|
||||
}
|
||||
default:
|
||||
// no match; fall back to standard behavior below
|
||||
}
|
||||
}
|
||||
|
||||
fallback:
|
||||
return irw.Context.Value(key)
|
||||
}
|
||||
|
|
|
@ -80,8 +80,8 @@ func NewURLBuilderFromRequest(r *http.Request, relative bool) *URLBuilder {
|
|||
// comma-separated list of hosts, to which each proxy appends the
|
||||
// requested host. We want to grab the first from this comma-separated
|
||||
// list.
|
||||
hosts := strings.SplitN(forwardedHost, ",", 2)
|
||||
host = strings.TrimSpace(hosts[0])
|
||||
host, _, _ = strings.Cut(forwardedHost, ",")
|
||||
host = strings.TrimSpace(host)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -247,15 +247,12 @@ func (ac *accessController) Authorized(ctx context.Context, accessItems ...auth.
|
|||
return nil, err
|
||||
}
|
||||
|
||||
parts := strings.Split(req.Header.Get("Authorization"), " ")
|
||||
|
||||
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
|
||||
prefix, rawToken, ok := strings.Cut(req.Header.Get("Authorization"), " ")
|
||||
if !ok || rawToken == "" || !strings.EqualFold(prefix, "bearer") {
|
||||
challenge.err = ErrTokenRequired
|
||||
return nil, challenge
|
||||
}
|
||||
|
||||
rawToken := parts[1]
|
||||
|
||||
token, err := NewToken(rawToken)
|
||||
if err != nil {
|
||||
challenge.err = err
|
||||
|
|
|
@ -83,7 +83,9 @@ type VerifyOptions struct {
|
|||
// NewToken parses the given raw token string
|
||||
// and constructs an unverified JSON Web Token.
|
||||
func NewToken(rawToken string) (*Token, error) {
|
||||
parts := strings.Split(rawToken, TokenSeparator)
|
||||
// We expect 3 parts, but limit the split to 4 to detect cases where
|
||||
// the token contains too many (or too few) separators.
|
||||
parts := strings.SplitN(rawToken, TokenSeparator, 4)
|
||||
if len(parts) != 3 {
|
||||
return nil, ErrMalformedToken
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -239,8 +240,8 @@ func (t *tags) All(ctx context.Context) ([]string, error) {
|
|||
}
|
||||
tags = append(tags, tagsResponse.Tags...)
|
||||
if link := resp.Header.Get("Link"); link != "" {
|
||||
linkURLStr := strings.Trim(strings.Split(link, ";")[0], "<>")
|
||||
linkURL, err := url.Parse(linkURLStr)
|
||||
firsLink, _, _ := strings.Cut(link, ";")
|
||||
linkURL, err := url.Parse(strings.Trim(firsLink, "<>"))
|
||||
if err != nil {
|
||||
return tags, err
|
||||
}
|
||||
|
@ -808,8 +809,8 @@ func (bs *blobs) Create(ctx context.Context, options ...distribution.BlobCreateO
|
|||
// TODO(dmcgowan): Check for invalid UUID
|
||||
uuid := resp.Header.Get("Docker-Upload-UUID")
|
||||
if uuid == "" {
|
||||
parts := strings.Split(resp.Header.Get("Location"), "/")
|
||||
uuid = parts[len(parts)-1]
|
||||
// uuid is expected to be the last path element
|
||||
_, uuid = path.Split(resp.Header.Get("Location"))
|
||||
}
|
||||
if uuid == "" {
|
||||
return nil, errors.New("cannot retrieve docker upload UUID")
|
||||
|
|
|
@ -68,19 +68,18 @@ func copyFullPayload(ctx context.Context, responseWriter http.ResponseWriter, r
|
|||
return nil
|
||||
}
|
||||
|
||||
func parseContentRange(cr string) (int64, int64, error) {
|
||||
ranges := strings.Split(cr, "-")
|
||||
if len(ranges) != 2 {
|
||||
func parseContentRange(cr string) (start int64, end int64, err error) {
|
||||
rStart, rEnd, ok := strings.Cut(cr, "-")
|
||||
if !ok {
|
||||
return -1, -1, fmt.Errorf("invalid content range format, %s", cr)
|
||||
}
|
||||
start, err := strconv.ParseInt(ranges[0], 10, 64)
|
||||
start, err = strconv.ParseInt(rStart, 10, 64)
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
}
|
||||
end, err := strconv.ParseInt(ranges[1], 10, 64)
|
||||
end, err = strconv.ParseInt(rEnd, 10, 64)
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
}
|
||||
|
||||
return start, end, nil
|
||||
}
|
||||
|
|
|
@ -18,11 +18,10 @@ type logHook struct {
|
|||
|
||||
// Fire forwards an error to LogHook
|
||||
func (hook *logHook) Fire(entry *logrus.Entry) error {
|
||||
addr := strings.Split(hook.Mail.Addr, ":")
|
||||
if len(addr) != 2 {
|
||||
host, _, ok := strings.Cut(hook.Mail.Addr, ":")
|
||||
if !ok || host == "" {
|
||||
return errors.New("invalid Mail Address")
|
||||
}
|
||||
host := addr[0]
|
||||
subject := fmt.Sprintf("[%s] %s: %s", entry.Level, host, entry.Message)
|
||||
|
||||
html := `
|
||||
|
|
|
@ -17,14 +17,14 @@ type Version string
|
|||
|
||||
// Major returns the major (primary) component of a version.
|
||||
func (version Version) Major() uint {
|
||||
majorPart := strings.Split(string(version), ".")[0]
|
||||
majorPart, _, _ := strings.Cut(string(version), ".")
|
||||
major, _ := strconv.ParseUint(majorPart, 10, 0)
|
||||
return uint(major)
|
||||
}
|
||||
|
||||
// Minor returns the minor (secondary) component of a version.
|
||||
func (version Version) Minor() uint {
|
||||
minorPart := strings.Split(string(version), ".")[1]
|
||||
_, minorPart, _ := strings.Cut(string(version), ".")
|
||||
minor, _ := strconv.ParseUint(minorPart, 10, 0)
|
||||
return uint(minor)
|
||||
}
|
||||
|
|
|
@ -763,11 +763,7 @@ func chunkFilenames(slice []string, maxSize int) (chunks [][]string, err error)
|
|||
}
|
||||
|
||||
func parseManifest(manifest string) (container string, prefix string) {
|
||||
components := strings.SplitN(manifest, "/", 2)
|
||||
container = components[0]
|
||||
if len(components) > 1 {
|
||||
prefix = components[1]
|
||||
}
|
||||
container, prefix, _ = strings.Cut(manifest, "/")
|
||||
return container, prefix
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue