Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/notation/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ func addExternalKey(ctx *cli.Context, pluginName, keyName string) (config.KeySui
if p.Err != nil {
return config.KeySuite{}, fmt.Errorf("invalid plugin: %w", p.Err)
}
pluginConfig, err := cmd.ParseFlagPluginConfig(ctx.StringSlice(cmd.FlagPluginConfig.Name))
pluginConfig, err := cmd.ParseFlagPluginConfig(ctx.String(cmd.FlagPluginConfig.Name))
if err != nil {
return config.KeySuite{}, err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/notation/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func prepareSigningContent(ctx *cli.Context) (notation.Descriptor, notation.Sign
return notation.Descriptor{}, notation.SignOptions{}, err
}
}
pluginConfig, err := cmd.ParseFlagPluginConfig(ctx.StringSlice(cmd.FlagPluginConfig.Name))
pluginConfig, err := cmd.ParseFlagPluginConfig(ctx.String(cmd.FlagPluginConfig.Name))
if err != nil {
return notation.Descriptor{}, notation.SignOptions{}, err
}
Expand Down
71 changes: 68 additions & 3 deletions internal/cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package cmd

import (
"errors"
"fmt"
"strings"

Expand Down Expand Up @@ -45,17 +46,21 @@ var (
Usage: "original reference",
}

FlagPluginConfig = &cli.StringSliceFlag{
FlagPluginConfig = &cli.StringFlag{
Name: "pluginConfig",
Aliases: []string{"pc"},
Usage: "list of comma-separated {key}={value} pairs that are passed as is to the plugin, refer plugin documentation to set appropriate values",
}
)

func ParseFlagPluginConfig(pluginConfigSlice []string) (map[string]string, error) {
if len(pluginConfigSlice) == 0 {
func ParseFlagPluginConfig(v string) (map[string]string, error) {
if v == "" {
return nil, nil
}
pluginConfigSlice, err := splitQuoted(v)
if err != nil {
return nil, err
}
m := make(map[string]string, len(pluginConfigSlice))
for _, c := range pluginConfigSlice {
if k, v, ok := strings.Cut(c, "="); ok {
Expand All @@ -69,3 +74,63 @@ func ParseFlagPluginConfig(pluginConfigSlice []string) (map[string]string, error
}
return m, nil
}

// splitQuoted splits the string s around each instance of one or more consecutive
// comma characters while taking into account quotes and escaping, and
// returns an array of substrings of s or an empty list if s is empty.
// Single quotes and double quotes are recognized to prevent splitting within the
// quoted region, and are removed from the resulting substrings. If a quote in s
// isn't closed err will be set and r will have the unclosed argument as the
// last element. The backslash is used for escaping.
//
// For example, the following string:
//
// `a,b:"c,d",'e''f',,"g\""`
//
// Would be parsed as:
//
// []string{"a", "b:c,d", "ef", `g"`}
func splitQuoted(s string) (r []string, err error) {
var args []string
arg := make([]rune, len(s))
escaped := false
quoted := false
quote := '\x00'
i := 0
for _, r := range s {
switch {
case escaped:
escaped = false
case r == '\\':
escaped = true
continue
case quote != 0:
if r == quote {
quote = 0
continue
}
case r == '"' || r == '\'':
quoted = true
quote = r
continue
case r == ',':
if quoted || i > 0 {
quoted = false
args = append(args, string(arg[:i]))
i = 0
}
continue
}
arg[i] = r
i++
}
if quoted || i > 0 {
args = append(args, string(arg[:i]))
}
if quote != 0 {
err = errors.New("unclosed quote")
} else if escaped {
err = errors.New("unfinished escaping")
}
return args, err
}
16 changes: 8 additions & 8 deletions internal/cmd/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@ import (

func TestParseFlagPluginConfig(t *testing.T) {
type args struct {
s []string
s string
}
tests := []struct {
name string
args args
want map[string]string
wantErr bool
}{
{"nil", args{nil}, nil, false},
{"empty", args{[]string{}}, nil, false},
{"single", args{[]string{"a=b"}}, map[string]string{"a": "b"}, false},
{"multiple", args{[]string{"a=b", "c=d"}}, map[string]string{"a": "b", "c": "d"}, false},
{"quoted", args{[]string{"a=b", "\"c\"=d"}}, map[string]string{"a": "b", "\"c\"": "d"}, false},
{"duplicated", args{[]string{"a=b", "a=d"}}, nil, true},
{"malformed", args{[]string{"a=b", "c:d"}}, nil, true},
{"empty", args{""}, nil, false},
{"single", args{"a=b"}, map[string]string{"a": "b"}, false},
{"multiple", args{"a=b,c=d"}, map[string]string{"a": "b", "c": "d"}, false},
{"quoted", args{"a=b,\"c\"=d"}, map[string]string{"a": "b", "c": "d"}, false},
{"quoted comma", args{"a=b,\"c,h\"=d"}, map[string]string{"a": "b", "c,h": "d"}, false},
{"duplicated", args{"a=b,a=d"}, nil, true},
{"malformed", args{"a=b,c:d"}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down