Skip to content

Commit

Permalink
Merge pull request #68 from paketo-buildpacks/dan-pr
Browse files Browse the repository at this point in the history
Replaces use of shellwords for parsing labels with custom parser
  • Loading branch information
Daniel Mikusa authored Oct 21, 2021
2 parents 52ef2f1 + edea117 commit 416bba1
Show file tree
Hide file tree
Showing 6 changed files with 401 additions and 10 deletions.
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ go 1.15
require (
github.com/buildpacks/libcnb v1.23.0
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-shellwords v1.0.12
github.com/onsi/gomega v1.16.0
github.com/paketo-buildpacks/libpak v1.55.0
github.com/sclevine/spec v1.4.0
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
Expand Down
181 changes: 176 additions & 5 deletions labels/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ package labels

import (
"fmt"
"sort"
"strings"

"github.com/buildpacks/libcnb"
"github.com/mattn/go-shellwords"
"github.com/paketo-buildpacks/libpak"
"github.com/paketo-buildpacks/libpak/bard"
)
Expand All @@ -46,16 +46,187 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) {
}

if s, ok := cr.Resolve("BP_IMAGE_LABELS"); ok {
words, err := shellwords.Parse(s)
words, err := ParseLabels(s)
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to parse %s\n%w", s, err)
}

for _, word := range words {
parts := strings.Split(word, "=")
result.Labels = append(result.Labels, libcnb.Label{Key: parts[0], Value: parts[1]})
keys := []string{}
for k := range words {
keys = append(keys, k)
}
sort.Strings(keys)

for _, key := range keys {
result.Labels = append(result.Labels, libcnb.Label{Key: key, Value: words[key]})
}
}

return result, nil
}

// ReadToNext rune in string consuming the character
//
// It returns a string of read characters, a string of the remaining characters
// and the specific rune that was read.
func ReadToNext(buf string, chars string) (string, string, rune) {
i := strings.IndexAny(buf, chars)

if i > 0 && i < len(buf) {
return buf[0:i], buf[i+1:], rune(buf[i])
}
if i > 0 && i == len(buf) {
return buf[0 : i-1], "", rune(buf[i])
}
if i == 0 && i < len(buf) {
return "", buf[i+1:], rune(buf[i])
}
return buf, "", rune(0)
}

// ReadKey from the string
//
// A key is either all words before the equals sign, or a single or
// double quoted word group, again before an equals sign.
//
// If key is quoted and there are characters between the ending quote
// and the equals sign, those are discarded.
//
// It returns the key, a string with the remainder of the characters
// or an error.
func ReadKey(buf string) (string, string, error) {
item := ""
needClosingQuote := false
key, rest, ch := ReadToNext(buf, `"'=`)
for {
// unescape, escaped single and double quotes
if (ch == '"' || ch == '\'') && strings.HasSuffix(key, `\`) {
key = fmt.Sprintf("%s%c", strings.TrimSuffix(key, `\`), ch)
ch = '\\'
}

item += key

// end quote
if (ch == '"' || ch == '\'') && needClosingQuote {
invalid, rest, _ := ReadToNext(rest, `=`)
if len(strings.TrimSpace(invalid)) > 0 {
return "", rest, fmt.Errorf("unable to have characters after a trailing quote")
}
return item, rest, nil
}

// beginning quote
if (ch == '"' || ch == '\'') && !needClosingQuote {
needClosingQuote = true
}

// end of key or no more data
if (ch == '=' || rest == "") && needClosingQuote {
return "", rest, fmt.Errorf("unable to find a closing quote")
} else if ch == '=' || rest == "" {
return item, rest, nil
}

key, rest, ch = ReadToNext(rest, `"'=`)
}
}

// ReadValue from the string
//
// A value is either all words up to the first space character, or a
// single or double quoted word group, again before the first space.
//
// If value is quoted and there are characters between the ending quote
// and the equals sign, those are discarded.
//
// It returns the value, a string with the remainder of the characters
// or an error.
func ReadValue(buf string) (string, string, error) {
item := ""
needClosingQuote := false
value, rest, ch := ReadToNext(buf, `"' `)
for {
// unescape, escaped single and double quotes
if (ch == '"' || ch == '\'') && strings.HasSuffix(value, `\`) {
value = fmt.Sprintf("%s%c", strings.TrimSuffix(value, `\`), ch)
ch = '\\'
}

item += value

// end quote
if (ch == '"' || ch == '\'') && needClosingQuote {
invalid, rest, _ := ReadToNext(rest, ` `)
if len(strings.TrimSpace(invalid)) > 0 {
return "", rest, fmt.Errorf("unable to have characters after a trailing quote")
}
return item, rest, nil
}

// beginning quote
if (ch == '"' || ch == '\'') && !needClosingQuote {
needClosingQuote = true
}

// successful end of value
if ch == ' ' && !needClosingQuote {
return item, rest, nil
}

// embedded space
if ch == ' ' && needClosingQuote {
item += " "
}

// end of data buffer
if rest == "" && needClosingQuote {
return "", rest, fmt.Errorf("unable to find a closing quote")
} else if rest == "" {
return item, rest, nil
}

value, rest, ch = ReadToNext(rest, `"' `)
}
}

func ParseLabels(rest string) (map[string]string, error) {
m := make(map[string]string)

var (
key, val string
err error
pos int
)

for {
before := len(rest)
key, rest, err = ReadKey(rest)
pos += before - len(rest)

if err != nil {
return nil, fmt.Errorf("unable to read key ending at char %d\n%w", pos-1, err)
}

if len(key) == 0 {
return nil, fmt.Errorf("unable to have empty key ending at char %d", pos-1)
}

before = len(rest)
val, rest, err = ReadValue(rest)
pos += before - len(rest)

if err != nil {
if len(rest) > 0 {
pos -= 1
}
return nil, fmt.Errorf("unable to read value ending at char %d\n%w", pos, err)
}

m[key] = val

if rest == "" {
return m, nil
}
}
}
Loading

0 comments on commit 416bba1

Please sign in to comment.