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
60 changes: 34 additions & 26 deletions misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,68 +107,76 @@ func truncateLeft(s string, maxWidth int) string {
}

// cmdEscape is used to escape whitespace and special characters with
// backslashes in a given string.
// backslashes in a given string. Byte-oriented to preserve invalid UTF-8
func cmdEscape(s string) string {
buf := make([]rune, 0, len(s))
for _, r := range s {
if unicode.IsSpace(r) || r == '\\' || r == ';' || r == '#' {
buf = append(buf, '\\')
var buf strings.Builder
buf.Grow(len(s))
for i := 0; i < len(s); i++ {
c := s[i]
if c == '\\' || c == ';' || c == '#' || isCmdSpace(c) {
buf.WriteByte('\\')
}
buf = append(buf, r)
buf.WriteByte(c)
}
return string(buf)
return buf.String()
}

// cmdUnescape is used to remove backslashes that are used to escape
// whitespace and special characters in a given string.
func cmdUnescape(s string) string {
var buf strings.Builder
buf.Grow(len(s))
esc := false
buf := make([]rune, 0, len(s))
for _, r := range s {
for i := 0; i < len(s); i++ {
c := s[i]
if esc {
if !unicode.IsSpace(r) && r != '\\' && r != ';' && r != '#' {
buf = append(buf, '\\')
if c != '\\' && c != ';' && c != '#' && !isCmdSpace(c) {
buf.WriteByte('\\')
}
buf = append(buf, r)
buf.WriteByte(c)
esc = false
continue
}
if r == '\\' {
if c == '\\' {
esc = true
continue
}
esc = false
buf = append(buf, r)
buf.WriteByte(c)
}
if esc {
buf = append(buf, '\\')
buf.WriteByte('\\')
}
return string(buf)
return buf.String()
}

func isCmdSpace(c byte) bool {
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f'
}

// tokenize splits the given string by whitespace. It is aware of escaped
// and quoted whitespace so that they are not split unintentionally.
func tokenize(s string) []string {
esc := false
quote := false
var buf []rune
var buf []byte
var toks []string
for _, r := range s {
for i := 0; i < len(s); i++ {
c := s[i]
switch {
case esc:
esc = false
buf = append(buf, r)
case r == '\\':
buf = append(buf, c)
case c == '\\':
esc = true
buf = append(buf, r)
case r == '"':
buf = append(buf, c)
case c == '"':
quote = !quote
buf = append(buf, r)
case unicode.IsSpace(r) && !quote:
buf = append(buf, c)
case isCmdSpace(c) && !quote:
toks = append(toks, string(buf))
buf = nil
default:
buf = append(buf, r)
buf = append(buf, c)
}
}
return append(toks, string(buf))
Expand Down
6 changes: 6 additions & 0 deletions misc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ func TestCmdEscape(t *testing.T) {
{`foo\tbar`, `foo\\tbar`},
{"foo\tbar", "foo\\\tbar"},
{`foo\`, `foo\\`},
{"foo\xffbar", "foo\xffbar"},
{"foo\u00a0bar", "foo\u00a0bar"},
}

for _, test := range tests {
Expand All @@ -163,6 +165,8 @@ func TestCmdUnescape(t *testing.T) {
{`foo\\tbar`, `foo\tbar`},
{"foo\\\tbar", "foo\tbar"},
{`foo\`, `foo\`},
{"foo\xffbar", "foo\xffbar"},
{"foo\u00a0bar", "foo\u00a0bar"},
}

for _, test := range tests {
Expand Down Expand Up @@ -190,6 +194,8 @@ func TestTokenize(t *testing.T) {
{`\"foo bar\"`, []string{`\"foo`, `bar\"`}},
{`:rename foo\ bar`, []string{":rename", `foo\ bar`}},
{`!dir "C:\Program Files"`, []string{"!dir", `"C:\Program Files"`}},
{"foo\xffbar", []string{"foo\xffbar"}},
{"foo\u00a0bar", []string{"foo\u00a0bar"}},
}

for _, test := range tests {
Expand Down
28 changes: 16 additions & 12 deletions os.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,29 +230,33 @@ func quoteString(s string) string {
}

func shellEscape(s string) string {
buf := make([]rune, 0, len(s))
for _, r := range s {
if strings.ContainsRune(" !\"$&'()*,:;<=>?@[\\]^`{|}", r) {
buf = append(buf, '\\')
var buf strings.Builder
buf.Grow(len(s))
for i := 0; i < len(s); i++ {
c := s[i]
if strings.IndexByte(" !\"$&'()*,:;<=>?@[\\]^`{|}", c) >= 0 {
buf.WriteByte('\\')
}
buf = append(buf, r)
buf.WriteByte(c)
}
return string(buf)
return buf.String()
}

func shellUnescape(s string) string {
var buf strings.Builder
buf.Grow(len(s))
esc := false
buf := make([]rune, 0, len(s))
for _, r := range s {
if r == '\\' && !esc {
for i := 0; i < len(s); i++ {
c := s[i]
if c == '\\' && !esc {
esc = true
continue
}
esc = false
buf = append(buf, r)
buf.WriteByte(c)
}
if esc {
buf = append(buf, '\\')
buf.WriteByte('\\')
}
return string(buf)
return buf.String()
}