diff options
-rw-r--r-- | src/bfstd.c | 46 | ||||
-rw-r--r-- | src/bfstd.h | 13 |
2 files changed, 59 insertions, 0 deletions
diff --git a/src/bfstd.c b/src/bfstd.c index 4e1175f..cfff426 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -544,3 +544,49 @@ size_t xstrwidth(const char *str) { return ret; } + +char *wordesc(const char *str) { + size_t len = strlen(str); + + if (strcspn(str, "|&;<>()$`\\\"' \t\n*?[#˜=%") == len) { + // Whole string is safe + // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02 + if (len > 0) { + return strdup(str); + } else { + return strdup("\"\""); + } + } else if (strcspn(str, "`$\\\"") == len) { + // Safe to double-quote the whole string + // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_03 + char *ret = malloc(len + 3); + if (!ret) { + return NULL; + } + + char *cur = stpcpy(ret, "\""); + cur = stpcpy(cur, str); + cur = stpcpy(cur, "\""); + return ret; + } + + // Every ' is replaced with '\'', so at most a 3x growth + char *ret = malloc(3 * len + 3); + if (!ret) { + return NULL; + } + + char *cur = stpcpy(ret, "'"); + while (*str) { + size_t chunk = strcspn(str, "'"); + cur = stpncpy(cur, str, chunk); + str += chunk; + if (*str) { + cur = stpcpy(cur, "'\\''"); + ++str; + } + } + cur = stpcpy(cur, "'"); + + return ret; +} diff --git a/src/bfstd.h b/src/bfstd.h index ee4cf16..750847e 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -277,4 +277,17 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long * */ size_t xstrwidth(const char *str); +// #include <wordexp.h> + +/** + * Escape a string as a single shell word. + * + * @param str + * The string to escape. + * @return + * A string that a shell would evaluate to str, dynamically allocated, + * or NULL on failure. + */ +char *wordesc(const char *str); + #endif // BFS_BFSTD_H |