Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
53 changes: 48 additions & 5 deletions remote/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ func getCleanWpCliArgumentArray(wpCliCmdString string) ([]string, error) {

// Remove quotes from the args
for i := range cleanArgs {
if !isJSONObject(cleanArgs[i]) { //don't alter JSON arguments
if !isJSONObjectOrArray(cleanArgs[i]) { //don't alter JSON arguments
cleanArgs[i] = strings.ReplaceAll(cleanArgs[i], "\"", "")
}
}
Expand Down Expand Up @@ -1114,10 +1114,53 @@ func isJSON(str string) bool {
return json.Valid([]byte(str))
}

func isJSONObject(str string) bool {
trimmedStr := strings.TrimSpace(str)
if !strings.HasPrefix(trimmedStr, "{") || !strings.HasSuffix(trimmedStr, "}") {
// See: https://stackoverflow.com/a/55017470
func jsonType(str string) (string, error) {
if !isJSON(str) {
return "", errors.New("input is not valid JSON")
}
in := strings.NewReader(str)
dec := json.NewDecoder(in)
// Get just the first valid JSON token from input
t, err := dec.Token()
if err != nil {
return "token error!", err
}
if d, ok := t.(json.Delim); ok {
// The first token is a delimiter, so this is an array or an object
switch d {
case '[':
return "array", nil
case '{':
return "object", nil
default:
return "", errors.New("unexpected delimiter")
}
}
return "", errors.New("input does not represent a JSON object or array")
}

func contains(haystack []string, needle string) bool {
for _, s := range haystack {
if s == needle {
return true
}
}
return false
}

func isJSONObjectOrArray(str string) bool {
var _str = str

// The type checking is intolerant of single quotes, strip them before checking if present
strlen := len(str)
if strlen >= 2 && str[0:1] == "'" && str[strlen-1:] == "'" {
_str = str[1 : strlen-1]
}
t, err := jsonType(_str)
if err != nil {
return false
}
return isJSON(str)

return contains([]string{"object", "array"}, t)
}
70 changes: 58 additions & 12 deletions remote/remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,78 @@ import (
"testing"
)

func TestCheckIsJSONObject(t *testing.T) {
func TestCheckIsJSONObjectOrArray(t *testing.T) {
tests := map[string]struct {
input string
want bool
}{
"normal text with no quotes": {want: false, input: "normal text"},
"array object": {want: false, input: "[1,2,3]"},
"valid json string that should be excluded": {want: false, input: `"normal text inside quotes"`},
"missing quotes json": {want: false, input: `{"broken":json"}`},
"json inside extra quotes": {want: false, input: `"{"broken":"json"}"`},
"missing closing parenthesis": {want: false, input: `{"broken":"json object"`},
"wrong numerical key json": {want: false, input: ` { 1 : " wrong" } `},
"standard json": {want: true, input: `{"object":"json"}`},
"json with extra spacing": {want: true, input: ` { "object space" : "with spacing" } `},
"empty string": {want: false, input: ""},
"just single quotes": {want: false, input: "''"},
"just double quotes": {want: false, input: `""`},
"normal text with no quotes": {want: false, input: "normal text"},
"number with no quotes": {want: false, input: "123456"},
"normal text inside double quotes": {want: false, input: `"normal text inside quotes"`},
"normal text inside single quotes": {want: false, input: "'normal text inside quotes'"},
"normal text inside double quotes w/ padding": {want: false, input: ` "normal text inside quotes" `},
"normal text inside single quotes w/ padding": {want: false, input: " 'normal text inside quotes' "},
"missing quotes json": {want: false, input: `{"broken":json"}`},
"json inside extra quotes": {want: false, input: `"{"broken":"json"}"`},
"missing closing parenthesis": {want: false, input: `{"broken":"json object"`},
"wrong numerical key json": {want: false, input: ` { 1 : " wrong" } `},
"array object": {want: true, input: "[1,2,3]"},
"array object with strings": {want: true, input: "[\"a\",\"b\",\"c\"]"},
"array object with strings & padding": {want: true, input: " [\"a\",\"b\",\"c\"] "},
"standard json": {want: true, input: `{"object":"json"}`},
"json with extra spacing": {want: true, input: ` { "object space" : "with spacing" } `},
"empty array should be true": {want: true, input: "[]"},
"empty object should be true": {want: true, input: "{}"},
"object surrounded with single quotes": {want: true, input: `'{"object":"json"}'`},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
got := isJSONObject(tc.input)
got := isJSONObjectOrArray(tc.input)
if !reflect.DeepEqual(tc.want, got) {
t.Fatalf("testing '%v' isJSONObject(\"%v\") expected: %v, got: %v", name, tc.input, tc.want, got)
t.Fatalf("testing '%v' isJSONObjectOrArray(\"%v\") expected: %v, got: %v", name, tc.input, tc.want, got)
}
})
}
}

func TestGetCleanWpCliArgumentArray(t *testing.T) {
tests := map[string]struct {
errString string
input string
want []string
}{
"vip whatever should not be changed": {errString: "", want: []string{"vip", "whatever"}, input: "vip whatever"},
"wp option update with array should not be changed": {errString: "", want: []string{"wp", "option", "update", "someoption", `["stuff","things"]`, "--format=json"}, input: "wp option update someoption [\"stuff\",\"things\"] --format=json"},

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows the invalid JSON error to bubble back to the client.

"wp option update with array wrapped in single quotes should not be changed": {errString: "", want: []string{"wp", "option", "update", "someoption", `'["stuff","things"]'`, "--format=json"}, input: "wp option update someoption '[\"stuff\",\"things\"]' --format=json"},
"wp option update with object should not be changed": {errString: "", want: []string{"wp", "option", "update", "someoption", `{"val1":"stuff","val2":"things"}`, "--format=json"}, input: "wp option update someoption {\"val1\":\"stuff\",\"val2\":\"things\"} --format=json"},
"wp option update with object wrapped in single quotes should not be changed": {errString: "", want: []string{"wp", "option", "update", "someoption", `'{"val1":"stuff","val2":"things"}'`, "--format=json"}, input: "wp option update someoption '{\"val1\":\"stuff\",\"val2\":\"things\"}' --format=json"},
"wp option update with quotes in value should be changed": {errString: "", want: []string{"wp", "option", "update", "someoption", `stuff,things`}, input: "wp option update someoption \"stuff\",\"things\""},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
got, err := getCleanWpCliArgumentArray(tc.input)

if err != nil && tc.errString != err.Error() {
t.Fatalf("testing '%v' getCleanWpCliArgumentArray(\"%v\") expected error: %v, got: %v", name, tc.input, tc.errString, err.Error())
}

if err == nil && tc.errString != "" {
t.Fatalf("testing '%v' getCleanWpCliArgumentArray(\"%v\") expected error string: %v, got: nil", name, tc.input, tc.errString)
}

if !reflect.DeepEqual(tc.want, got) {
t.Fatalf("testing '%v' getCleanWpCliArgumentArray(\"%v\") expected:\n\t%v\ngot:\n\t%v", name, tc.input, tc.want, got)
}
})
}

}

func TestValidateCommand(t *testing.T) {
tests := map[string]struct {
errString string
Expand Down