From: Michihiro NAKAJIMA Date: Thu, 8 Sep 2011 06:18:18 +0000 (-0400) Subject: Fix issue 174. X-Git-Tag: v3.0.0a~69 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=057322dfa8692c886eecf062be9fd80c862b7858;p=thirdparty%2Flibarchive.git Fix issue 174. Correctly handle Windows style full-path names, which are leading \\.\ or \\?\ or \\?\\UNC\ or \\?\Volume{GUID}\ or C:\, in archive_write_disk_windows.c. SVN-Revision: 3692 --- diff --git a/libarchive/archive_write_disk_windows.c b/libarchive/archive_write_disk_windows.c index 863dcdc9e..1dcf9d0dd 100644 --- a/libarchive/archive_write_disk_windows.c +++ b/libarchive/archive_write_disk_windows.c @@ -1864,6 +1864,22 @@ check_symlinks(struct archive_write_disk *a) return (ARCHIVE_OK); } +static int +guidword(wchar_t *p, int n) +{ + int i; + + for (i = 0; i < n; i++) { + if ((*p >= L'0' && *p <= L'9') || + (*p >= L'a' && *p <= L'f') || + (*p >= L'A' && *p <= L'F')) + p++; + else + return (-1); + } + return (0); +} + /* * Canonicalize the pathname. In particular, this strips duplicate * '\' characters, '.' elements, and trailing '\'. It also raises an @@ -1873,20 +1889,95 @@ check_symlinks(struct archive_write_disk *a) static int cleanup_pathname(struct archive_write_disk *a) { - wchar_t *dest, *src, *p; + wchar_t *dest, *src, *p, *top; wchar_t separator = L'\0'; - dest = src = a->name; - if (*src == L'\0') { + p = a->name; + if (*p == L'\0') { archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Invalid empty pathname"); return (ARCHIVE_FAILED); } - for (p = a->name; *p != L'\0'; p++) { + /* Replace '/' by '\' */ + for (; *p != L'\0'; p++) { if (*p == L'/') *p = L'\\'; - /* Rewrite the path name if its character is a unusable. */ + } + p = a->name; + + /* Skip leading "\\.\" or "\\?\" or "\\?\UNC\" or + * "\\?\Volume{GUID}\" + * (absolute path prefixes used by Windows API) */ + if (p[0] == L'\\' && p[1] == L'\\' && + (p[2] == L'.' || p[2] == L'?') && p[3] == L'\\') + { + /* A path begin with "\\?\UNC\" */ + if (p[2] == L'?' && + (p[4] == L'U' || p[4] == L'u') && + (p[5] == L'N' || p[5] == L'n') && + (p[6] == L'C' || p[6] == L'c') && + p[7] == L'\\') + p += 8; + /* A path begin with "\\?\Volume{GUID}\" */ + else if (p[2] == L'?' && + (p[4] == L'V' || p[4] == L'v') && + (p[5] == L'O' || p[5] == L'o') && + (p[6] == L'L' || p[6] == L'l') && + (p[7] == L'U' || p[7] == L'u') && + (p[8] == L'M' || p[8] == L'm') && + (p[9] == L'E' || p[9] == L'e') && + p[10] == L'{') { + if (guidword(p+11, 8) == 0 && p[19] == L'-' && + guidword(p+20, 4) == 0 && p[24] == L'-' && + guidword(p+25, 4) == 0 && p[29] == L'-' && + guidword(p+30, 4) == 0 && p[34] == L'-' && + guidword(p+35, 12) == 0 && p[47] == L'}' && + p[48] == L'\\') + p += 49; + else + p += 4; + /* A path begin with "\\.\PhysicalDriveX" */ + } else if (p[2] == L'.' && + (p[4] == L'P' || p[4] == L'p') && + (p[5] == L'H' || p[5] == L'h') && + (p[6] == L'Y' || p[6] == L'y') && + (p[7] == L'S' || p[7] == L's') && + (p[8] == L'I' || p[8] == L'i') && + (p[9] == L'C' || p[9] == L'c') && + (p[9] == L'A' || p[9] == L'a') && + (p[9] == L'L' || p[9] == L'l') && + (p[9] == L'D' || p[9] == L'd') && + (p[9] == L'R' || p[9] == L'r') && + (p[9] == L'I' || p[9] == L'i') && + (p[9] == L'V' || p[9] == L'v') && + (p[9] == L'E' || p[9] == L'e') && + (p[10] >= L'0' && p[10] <= L'9') && + p[11] == L'\0') { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Path is a physical drive name"); + return (ARCHIVE_FAILED); + } else + p += 4; + } + + /* Skip leading drive letter from archives created + * on Windows. */ + if (((p[0] >= L'a' && p[0] <= L'z') || + (p[0] >= L'A' && p[0] <= L'Z')) && + p[1] == L':') { + if (p[2] == L'\0') { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Path is a drive name"); + return (ARCHIVE_FAILED); + } + if (p[2] == L'\\') + p += 2; + } + + top = dest = src = p; + /* Rewrite the path name if its character is a unusable. */ + for (; *p != L'\0'; p++) { if (*p == L':' || *p == L'*' || *p == L'?' || *p == L'"' || *p == L'<' || *p == L'>' || *p == L'|') *p = L'_'; @@ -1915,7 +2006,8 @@ cleanup_pathname(struct archive_write_disk *a) } else if (src[1] == L'.') { if (src[2] == L'\\' || src[2] == L'\0') { /* Conditionally warn about '..' */ - if (a->flags & ARCHIVE_EXTRACT_SECURE_NODOTDOT) { + if (a->flags & + ARCHIVE_EXTRACT_SECURE_NODOTDOT) { archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Path contains '..'"); @@ -1949,7 +2041,7 @@ cleanup_pathname(struct archive_write_disk *a) * We've just copied zero or more path elements, not including the * final '\'. */ - if (dest == a->name) { + if (dest == top) { /* * Nothing got copied. The path must have been something * like '.' or '\' or './' or '/././././/./'. diff --git a/libarchive/test/test_write_disk.c b/libarchive/test/test_write_disk.c index 9a4475599..60bcdc24e 100644 --- a/libarchive/test/test_write_disk.c +++ b/libarchive/test/test_write_disk.c @@ -195,13 +195,13 @@ static void create_reg_file_win(struct archive_entry *ae, const char *msg) { static const char data[]="abcdefghijklmnopqrstuvwxyz"; struct archive *ad; - struct stat st; - char *p, *fname; + struct _stat st; + wchar_t *p, *fname; size_t l; /* Write the entry to disk. */ assert((ad = archive_write_disk_new()) != NULL); - archive_write_disk_set_options(ad, ARCHIVE_EXTRACT_TIME); + archive_write_disk_set_options(ad, ARCHIVE_EXTRACT_TIME); failure("%s", msg); archive_entry_set_size(ae, sizeof(data)); archive_entry_set_mtime(ae, 123456789, 0); @@ -211,25 +211,38 @@ static void create_reg_file_win(struct archive_entry *ae, const char *msg) assertEqualInt(0, archive_write_free(ad)); /* Test the entries on disk. */ - l = strlen(archive_entry_pathname(ae)); - fname = malloc(l + 1); + l = wcslen(archive_entry_pathname_w(ae)); + fname = malloc((l + 1) * sizeof(wchar_t)); assert(NULL != fname); - strcpy(fname, archive_entry_pathname(ae)); + wcscpy(fname, archive_entry_pathname_w(ae)); + p = fname; + /* Skip leading drive letter from archives created + * on Windows. */ + if (((p[0] >= L'a' && p[0] <= L'z') || + (p[0] >= L'A' && p[0] <= L'Z')) && + p[1] == L':' && p[2] == L'\\') { + p += 3; + } /* Replace unusable characters in Windows to '_' */ - for (p = fname; *p != '\0'; p++) - if (*p == ':' || *p == '*' || *p == '?' || - *p == '"' || *p == '<' || *p == '>' || *p == '|') + for (; *p != L'\0'; p++) + if (*p == L':' || *p == L'*' || *p == L'?' || + *p == L'"' || *p == L'<' || *p == L'>' || *p == L'|') *p = '_'; - assert(0 == stat(fname, &st)); + assert(0 == _wstat(fname, &st)); failure("st.st_mode=%o archive_entry_mode(ae)=%o", st.st_mode, archive_entry_mode(ae)); assertEqualInt(st.st_size, sizeof(data)); + free(fname); } #endif /* _WIN32 && !__CYGWIN__ */ DEFINE_TEST(test_write_disk) { struct archive_entry *ae; +#if defined(_WIN32) && !defined(__CYGWIN__) + wchar_t *fullpath; + DWORD l; +#endif /* Force the umask to something predictable. */ assertUmask(UMASK); @@ -293,7 +306,7 @@ DEFINE_TEST(test_write_disk) #if defined(_WIN32) && !defined(__CYGWIN__) /* A file with unusable characters in its file name. */ assert((ae = archive_entry_new()) != NULL); - archive_entry_copy_pathname(ae, "f:i*l?e\"fl|e"); + archive_entry_copy_pathname_w(ae, L"f:i*l?e\"fl|e"); archive_entry_set_mode(ae, S_IFREG | 0755); create_reg_file_win(ae, "Test creating a regular file" " with unusable characters in its file name"); @@ -301,10 +314,36 @@ DEFINE_TEST(test_write_disk) /* A file with unusable characters in its directory name. */ assert((ae = archive_entry_new()) != NULL); - archive_entry_copy_pathname(ae, "d:i*r?e\"co|ry/file1"); + archive_entry_copy_pathname_w(ae, L"d:i*r?e\"co|ry/file1"); + archive_entry_set_mode(ae, S_IFREG | 0755); + create_reg_file_win(ae, "Test creating a regular file" + " with unusable characters in its file name"); + archive_entry_free(ae); + + /* A full-path file with unusable characters in its file name. */ + assert((l = GetCurrentDirectoryW(0, NULL)) != 0); + assert((fullpath = malloc((l + 20) * sizeof(wchar_t))) != NULL); + assert((l = GetCurrentDirectoryW(l, fullpath)) != 0); + wcscat(fullpath, L"\\f:i*l?e\"fl|e"); + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname_w(ae, fullpath); + archive_entry_set_mode(ae, S_IFREG | 0755); + create_reg_file_win(ae, "Test creating a regular file" + " with unusable characters in its file name"); + archive_entry_free(ae); + free(fullpath); + + /* A full-path file with unusable characters in its directory name. */ + assert((l = GetCurrentDirectoryW(0, NULL)) != 0); + assert((fullpath = malloc((l + 30) * sizeof(wchar_t))) != NULL); + assert((l = GetCurrentDirectoryW(l, fullpath)) != 0); + wcscat(fullpath, L"\\d:i*r?e\"co|ry/file1"); + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname_w(ae, fullpath); archive_entry_set_mode(ae, S_IFREG | 0755); create_reg_file_win(ae, "Test creating a regular file" " with unusable characters in its file name"); archive_entry_free(ae); + free(fullpath); #endif /* _WIN32 && !__CYGWIN__ */ }