nnn

mirror of https://github.com/jarun/nnn
Log | Files | Refs | README | LICENSE

commit febf442f7d922d570d7e77cb1c57d8e32244e433
parent 339356065d5be1683a8f1265232239f7762d1c7d
Author: Arun Prakash Jana <engineerarun@gmail.com>
Date:   Sun, 29 Mar 2026 18:20:35 +0530

Allow shell_escape() to dynamically allocate memory

Diffstat:
Msrc/nnn.c | 134++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 116 insertions(+), 18 deletions(-)

diff --git a/src/nnn.c b/src/nnn.c @@ -1391,41 +1391,132 @@ static char *bmtarget(const char *filepath, char *cwd, char *buf) return NULL; } -/* wraps the argument in single quotes so it can be safely fed to shell */ -static ssize_t shell_escape(char *output, size_t outlen, const char *s) +/** + * shell_escape - Escape a string for safe shell usage with single quotes + * + * @poutbuf: Pointer to output buffer pointer. If *poutbuf is NULL, memory will be + * dynamically allocated. If *poutbuf is non-NULL, it must point to a + * buffer of at least *outlen bytes. In dynamic allocation mode, the + * caller is responsible for freeing the allocated memory. + * + * @outlen: Pointer to the output buffer size. When input is provided: + * - The caller allocates a buffer and sets *outlen to its size + * - The buffer must be at least 3 bytes + * When poutbuf is NULL (dynamic allocation): + * - Set *outlen to the desired initial chunk size (or 0 for default 256) + * - It will be updated to track the allocated size + * - The caller must free the memory in *poutbuf when done + * + * @inbuf: The input string to escape (must not be NULL) + * + * Return value: On success, returns the length of the escaped string (not including + * the null terminator). On error, returns -1 and sets errno: + * - EINVAL: if inbuf == poutbuf or outlen < 3 (for fixed buffers) + * - ENAMETOOLONG: if the output buffer is too small (for fixed buffers) + * - ENOMEM: if memory allocation fails (for dynamic buffers) + * + * Usage examples: + * + * Example 1: Using a fixed-size buffer (caller-allocated): + * char buf[256]; + * size_t len = sizeof(buf); + * ssize_t result = shell_escape(&buf, &len, "my file.txt"); + * if (result >= 0) { + * printf("Escaped: %s", buf); + * } + * + * Example 2: Using dynamic allocation (caller must free): + * char *escaped = NULL; + * size_t capacity = 0; // 0 uses default initial size + * ssize_t result = shell_escape(&escaped, &capacity, "my file.txt"); + * if (result >= 0) { + * printf("Escaped: %s", escaped); + * free(escaped); + * } else { + * fprintf(stderr, "Error: %s", strerror(errno)); + * } + * + * Example 3: Dynamic allocation with specific initial size: + * char *escaped = NULL; + * size_t capacity = 512; // Start with 512 byte chunks + * ssize_t result = shell_escape(&escaped, &capacity, input_path); + * if (result >= 0) { + * use_escaped_path(escaped); + * free(escaped); + * } + */ +static ssize_t shell_escape(char **poutbuf, size_t *outlen, const char *inbuf) { - size_t n = xstrlen(s), w = 0; + size_t n = xstrlen(inbuf), w = 0; + char *buf; + size_t buflen; + int is_dynamic = (*poutbuf == NULL); - if (s == output || outlen < 3) { + if (inbuf == (const char *)(*poutbuf)) { errno = EINVAL; return -1; } - output[w++] = '\''; /* begin single quote */ + /* Initialize buffer for dynamic allocation */ + if (is_dynamic) { + buflen = (*outlen > 0) ? *outlen : 256; /* default 256 byte chunks */ + buf = malloc(buflen); + if (!buf) { + errno = ENOMEM; + return -1; + } + *poutbuf = buf; + *outlen = buflen; + } else { + buf = *poutbuf; + buflen = *outlen; + if (buflen < 3) { + errno = EINVAL; + return -1; + } + } + + buf[w++] = '\''; /* begin single quote */ for (size_t r = 0; r < n; ++r) { /* potentially too big: 4 for the single quote case, 2 from * outside the loop */ - if (w + 6 >= outlen) { - errno = ENAMETOOLONG; - return -1; + if (w + 6 >= buflen) { + if (is_dynamic) { + /* Grow buffer by chunk size for dynamic allocation */ + size_t newsize = buflen + (*outlen > 0 ? *outlen : 256); + char *newbuf = realloc(buf, newsize); + if (!newbuf) { + free(buf); + *poutbuf = NULL; + errno = ENOMEM; + return -1; + } + buf = newbuf; + buflen = newsize; + *poutbuf = buf; + *outlen = buflen; + } else { + errno = ENAMETOOLONG; + return -1; + } } - switch (s[r]) { + switch (inbuf[r]) { /* the only thing that has special meaning inside single * quotes are single quotes themselves. */ case '\'': - output[w++] = '\''; /* end single quote */ - output[w++] = '\\'; /* put \' so it's treated as literal single quote */ - output[w++] = '\''; - output[w++] = '\''; /* start single quoting again */ + buf[w++] = '\''; /* end single quote */ + buf[w++] = '\\'; /* put \' so it's treated as literal single quote */ + buf[w++] = '\''; + buf[w++] = '\''; /* start single quoting again */ break; default: - output[w++] = s[r]; + buf[w++] = inbuf[r]; break; } } - output[w++] = '\''; /* end single quote */ - output[w] = '\0'; /* nul terminator */ + buf[w++] = '\''; /* end single quote */ + buf[w] = '\0'; /* nul terminator */ return w; } @@ -2922,8 +3013,15 @@ static void write_lastdir(const char *curpath, const char *outfile) ? (tilde ? g_buf : outfile) : cfgpath, O_CREAT | O_WRONLY | O_TRUNC, S_IWUSR | S_IRUSR); if (fd >= 0) { - memcpy(g_buf, "cd ", 3); // NOLINT - ssize_t l = shell_escape(g_buf + 3, sizeof(g_buf) - 3, curpath); + char *bufptr = g_buf; + size_t buflen = sizeof(g_buf) - 3; + + *bufptr++ = 'c'; + *bufptr++ = 'd'; + *bufptr++ = ' '; + *bufptr = '\0'; + + ssize_t l = shell_escape(&bufptr, &buflen, curpath); ok = l >= 0 && write(fd, g_buf, l + 3) == l + 3; close(fd); }