]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Fix issue 174.
authorMichihiro NAKAJIMA <ggcueroad@gmail.com>
Thu, 8 Sep 2011 06:18:18 +0000 (02:18 -0400)
committerMichihiro NAKAJIMA <ggcueroad@gmail.com>
Thu, 8 Sep 2011 06:18:18 +0000 (02:18 -0400)
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

libarchive/archive_write_disk_windows.c
libarchive/test/test_write_disk.c

index 863dcdc9ef7eb85bcd06a3be31c1df076c60c9d9..1dcf9d0ddbdc3f31dd40315d5b7e3176b51b773c 100644 (file)
@@ -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 '/././././/./'.
index 9a4475599d4f1e41dc726a80c398ee034b1ab180..60bcdc24edf5e9896217630d0314dc8e88a6d65b 100644 (file)
@@ -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\"f<i>l|e");
+       archive_entry_copy_pathname_w(ae, L"f:i*l?e\"f<i>l|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\"c<t>o|ry/file1");
+       archive_entry_copy_pathname_w(ae, L"d:i*r?e\"c<t>o|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\"f<i>l|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\"c<t>o|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__ */
 }