diff --git a/client/common/client_cliprdr_file.c b/client/common/client_cliprdr_file.c index 448ca90bf..75768d09a 100644 --- a/client/common/client_cliprdr_file.c +++ b/client/common/client_cliprdr_file.c @@ -1955,7 +1955,7 @@ static BOOL cliprdr_local_file_new(CliprdrFileContext* context, CliprdrLocalFile *f = empty; f->context = context; - f->name = _strdup(path); + f->name = winpr_str_url_decode(path, strlen(path)); if (!f->name) goto fail; diff --git a/winpr/include/winpr/string.h b/winpr/include/winpr/string.h index c0d39b77c..95e9f39f6 100644 --- a/winpr/include/winpr/string.h +++ b/winpr/include/winpr/string.h @@ -33,6 +33,9 @@ extern "C" { #endif + WINPR_API char* winpr_str_url_encode(const char* str, size_t len); + WINPR_API char* winpr_str_url_decode(const char* str, size_t len); + WINPR_API BOOL winpr_str_append(const char* what, char* buffer, size_t size, const char* separator); diff --git a/winpr/libwinpr/clipboard/clipboard.c b/winpr/libwinpr/clipboard/clipboard.c index da6d2b12f..05d57c42b 100644 --- a/winpr/libwinpr/clipboard/clipboard.c +++ b/winpr/libwinpr/clipboard/clipboard.c @@ -726,10 +726,9 @@ char* parse_uri_to_local_file(const char* uri, size_t uri_len) } while (0); - buffer = calloc(localLen + 1, sizeof(char)); + buffer = winpr_str_url_decode(localName, localLen); if (buffer) { - memcpy(buffer, localName, localLen); if (buffer[1] == '|' && ((buffer[0] >= 'A' && buffer[0] <= 'Z') || (buffer[0] >= 'a' && buffer[0] <= 'z'))) buffer[1] = ':'; diff --git a/winpr/libwinpr/crt/string.c b/winpr/libwinpr/crt/string.c index f460c3ec9..308548872 100644 --- a/winpr/libwinpr/crt/string.c +++ b/winpr/libwinpr/crt/string.c @@ -39,6 +39,92 @@ #define MIN(x, y) (((x) < (y)) ? (x) : (y)) #endif +static const char rfc3986[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x2e, 0x00, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x5f, + 0x00, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x00, 0x00, 0x00, 0x7e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static char hex2bin(char what) +{ + if (what >= 'a') + what -= 'a' - 'A'; + if (what >= 'A') + what -= ('A' - 10); + else + what -= '0'; + return what; +} + +static char unescape(const char* what, size_t* px) +{ + if ((*what == '%') && (isxdigit(what[1]) && isxdigit(what[2]))) + { + *px += 2; + return 16 * hex2bin(what[1]) + hex2bin(what[2]); + } + + if (*what == '+') + return ' '; + + return *what; +} + +char* winpr_str_url_decode(const char* str, size_t len) +{ + char* dst = calloc(len + 1, sizeof(char)); + if (!dst) + return NULL; + + size_t pos = 0; + for (size_t x = 0; x < strnlen(str, len); x++) + { + const char* cur = &str[x]; + dst[pos++] = unescape(cur, &x); + } + return dst; +} + +static char* escape(char* dst, char what) +{ + if (rfc3986[what & 0xff]) + { + *dst = what; + return dst + 1; + } + + sprintf(dst, "%%%02" PRIX8, what & 0xff); + return dst + 3; +} + +char* winpr_str_url_encode(const char* str, size_t len) +{ + char* dst = calloc(len + 1, sizeof(char) * 3); + if (!dst) + return NULL; + + char* ptr = dst; + for (size_t x = 0; x < strnlen(str, len); x++) + { + const char cur = str[x]; + ptr = escape(ptr, cur); + } + return dst; +} + BOOL winpr_str_append(const char* what, char* buffer, size_t size, const char* separator) { const size_t used = strnlen(buffer, size); diff --git a/winpr/libwinpr/crt/test/TestString.c b/winpr/libwinpr/crt/test/TestString.c index 2d284aeac..dc1434de5 100644 --- a/winpr/libwinpr/crt/test/TestString.c +++ b/winpr/libwinpr/crt/test/TestString.c @@ -27,6 +27,56 @@ static WCHAR testDelimiter[] = { '\r', '\n', '\0' }; #define testDelimiter_Length ((sizeof(testDelimiter) / sizeof(WCHAR)) - 1) +struct url_test_pair +{ + const char* what; + const char* escaped; +}; + +static const struct url_test_pair url_tests[] = { + { "xxx%bar gaee#%%#%{h}g{f{e%d|c\\b^a~p[q]r`s;t/u?v:w@x=y&z$xxx", + "xxx%25bar%20ga%3Cka%3Eee%23%25%25%23%25%7Bh%7Dg%7Bf%7Be%25d%7Cc%5Cb%5Ea~p%5Bq%5Dr%60s%3Bt%" + "2Fu%3Fv%3Aw%40x%3Dy%26z%24xxx" }, + { "äöúëü", "%C3%A4%C3%B6%C3%BA%C3%AB%C3%BC" }, + { "🎅🏄🤘😈", "%F0%9F%8E%85%F0%9F%8F%84%F0%9F%A4%98%F0%9F%98%88" } +}; + +static BOOL test_url_escape(void) +{ + for (size_t x = 0; x < ARRAYSIZE(url_tests); x++) + { + const struct url_test_pair* cur = &url_tests[x]; + + char* escaped = winpr_str_url_encode(cur->what, strlen(cur->what) + 1); + char* what = winpr_str_url_decode(cur->escaped, strlen(cur->escaped) + 1); + + const size_t elen = strlen(escaped); + const size_t wlen = strlen(what); + const size_t pelen = strlen(cur->escaped); + const size_t pwlen = strlen(cur->what); + BOOL rc = TRUE; + if (!escaped || (elen != pelen) || (strcmp(escaped, cur->escaped) != 0)) + { + printf("expected: [%" PRIuz "] %s\n", pelen, cur->escaped); + printf("got : [%" PRIuz "] %s\n", elen, escaped); + rc = FALSE; + } + else if (!what || (wlen != pwlen) || (strcmp(what, cur->what) != 0)) + { + printf("expected: [%" PRIuz "] %s\n", pwlen, cur->what); + printf("got : [%" PRIuz "] %s\n", wlen, what); + rc = FALSE; + } + + free(escaped); + free(what); + if (!rc) + return FALSE; + } + + return TRUE; +} + int TestString(int argc, char* argv[]) { const WCHAR* p; @@ -37,6 +87,9 @@ int TestString(int argc, char* argv[]) WINPR_UNUSED(argc); WINPR_UNUSED(argv); + if (!test_url_escape()) + return -1; + #ifdef __BIG_ENDIAN__ /* Be sure that we always use LE encoded string */ ByteSwapUnicode(testStringW, testStringW_Length);