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
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'_';
} 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 '..'");
* 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 '/././././/./'.
{
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);
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);
#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");
/* 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__ */
}