diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 978288e1..655107a2 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -294,10 +294,36 @@ void handle_button(xcb_button_press_event_t *event) { break; } - const size_t len = strlen(cur_ws->name) + strlen("workspace \"\"") + 1; - char buffer[len]; - snprintf(buffer, len, "workspace \"%s\"", cur_ws->name); + /* To properly handle workspace names with double quotes in them, we need + * to escape the double quotes. Unfortunately, that’s rather ugly in C: We + * first count the number of double quotes, then we allocate a large enough + * buffer, then we copy character by character. */ + int num_quotes = 0; + size_t namelen = 0; + for (char *walk = cur_ws->name; *walk != '\0'; walk++) { + if (*walk == '"') + num_quotes++; + /* While we’re looping through the name anyway, we can save one + * strlen(). */ + namelen++; + } + + const size_t len = namelen + strlen("workspace \"\"") + 1; + char *buffer = scalloc(len+num_quotes); + strncpy(buffer, "workspace \"", strlen("workspace \"")); + int inpos, outpos; + for (inpos = 0, outpos = strlen("workspace \""); + inpos < namelen; + inpos++, outpos++) { + if (cur_ws->name[inpos] == '"') { + buffer[outpos] = '\\'; + outpos++; + } + buffer[outpos] = cur_ws->name[inpos]; + } + buffer[++outpos] = '"'; i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, buffer); + free(buffer); } /* diff --git a/src/commands_parser.c b/src/commands_parser.c index 62dba861..1b030102 100644 --- a/src/commands_parser.c +++ b/src/commands_parser.c @@ -274,7 +274,18 @@ char *parse_command(const char *input) { } if (walk != beginning) { char *str = scalloc(walk-beginning + 1); - strncpy(str, beginning, walk-beginning); + /* We copy manually to handle escaping of characters. */ + int inpos, outpos; + for (inpos = 0, outpos = 0; + inpos < (walk-beginning); + inpos++, outpos++) { + /* We only handle escaped double quotes to not break + * backwards compatibility with people using \w in + * regular expressions etc. */ + if (beginning[inpos] == '\\' && beginning[inpos+1] == '"') + inpos++; + str[outpos] = beginning[inpos]; + } if (token->identifier) push_string(token->identifier, str); DLOG("str is \"%s\"\n", str); diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t index 1a11068b..ca81e2f6 100644 --- a/testcases/t/187-commands-parser.t +++ b/testcases/t/187-commands-parser.t @@ -157,4 +157,16 @@ is(parser_calls('move something to somewhere'), " ^^^^^^^^^^^^^^^^^^^^^^", 'error for unknown literal ok'); +################################################################################ +# 3: Verify that escaping of double quotes works correctly +################################################################################ + +is(parser_calls('workspace "foo"'), + 'cmd_workspace_name(foo)', + 'Command with simple double quotes ok'); + +is(parser_calls('workspace "foo \"bar"'), + 'cmd_workspace_name(foo "bar)', + 'Command with escaped double quotes ok'); + done_testing;