mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-03 19:17:29 -05:00 
			
		
		
		
	I am not a lawyer, but according to the appendix of the license, these boilerplate notices should be included with every source file.
		
			
				
	
	
		
			306 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			306 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2015 Light Code Labs, LLC
 | 
						|
//
 | 
						|
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
// you may not use this file except in compliance with the License.
 | 
						|
// You may obtain a copy of the License at
 | 
						|
//
 | 
						|
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
//
 | 
						|
// Unless required by applicable law or agreed to in writing, software
 | 
						|
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
// See the License for the specific language governing permissions and
 | 
						|
// limitations under the License.
 | 
						|
 | 
						|
package caddy
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"runtime"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
)
 | 
						|
 | 
						|
func TestParseUnixCommand(t *testing.T) {
 | 
						|
	tests := []struct {
 | 
						|
		input    string
 | 
						|
		expected []string
 | 
						|
	}{
 | 
						|
		// 0 - empty command
 | 
						|
		{
 | 
						|
			input:    ``,
 | 
						|
			expected: []string{},
 | 
						|
		},
 | 
						|
		// 1 - command without arguments
 | 
						|
		{
 | 
						|
			input:    `command`,
 | 
						|
			expected: []string{`command`},
 | 
						|
		},
 | 
						|
		// 2 - command with single argument
 | 
						|
		{
 | 
						|
			input:    `command arg1`,
 | 
						|
			expected: []string{`command`, `arg1`},
 | 
						|
		},
 | 
						|
		// 3 - command with multiple arguments
 | 
						|
		{
 | 
						|
			input:    `command arg1 arg2`,
 | 
						|
			expected: []string{`command`, `arg1`, `arg2`},
 | 
						|
		},
 | 
						|
		// 4 - command with single argument with space character - in quotes
 | 
						|
		{
 | 
						|
			input:    `command "arg1 arg1"`,
 | 
						|
			expected: []string{`command`, `arg1 arg1`},
 | 
						|
		},
 | 
						|
		// 5 - command with multiple spaces and tab character
 | 
						|
		{
 | 
						|
			input:    "command arg1    arg2\targ3",
 | 
						|
			expected: []string{`command`, `arg1`, `arg2`, `arg3`},
 | 
						|
		},
 | 
						|
		// 6 - command with single argument with space character - escaped with backspace
 | 
						|
		{
 | 
						|
			input:    `command arg1\ arg2`,
 | 
						|
			expected: []string{`command`, `arg1 arg2`},
 | 
						|
		},
 | 
						|
		// 7 - single quotes should escape special chars
 | 
						|
		{
 | 
						|
			input:    `command 'arg1\ arg2'`,
 | 
						|
			expected: []string{`command`, `arg1\ arg2`},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for i, test := range tests {
 | 
						|
		errorPrefix := fmt.Sprintf("Test [%d]: ", i)
 | 
						|
		errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input)
 | 
						|
		actual, _ := parseUnixCommand(test.input)
 | 
						|
		if len(actual) != len(test.expected) {
 | 
						|
			t.Errorf(errorPrefix+"Expected %d parts, got %d: %#v."+errorSuffix, len(test.expected), len(actual), actual)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		for j := 0; j < len(actual); j++ {
 | 
						|
			if expectedPart, actualPart := test.expected[j], actual[j]; expectedPart != actualPart {
 | 
						|
				t.Errorf(errorPrefix+"Expected: %v Actual: %v (index %d)."+errorSuffix, expectedPart, actualPart, j)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestParseWindowsCommand(t *testing.T) {
 | 
						|
	tests := []struct {
 | 
						|
		input    string
 | 
						|
		expected []string
 | 
						|
	}{
 | 
						|
		{ // 0 - empty command - do not fail
 | 
						|
			input:    ``,
 | 
						|
			expected: []string{},
 | 
						|
		},
 | 
						|
		{ // 1 - cmd without args
 | 
						|
			input:    `cmd`,
 | 
						|
			expected: []string{`cmd`},
 | 
						|
		},
 | 
						|
		{ // 2 - multiple args
 | 
						|
			input:    `cmd arg1 arg2`,
 | 
						|
			expected: []string{`cmd`, `arg1`, `arg2`},
 | 
						|
		},
 | 
						|
		{ // 3 - multiple args with space
 | 
						|
			input:    `cmd "combined arg" arg2`,
 | 
						|
			expected: []string{`cmd`, `combined arg`, `arg2`},
 | 
						|
		},
 | 
						|
		{ // 4 - path without spaces
 | 
						|
			input:    `mkdir C:\Windows\foo\bar`,
 | 
						|
			expected: []string{`mkdir`, `C:\Windows\foo\bar`},
 | 
						|
		},
 | 
						|
		{ // 5 - command with space in quotes
 | 
						|
			input:    `"command here"`,
 | 
						|
			expected: []string{`command here`},
 | 
						|
		},
 | 
						|
		{ // 6 - argument with escaped quotes (two quotes)
 | 
						|
			input:    `cmd ""arg""`,
 | 
						|
			expected: []string{`cmd`, `"arg"`},
 | 
						|
		},
 | 
						|
		{ // 7 - argument with escaped quotes (backslash)
 | 
						|
			input:    `cmd \"arg\"`,
 | 
						|
			expected: []string{`cmd`, `"arg"`},
 | 
						|
		},
 | 
						|
		{ // 8 - two quotes (escaped) inside an inQuote element
 | 
						|
			input:    `cmd "a ""quoted value"`,
 | 
						|
			expected: []string{`cmd`, `a "quoted value`},
 | 
						|
		},
 | 
						|
		// TODO - see how many quotes are dislayed if we use "", """, """""""
 | 
						|
		{ // 9 - two quotes outside an inQuote element
 | 
						|
			input:    `cmd a ""quoted value`,
 | 
						|
			expected: []string{`cmd`, `a`, `"quoted`, `value`},
 | 
						|
		},
 | 
						|
		{ // 10 - path with space in quotes
 | 
						|
			input:    `mkdir "C:\directory name\foobar"`,
 | 
						|
			expected: []string{`mkdir`, `C:\directory name\foobar`},
 | 
						|
		},
 | 
						|
		{ // 11 - space without quotes
 | 
						|
			input:    `mkdir C:\ space`,
 | 
						|
			expected: []string{`mkdir`, `C:\`, `space`},
 | 
						|
		},
 | 
						|
		{ // 12 - space in quotes
 | 
						|
			input:    `mkdir "C:\ space"`,
 | 
						|
			expected: []string{`mkdir`, `C:\ space`},
 | 
						|
		},
 | 
						|
		{ // 13 - UNC
 | 
						|
			input:    `mkdir \\?\C:\Users`,
 | 
						|
			expected: []string{`mkdir`, `\\?\C:\Users`},
 | 
						|
		},
 | 
						|
		{ // 14 - UNC with space
 | 
						|
			input:    `mkdir "\\?\C:\Program Files"`,
 | 
						|
			expected: []string{`mkdir`, `\\?\C:\Program Files`},
 | 
						|
		},
 | 
						|
 | 
						|
		{ // 15 - unclosed quotes - treat as if the path ends with quote
 | 
						|
			input:    `mkdir "c:\Program files`,
 | 
						|
			expected: []string{`mkdir`, `c:\Program files`},
 | 
						|
		},
 | 
						|
		{ // 16 - quotes used inside the argument
 | 
						|
			input:    `mkdir "c:\P"rogra"m f"iles`,
 | 
						|
			expected: []string{`mkdir`, `c:\Program files`},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for i, test := range tests {
 | 
						|
		errorPrefix := fmt.Sprintf("Test [%d]: ", i)
 | 
						|
		errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input)
 | 
						|
 | 
						|
		actual := parseWindowsCommand(test.input)
 | 
						|
		if len(actual) != len(test.expected) {
 | 
						|
			t.Errorf(errorPrefix+"Expected %d parts, got %d: %#v."+errorSuffix, len(test.expected), len(actual), actual)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		for j := 0; j < len(actual); j++ {
 | 
						|
			if expectedPart, actualPart := test.expected[j], actual[j]; expectedPart != actualPart {
 | 
						|
				t.Errorf(errorPrefix+"Expected: %v Actual: %v (index %d)."+errorSuffix, expectedPart, actualPart, j)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestSplitCommandAndArgs(t *testing.T) {
 | 
						|
 | 
						|
	// force linux parsing. It's more robust and covers error cases
 | 
						|
	runtimeGoos = "linux"
 | 
						|
	defer func() {
 | 
						|
		runtimeGoos = runtime.GOOS
 | 
						|
	}()
 | 
						|
 | 
						|
	var parseErrorContent = "error parsing command:"
 | 
						|
	var noCommandErrContent = "no command contained in"
 | 
						|
 | 
						|
	tests := []struct {
 | 
						|
		input              string
 | 
						|
		expectedCommand    string
 | 
						|
		expectedArgs       []string
 | 
						|
		expectedErrContent string
 | 
						|
	}{
 | 
						|
		// 0 - empty command
 | 
						|
		{
 | 
						|
			input:              ``,
 | 
						|
			expectedCommand:    ``,
 | 
						|
			expectedArgs:       nil,
 | 
						|
			expectedErrContent: noCommandErrContent,
 | 
						|
		},
 | 
						|
		// 1 - command without arguments
 | 
						|
		{
 | 
						|
			input:              `command`,
 | 
						|
			expectedCommand:    `command`,
 | 
						|
			expectedArgs:       nil,
 | 
						|
			expectedErrContent: ``,
 | 
						|
		},
 | 
						|
		// 2 - command with single argument
 | 
						|
		{
 | 
						|
			input:              `command arg1`,
 | 
						|
			expectedCommand:    `command`,
 | 
						|
			expectedArgs:       []string{`arg1`},
 | 
						|
			expectedErrContent: ``,
 | 
						|
		},
 | 
						|
		// 3 - command with multiple arguments
 | 
						|
		{
 | 
						|
			input:              `command arg1 arg2`,
 | 
						|
			expectedCommand:    `command`,
 | 
						|
			expectedArgs:       []string{`arg1`, `arg2`},
 | 
						|
			expectedErrContent: ``,
 | 
						|
		},
 | 
						|
		// 4 - command with unclosed quotes
 | 
						|
		{
 | 
						|
			input:              `command "arg1 arg2`,
 | 
						|
			expectedCommand:    "",
 | 
						|
			expectedArgs:       nil,
 | 
						|
			expectedErrContent: parseErrorContent,
 | 
						|
		},
 | 
						|
		// 5 - command with unclosed quotes
 | 
						|
		{
 | 
						|
			input:              `command 'arg1 arg2"`,
 | 
						|
			expectedCommand:    "",
 | 
						|
			expectedArgs:       nil,
 | 
						|
			expectedErrContent: parseErrorContent,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for i, test := range tests {
 | 
						|
		errorPrefix := fmt.Sprintf("Test [%d]: ", i)
 | 
						|
		errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input)
 | 
						|
		actualCommand, actualArgs, actualErr := SplitCommandAndArgs(test.input)
 | 
						|
 | 
						|
		// test if error matches expectation
 | 
						|
		if test.expectedErrContent != "" {
 | 
						|
			if actualErr == nil {
 | 
						|
				t.Errorf(errorPrefix+"Expected error with content [%s], found no error."+errorSuffix, test.expectedErrContent)
 | 
						|
			} else if !strings.Contains(actualErr.Error(), test.expectedErrContent) {
 | 
						|
				t.Errorf(errorPrefix+"Expected error with content [%s], found [%v]."+errorSuffix, test.expectedErrContent, actualErr)
 | 
						|
			}
 | 
						|
		} else if actualErr != nil {
 | 
						|
			t.Errorf(errorPrefix+"Expected no error, found [%v]."+errorSuffix, actualErr)
 | 
						|
		}
 | 
						|
 | 
						|
		// test if command matches
 | 
						|
		if test.expectedCommand != actualCommand {
 | 
						|
			t.Errorf(errorPrefix+"Expected command: [%s], actual: [%s]."+errorSuffix, test.expectedCommand, actualCommand)
 | 
						|
		}
 | 
						|
 | 
						|
		// test if arguments match
 | 
						|
		if len(test.expectedArgs) != len(actualArgs) {
 | 
						|
			t.Errorf(errorPrefix+"Wrong number of arguments! Expected [%v], actual [%v]."+errorSuffix, test.expectedArgs, actualArgs)
 | 
						|
		} else {
 | 
						|
			// test args only if the count matches.
 | 
						|
			for j, actualArg := range actualArgs {
 | 
						|
				expectedArg := test.expectedArgs[j]
 | 
						|
				if actualArg != expectedArg {
 | 
						|
					t.Errorf(errorPrefix+"Argument at position [%d] differ! Expected [%s], actual [%s]"+errorSuffix, j, expectedArg, actualArg)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func ExampleSplitCommandAndArgs() {
 | 
						|
	var commandLine string
 | 
						|
	var command string
 | 
						|
	var args []string
 | 
						|
 | 
						|
	// just for the test - change GOOS and reset it at the end of the test
 | 
						|
	runtimeGoos = "windows"
 | 
						|
	defer func() {
 | 
						|
		runtimeGoos = runtime.GOOS
 | 
						|
	}()
 | 
						|
 | 
						|
	commandLine = `mkdir /P "C:\Program Files"`
 | 
						|
	command, args, _ = SplitCommandAndArgs(commandLine)
 | 
						|
 | 
						|
	fmt.Printf("Windows: %s: %s [%s]\n", commandLine, command, strings.Join(args, ","))
 | 
						|
 | 
						|
	// set GOOS to linux
 | 
						|
	runtimeGoos = "linux"
 | 
						|
 | 
						|
	commandLine = `mkdir -p /path/with\ space`
 | 
						|
	command, args, _ = SplitCommandAndArgs(commandLine)
 | 
						|
 | 
						|
	fmt.Printf("Linux: %s: %s [%s]\n", commandLine, command, strings.Join(args, ","))
 | 
						|
 | 
						|
	// Output:
 | 
						|
	// Windows: mkdir /P "C:\Program Files": mkdir [/P,C:\Program Files]
 | 
						|
	// Linux: mkdir -p /path/with\ space: mkdir [-p,/path/with space]
 | 
						|
}
 |