diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/nvim/os/shell.c | 25 | ||||
| -rw-r--r-- | src/nvim/strings.c | 48 | 
2 files changed, 61 insertions, 12 deletions
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 3813c45726..f5a1637c94 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -36,7 +36,6 @@ typedef struct {  # include "os/shell.c.generated.h"  #endif -  /// Builds the argument vector for running the user-configured 'shell' (p_sh)  /// with an optional command prefixed by 'shellcmdflag' (p_shcf).  /// @@ -45,9 +44,10 @@ typedef struct {  /// @return A newly allocated argument vector. It must be freed with  ///         `shell_free_argv` when no longer needed.  char **shell_build_argv(const char *cmd, const char *extra_args) +  FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC  { -  size_t argc = tokenize(p_sh, NULL) + tokenize(p_shcf, NULL); -  char **rv = xmalloc((unsigned)((argc + 4) * sizeof(char *))); +  size_t argc = tokenize(p_sh, NULL) + (cmd ? tokenize(p_shcf, NULL) : 0); +  char **rv = xmalloc((argc + 4) * sizeof(*rv));    // Split 'shell'    size_t i = tokenize(p_sh, rv); @@ -338,24 +338,22 @@ static void out_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data,  /// @param argv The vector that will be filled with copies of the parsed  ///        words. It can be NULL if the caller only needs to count words.  /// @return The number of words parsed. -static size_t tokenize(const char_u *str, char **argv) +static size_t tokenize(const char_u *const str, char **const argv) +  FUNC_ATTR_NONNULL_ARG(1)  { -  size_t argc = 0, len; -  char_u *p = (char_u *) str; +  size_t argc = 0; +  const char *p = (const char *) str;    while (*p != NUL) { -    len = word_length(p); +    const size_t len = word_length((const char_u *) p);      if (argv != NULL) {        // Fill the slot -      argv[argc] = xmalloc(len + 1); -      memcpy(argv[argc], p, len); -      argv[argc][len] = NUL; +      argv[argc] = vim_strnsave_unquoted(p, len);      }      argc++; -    p += len; -    p = skipwhite(p); +    p = (const char *) skipwhite((char_u *) (p + len));    }    return argc; @@ -377,6 +375,9 @@ static size_t word_length(const char_u *str)      if (*p == '"') {        // Found a quote character, switch the `inquote` flag        inquote = !inquote; +    } else if (*p == '\\' && inquote) { +      p++; +      length++;      }      p++; diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 00dcf3cf46..fe91141375 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -119,6 +119,54 @@ char_u *vim_strsave_escaped_ext(const char_u *string, const char_u *esc_chars,    return escaped_string;  } +/// Save a copy of an unquoted string +/// +/// Turns string like `a\bc"def\"ghi\\\n"jkl` into `a\bcdef"ghi\\njkl`, for use +/// in shell_build_argv: the only purpose of backslash is making next character +/// be treated literally inside the double quotes, if this character is +/// backslash or quote. +/// +/// @param[in]  string  String to copy. +/// @param[in]  length  Length of the string to copy. +/// +/// @return [allocated] Copy of the string. +char *vim_strnsave_unquoted(const char *const string, const size_t length) +  FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +  FUNC_ATTR_NONNULL_RET +{ +#define ESCAPE_COND(p, inquote, string_end) \ +  (*p == '\\' && inquote && p + 1 < string_end && (p[1] == '\\' || p[1] == '"')) +  size_t ret_length = 0; +  bool inquote = false; +  const char *const string_end = string + length; +  for (const char *p = string; p < string_end; p++) { +    if (*p == '"') { +      inquote = !inquote; +    } else if (ESCAPE_COND(p, inquote, string_end)) { +      ret_length++; +      p++; +    } else { +      ret_length++; +    } +  } + +  char *const ret = xmallocz(ret_length); +  char *rp = ret; +  inquote = false; +  for (const char *p = string; p < string_end; p++) { +    if (*p == '"') { +      inquote = !inquote; +    } else if (ESCAPE_COND(p, inquote, string_end)) { +      *rp++ = *(++p); +    } else { +      *rp++ = *p; +    } +  } +#undef ESCAPE_COND + +  return ret; +} +  /*   * Escape "string" for use as a shell argument with system().   * This uses single quotes, except when we know we need to use double quotes  | 
