Skip to content
Open
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
41 changes: 39 additions & 2 deletions cargs.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,43 @@ import (
"strings"
)

// encodeArgs encodes each argument individually using base64, then joins them with the NULL separator.
// This prevents arguments containing spaces from being incorrectly split during decryption.
// Empty strings are encoded as a single space to preserve them through the join/split process.
func encodeArgs(args []string) []byte {
var encoded []string
for _, arg := range args {
if arg == "" {
encoded = append(encoded, " ")
} else {
encoded = append(encoded, base64.StdEncoding.EncodeToString([]byte(arg)))
}
}
return []byte(strings.Join(encoded, "\x00"))
}

// decodeArgs splits the input by NULL separator and decodes each part using base64.
// A single space is decoded as an empty string to preserve empty arguments.
func decodeArgs(data []byte) []string {
parts := strings.Split(string(data), "\x00")
var decoded []string
for _, part := range parts {
if part == "" {
continue
}
if part == " " {
decoded = append(decoded, "")
continue
}
decodedBytes, err := base64.StdEncoding.DecodeString(part)
if err != nil {
continue
}
decoded = append(decoded, string(decodedBytes))
}
return decoded
}

// init cargs
// only flag occur,cargs crypto rest args,otherwise decrypt os.args[1]
func Init(key []byte, flag string) {
Expand All @@ -29,7 +66,7 @@ func Init(key []byte, flag string) {
newArgs = append(newArgs, os.Args[0])
if len(os.Args) > 2 && os.Args[1] == flag {
// encode args
input := []byte(strings.Join(os.Args[2:], " "))
input := encodeArgs(os.Args[2:])
output := make([]byte, len(input))
salsa20.XORKeyStream(output, input, nonce, &key32)
fmt.Printf("cargs output: %s\n", base64.StdEncoding.EncodeToString(output))
Expand All @@ -42,7 +79,7 @@ func Init(key []byte, flag string) {
}
output := make([]byte, len(input))
salsa20.XORKeyStream(output, input, nonce, &key32)
os.Args = append(os.Args[:1], strings.Split(string(output), " ")...)
os.Args = append(os.Args[:1], decodeArgs(output)...)
} else {
os.Exit(0)
}
Expand Down
116 changes: 116 additions & 0 deletions cargs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//+build cargs

package cargs

import (
"testing"
)

func TestEncodeDecodeArgs(t *testing.T) {
testCases := []struct {
name string
args []string
}{
{
name: "simple arguments",
args: []string{"--name", "hello"},
},
{
name: "argument with single space",
args: []string{"--name", "hello world"},
},
{
name: "argument with multiple spaces",
args: []string{"--name", "hello world"},
},
{
name: "multiple arguments with spaces",
args: []string{"--name", "hello world", "--other", "foo bar"},
},
{
name: "empty arguments",
args: []string{"", ""},
},
{
name: "argument with special characters",
args: []string{"--path", "/usr/local/bin"},
},
{
name: "argument with newlines",
args: []string{"--multiline", "line1\nline2"},
},
{
name: "argument with tabs",
args: []string{"--tab", "col1\tcol2"},
},
{
name: "argument with unicode",
args: []string{"--unicode", "你好世界"},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
encoded := encodeArgs(tc.args)
decoded := decodeArgs(encoded)

if len(decoded) != len(tc.args) {
t.Errorf("encode/decode failed for %s: got %d args, want %d args", tc.name, len(decoded), len(tc.args))
t.Errorf(" decoded: %v", decoded)
t.Errorf(" original: %v", tc.args)
}

for i := range tc.args {
if decoded[i] != tc.args[i] {
t.Errorf("encode/decode failed for %s at index %d: got %q, want %q", tc.name, i, decoded[i], tc.args[i])
}
}
})
}
}

func TestEncodeArgsProducesNullSeparatedOutput(t *testing.T) {
args := []string{"hello world", "foo bar"}
encoded := encodeArgs(args)
encodedStr := string(encoded)

// Should contain NULL byte separator
if !contains(encodedStr, "\x00") {
t.Error("encoded output should contain NULL byte separator")
}
// Should NOT contain space inside individual encoded parts (base64 doesn't use spaces)
parts := splitOnce(encodedStr, "\x00")
for i, part := range parts {
// Each part is a base64 encoded string - verify it's valid base64
// (no spaces in base64 output)
if contains(part, " ") {
t.Errorf("part %d should not contain spaces: %s", i, part)
}
}
}

// Helper functions for testing
func contains(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsAt(s, substr, 0))
}

func containsAt(s, substr string, start int) bool {
for i := start; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}

func splitOnce(s, sep string) []string {
var result []string
for i := 0; i <= len(s)-len(sep); i++ {
if s[i:i+len(sep)] == sep {
result = append(result, s[:i])
result = append(result, s[i+len(sep):])
return result
}
}
return []string{s}
}