mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-26 18:22:09 +02:00
Fix handling of empty quoted string in shlex
This commit is contained in:
@@ -15,7 +15,7 @@ typedef enum { NORMAL, WORD, STRING_WITHOUT_ESCAPES, STRING_WITH_ESCAPES, ANSI_C
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *src;
|
const char *src;
|
||||||
bool support_ansi_c_quoting;
|
bool support_ansi_c_quoting, allow_empty;
|
||||||
char *buf;
|
char *buf;
|
||||||
size_t src_sz, src_pos, word_start, buf_pos;
|
size_t src_sz, src_pos, word_start, buf_pos;
|
||||||
ShlexEnum state;
|
ShlexEnum state;
|
||||||
@@ -90,6 +90,7 @@ static size_t
|
|||||||
get_word(ShlexState *self) {
|
get_word(ShlexState *self) {
|
||||||
size_t ans = self->buf_pos; self->buf_pos = 0;
|
size_t ans = self->buf_pos; self->buf_pos = 0;
|
||||||
self->buf[ans] = 0;
|
self->buf[ans] = 0;
|
||||||
|
self->allow_empty = false;
|
||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +202,7 @@ next_word(ShlexState *self) {
|
|||||||
break;
|
break;
|
||||||
case WORD:
|
case WORD:
|
||||||
switch(ch) {
|
switch(ch) {
|
||||||
case WHITESPACE: set_state(self, NORMAL); if (self->buf_pos) return get_word(self); break;
|
case WHITESPACE: set_state(self, NORMAL); if (self->buf_pos || self->allow_empty) return get_word(self); break;
|
||||||
case STRING_WITH_ESCAPES_DELIM: set_state(self, STRING_WITH_ESCAPES); break;
|
case STRING_WITH_ESCAPES_DELIM: set_state(self, STRING_WITH_ESCAPES); break;
|
||||||
case STRING_WITHOUT_ESCAPES_DELIM:
|
case STRING_WITHOUT_ESCAPES_DELIM:
|
||||||
if (self->support_ansi_c_quoting && prev_word_ch == '$') { self->buf_pos--; set_state(self, ANSI_C_QUOTED); }
|
if (self->support_ansi_c_quoting && prev_word_ch == '$') { self->buf_pos--; set_state(self, ANSI_C_QUOTED); }
|
||||||
@@ -212,18 +213,18 @@ next_word(ShlexState *self) {
|
|||||||
} break;
|
} break;
|
||||||
case STRING_WITHOUT_ESCAPES:
|
case STRING_WITHOUT_ESCAPES:
|
||||||
switch(ch) {
|
switch(ch) {
|
||||||
case STRING_WITHOUT_ESCAPES_DELIM: set_state(self, WORD); break;
|
case STRING_WITHOUT_ESCAPES_DELIM: set_state(self, WORD); self->allow_empty = true; break;
|
||||||
default: write_ch(self, ch); break;
|
default: write_ch(self, ch); break;
|
||||||
} break;
|
} break;
|
||||||
case STRING_WITH_ESCAPES:
|
case STRING_WITH_ESCAPES:
|
||||||
switch(ch) {
|
switch(ch) {
|
||||||
case STRING_WITH_ESCAPES_DELIM: set_state(self, WORD); break;
|
case STRING_WITH_ESCAPES_DELIM: set_state(self, WORD); self->allow_empty = true; break;
|
||||||
case ESCAPE_CHAR: write_escaped_or_fail(); break;
|
case ESCAPE_CHAR: write_escaped_or_fail(); break;
|
||||||
default: write_ch(self, ch); break;
|
default: write_ch(self, ch); break;
|
||||||
} break;
|
} break;
|
||||||
case ANSI_C_QUOTED:
|
case ANSI_C_QUOTED:
|
||||||
switch(ch) {
|
switch(ch) {
|
||||||
case STRING_WITHOUT_ESCAPES_DELIM: set_state(self, WORD); break;
|
case STRING_WITHOUT_ESCAPES_DELIM: set_state(self, WORD); self->allow_empty = true; break;
|
||||||
case ESCAPE_CHAR: if (!write_ansi_escape_ch(self)) return -1; break;
|
case ESCAPE_CHAR: if (!write_ansi_escape_ch(self)) return -1; break;
|
||||||
default: write_ch(self, ch); break;
|
default: write_ch(self, ch); break;
|
||||||
} break;
|
} break;
|
||||||
@@ -232,7 +233,7 @@ next_word(ShlexState *self) {
|
|||||||
switch (self->state) {
|
switch (self->state) {
|
||||||
case WORD:
|
case WORD:
|
||||||
self->state = NORMAL;
|
self->state = NORMAL;
|
||||||
if (self->buf_pos) return get_word(self);
|
if (self->buf_pos || self->allow_empty) return get_word(self);
|
||||||
break;
|
break;
|
||||||
case STRING_WITH_ESCAPES: case STRING_WITHOUT_ESCAPES: case ANSI_C_QUOTED:
|
case STRING_WITH_ESCAPES: case STRING_WITHOUT_ESCAPES: case ANSI_C_QUOTED:
|
||||||
self->err = "Unterminated string at the end of input";
|
self->err = "Unterminated string at the end of input";
|
||||||
|
|||||||
@@ -683,6 +683,12 @@ class TestDataTypes(BaseTest):
|
|||||||
tuple(shlex_split(bad))
|
tuple(shlex_split(bad))
|
||||||
|
|
||||||
for q, expected in {
|
for q, expected in {
|
||||||
|
'a""': ((0, 'a'),),
|
||||||
|
'a""b': ((0, 'ab'),),
|
||||||
|
'-1 "" 2': ((0, '-1'), (3, ''), (6, '2')),
|
||||||
|
"-1 '' 2": ((0, '-1'), (3, ''), (6, '2')),
|
||||||
|
'a ""': ((0, 'a'), (2, '')),
|
||||||
|
'""': ((0, ''),),
|
||||||
'"ab"': ((0, 'ab'),),
|
'"ab"': ((0, 'ab'),),
|
||||||
r'x "ab"y \m': ((0, 'x'), (2, 'aby'), (8, 'm')),
|
r'x "ab"y \m': ((0, 'x'), (2, 'aby'), (8, 'm')),
|
||||||
r'''x'y"\z'1''': ((0, 'xy"\\z1'),),
|
r'''x'y"\z'1''': ((0, 'xy"\\z1'),),
|
||||||
@@ -694,11 +700,11 @@ class TestDataTypes(BaseTest):
|
|||||||
'😀 a': ((0, '😀'), (2, 'a')),
|
'😀 a': ((0, '😀'), (2, 'a')),
|
||||||
' \t😀a': ((2, '😀a'),),
|
' \t😀a': ((2, '😀a'),),
|
||||||
}.items():
|
}.items():
|
||||||
actual = tuple(shlex_split_with_positions(q))
|
|
||||||
self.ae(expected, actual, f'Failed for text: {q!r}')
|
|
||||||
ex = tuple(x[1] for x in expected)
|
ex = tuple(x[1] for x in expected)
|
||||||
actual = tuple(shlex_split(q))
|
actual = tuple(shlex_split(q))
|
||||||
self.ae(ex, actual, f'Failed for text: {q!r}')
|
self.ae(ex, actual, f'Failed for text: {q!r}')
|
||||||
|
actual = tuple(shlex_split_with_positions(q))
|
||||||
|
self.ae(expected, actual, f'Failed for text: {q!r}')
|
||||||
|
|
||||||
for q, expected in {
|
for q, expected in {
|
||||||
"$'ab'": ((0, 'ab'),),
|
"$'ab'": ((0, 'ab'),),
|
||||||
|
|||||||
Reference in New Issue
Block a user