archive-zip: support UTF-8 paths
authorRené Scharfe <rene.scharfe@lsrfire.ath.cx>
Tue, 4 Sep 2012 20:23:38 +0000 (22:23 +0200)
committerJunio C Hamano <gitster@pobox.com>
Tue, 4 Sep 2012 21:06:18 +0000 (14:06 -0700)
Set general purpose flag 11 if we encounter a path that contains
non-ASCII characters.  We assume that all paths are given as UTF-8; no
conversion is done.

The flag seems to be ignored by unzip unless we also mark the archive
entry as coming from a Unix system.  This is done by setting the field
creator_version ("version made by" in the standard[1]) to 0x03NN.

The NN part represents the version of the standard supported by us, and
this patch sets it to 3f (for version 6.3) for Unix paths.  We keep
creator_version set to 0 (FAT filesystem, standard version 0) in the
non-special cases, as before.

But when we declare a file to have a Unix path, then we have to set the
file mode as well, or unzip will extract the files with the permission
set 0000, i.e. inaccessible by all.

[1] http://www.pkware.com/documents/casestudies/APPNOTE.TXT

Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
archive-zip.c

index f5af81f904df081002dad46a71be2eca8e3bebab..928da1d79108ba783ec2afba2b8c6f8d5653f19b 100644 (file)
@@ -4,6 +4,8 @@
 #include "cache.h"
 #include "archive.h"
 #include "streaming.h"
+#include "commit.h"
+#include "utf8.h"
 
 static int zip_date;
 static int zip_time;
@@ -16,7 +18,8 @@ static unsigned int zip_dir_offset;
 static unsigned int zip_dir_entries;
 
 #define ZIP_DIRECTORY_MIN_SIZE (1024 * 1024)
-#define ZIP_STREAM (8)
+#define ZIP_STREAM     (1 <<  3)
+#define ZIP_UTF8       (1 << 11)
 
 struct zip_local_header {
        unsigned char magic[4];
@@ -173,7 +176,8 @@ static int write_zip_entry(struct archiver_args *args,
 {
        struct zip_local_header header;
        struct zip_dir_header dirent;
-       unsigned long attr2;
+       unsigned int creator_version = 0;
+       unsigned long attr2 = 0;
        unsigned long compressed_size;
        unsigned long crc;
        unsigned long direntsize;
@@ -187,6 +191,13 @@ static int write_zip_entry(struct archiver_args *args,
 
        crc = crc32(0, NULL, 0);
 
+       if (has_non_ascii(path)) {
+               if (is_utf8(path))
+                       flags |= ZIP_UTF8;
+               else
+                       warning("Path is not valid UTF-8: %s", path);
+       }
+
        if (pathlen > 0xffff) {
                return error("path too long (%d chars, SHA1: %s): %s",
                                (int)pathlen, sha1_to_hex(sha1), path);
@@ -204,10 +215,15 @@ static int write_zip_entry(struct archiver_args *args,
                enum object_type type = sha1_object_info(sha1, &size);
 
                method = 0;
-               attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) :
-                       (mode & 0111) ? ((mode) << 16) : 0;
                if (S_ISREG(mode) && args->compression_level != 0 && size > 0)
                        method = 8;
+               if (S_ISLNK(mode) || (mode & 0111) || (flags & ZIP_UTF8)) {
+                       creator_version = 0x033f;
+                       attr2 = mode;
+                       if (S_ISLNK(mode))
+                               attr2 |= 0777;
+                       attr2 <<= 16;
+               }
                compressed_size = size;
 
                if (S_ISREG(mode) && type == OBJ_BLOB && !args->convert &&
@@ -254,8 +270,7 @@ static int write_zip_entry(struct archiver_args *args,
        }
 
        copy_le32(dirent.magic, 0x02014b50);
-       copy_le16(dirent.creator_version,
-               S_ISLNK(mode) || (S_ISREG(mode) && (mode & 0111)) ? 0x0317 : 0);
+       copy_le16(dirent.creator_version, creator_version);
        copy_le16(dirent.version, 10);
        copy_le16(dirent.flags, flags);
        copy_le16(dirent.compression_method, method);