diff --git a/src/path.c b/src/path.c index e4b49f35d..bd62a3e4d 100644 --- a/src/path.c +++ b/src/path.c @@ -237,3 +237,38 @@ void git_path_string_to_dir(char* path, size_t size) } } +int git__percent_decode(git_buf *decoded_out, const char *input) +{ + int len, hi, lo, i, error = GIT_SUCCESS; + assert(decoded_out && input); + + len = strlen(input); + git_buf_clear(decoded_out); + + for(i = 0; i < len; i++) + { + char c = input[i]; + + if (c != '%') + goto append; + + if (i >= len - 2) + goto append; + + hi = git__fromhex(input[i + 1]); + lo = git__fromhex(input[i + 2]); + + if (hi < 0 || lo < 0) + goto append; + + c = (char)(hi << 4 | lo); + i += 2; + +append: + error = git_buf_putc(decoded_out, c); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to percent decode '%s'.", input); + } + + return error; +} diff --git a/src/path.h b/src/path.h index 0c8cc349c..6397feedf 100644 --- a/src/path.h +++ b/src/path.h @@ -74,4 +74,6 @@ GIT_INLINE(void) git_path_mkposix(char *path) # define git_path_mkposix(p) /* blank */ #endif +extern int git__percent_decode(git_buf *decoded_out, const char *input); + #endif diff --git a/tests-clay/clay.h b/tests-clay/clay.h index c2b69c8a5..b2b31bef7 100644 --- a/tests-clay/clay.h +++ b/tests-clay/clay.h @@ -116,6 +116,7 @@ extern void test_core_path__5_joins(void); extern void test_core_path__6_long_joins(void); extern void test_core_path__7_path_to_dir(void); extern void test_core_path__8_self_join(void); +extern void test_core_path__9_percent_decode(void); extern void test_core_rmdir__delete_recursive(void); extern void test_core_rmdir__fail_to_delete_non_empty_dir(void); extern void test_core_rmdir__initialize(void); diff --git a/tests-clay/clay_main.c b/tests-clay/clay_main.c index 7df342cdf..e6bb80440 100644 --- a/tests-clay/clay_main.c +++ b/tests-clay/clay_main.c @@ -180,7 +180,8 @@ static const struct clay_func _clay_cb_core_path[] = { {"5_joins", &test_core_path__5_joins}, {"6_long_joins", &test_core_path__6_long_joins}, {"7_path_to_dir", &test_core_path__7_path_to_dir}, - {"8_self_join", &test_core_path__8_self_join} + {"8_self_join", &test_core_path__8_self_join}, + {"9_percent_decode", &test_core_path__9_percent_decode} }; static const struct clay_func _clay_cb_core_rmdir[] = { {"delete_recursive", &test_core_rmdir__delete_recursive}, @@ -381,7 +382,7 @@ static const struct clay_suite _clay_suites[] = { "core::path", {NULL, NULL}, {NULL, NULL}, - _clay_cb_core_path, 7 + _clay_cb_core_path, 8 }, { "core::rmdir", @@ -548,7 +549,7 @@ static const struct clay_suite _clay_suites[] = { }; static size_t _clay_suite_count = 39; -static size_t _clay_callback_count = 123; +static size_t _clay_callback_count = 124; /* Core test functions */ static void diff --git a/tests-clay/core/path.c b/tests-clay/core/path.c index 49f85f085..8744247d2 100644 --- a/tests-clay/core/path.c +++ b/tests-clay/core/path.c @@ -273,3 +273,27 @@ void test_core_path__8_self_join(void) git_buf_free(&path); } + +static void check_percent_decoding(const char *expected_result, const char *input) +{ + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git__percent_decode(&buf, input)); + cl_assert_strequal(expected_result, git_buf_cstr(&buf)); + + git_buf_free(&buf); +} + +void test_core_path__9_percent_decode(void) +{ + check_percent_decoding("abcd", "abcd"); + check_percent_decoding("a2%", "a2%"); + check_percent_decoding("a2%3", "a2%3"); + check_percent_decoding("a2%%3", "a2%%3"); + check_percent_decoding("a2%3z", "a2%3z"); + check_percent_decoding("a,", "a%2c"); + check_percent_decoding("a21", "a2%31"); + check_percent_decoding("a2%1", "a2%%31"); + check_percent_decoding("a bc ", "a%20bc%20"); + check_percent_decoding("Vicent Mart" "\355", "Vicent%20Mart%ED"); +}