diff --git a/src/util/str.c b/src/util/str.c index 9e5b2ad3e..bff408389 100644 --- a/src/util/str.c +++ b/src/util/str.c @@ -1415,3 +1415,40 @@ done: git_str_dispose(&replaced); return error; } + +int git_str_shellquote(git_str *buf) +{ + git_str quoted = GIT_STR_INIT; + size_t i; + int error = 0; + + ENSURE_SIZE("ed, buf->size); + + git_str_putc("ed, '\''); + + for (i = 0; i < buf->size; i++) { + switch (buf->ptr[i]) { + case '\'': + case '!': + git_str_puts("ed, "'\\"); + git_str_putc("ed, buf->ptr[i]); + git_str_putc("ed, '\''); + break; + default: + git_str_putc("ed, buf->ptr[i]); + } + } + + git_str_putc("ed, '\''); + + if (git_str_oom("ed)) { + error = -1; + goto done; + } + + git_str_swap("ed, buf); + +done: + git_str_dispose("ed); + return error; +} diff --git a/src/util/str.h b/src/util/str.h index cc8045f13..df0ebae4c 100644 --- a/src/util/str.h +++ b/src/util/str.h @@ -366,4 +366,10 @@ int git_str_replace( const char *replacements[][2], size_t replacements_len); +/** + * Quote for shell safety. Wrap the given buffer in single quotes, + * escaping any single quotes and exclamation points. + */ +int git_str_shellquote(git_str *buf); + #endif diff --git a/tests/util/str/basic.c b/tests/util/str/basic.c index 3661dfa28..ef824624c 100644 --- a/tests/util/str/basic.c +++ b/tests/util/str/basic.c @@ -64,3 +64,32 @@ void test_str_basic__replace(void) git_str_dispose(&buf); } + +void test_str_basic__shellquote(void) +{ + git_str buf = GIT_BUF_INIT; + + cl_git_pass(git_str_puts(&buf, "filename")); + cl_git_pass(git_str_shellquote(&buf)); + cl_assert_equal_s("\'filename\'", buf.ptr); + + git_str_clear(&buf); + + cl_git_pass(git_str_puts(&buf, "file name")); + cl_git_pass(git_str_shellquote(&buf)); + cl_assert_equal_s("\'file name\'", buf.ptr); + + git_str_clear(&buf); + + cl_git_pass(git_str_puts(&buf, "file\'name")); + cl_git_pass(git_str_shellquote(&buf)); + cl_assert_equal_s("\'file\'\\\'\'name\'", buf.ptr); + + git_str_clear(&buf); + + cl_git_pass(git_str_puts(&buf, "file!name")); + cl_git_pass(git_str_shellquote(&buf)); + cl_assert_equal_s("\'file\'\\!\'name\'", buf.ptr); + + git_str_dispose(&buf); +}