Merge branch 'rr/submodule-diff-config'
[gitweb.git] / archive-zip.c
index f5af81f904df081002dad46a71be2eca8e3bebab..55f66b4060c64789bdb443f6c71f82a75ef22d0b 100644 (file)
@@ -4,6 +4,7 @@
 #include "cache.h"
 #include "archive.h"
 #include "streaming.h"
+#include "utf8.h"
 
 static int zip_date;
 static int zip_time;
@@ -16,7 +17,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];
@@ -74,6 +76,14 @@ struct zip_dir_trailer {
        unsigned char _end[1];
 };
 
+struct zip_extra_mtime {
+       unsigned char magic[2];
+       unsigned char extra_size[2];
+       unsigned char flags[1];
+       unsigned char mtime[4];
+       unsigned char _end[1];
+};
+
 /*
  * On ARM, padding is added at the end of the struct, so a simple
  * sizeof(struct ...) reports two bytes more than the payload size
@@ -83,6 +93,9 @@ struct zip_dir_trailer {
 #define ZIP_DATA_DESC_SIZE     offsetof(struct zip_data_desc, _end)
 #define ZIP_DIR_HEADER_SIZE    offsetof(struct zip_dir_header, _end)
 #define ZIP_DIR_TRAILER_SIZE   offsetof(struct zip_dir_trailer, _end)
+#define ZIP_EXTRA_MTIME_SIZE   offsetof(struct zip_extra_mtime, _end)
+#define ZIP_EXTRA_MTIME_PAYLOAD_SIZE \
+       (ZIP_EXTRA_MTIME_SIZE - offsetof(struct zip_extra_mtime, flags))
 
 static void copy_le16(unsigned char *dest, unsigned int n)
 {
@@ -164,6 +177,17 @@ static void set_zip_header_data_desc(struct zip_local_header *header,
        copy_le32(header->size, size);
 }
 
+static int has_only_ascii(const char *s)
+{
+       for (;;) {
+               int c = *s++;
+               if (c == '\0')
+                       return 1;
+               if (!isascii(c))
+                       return 0;
+       }
+}
+
 #define STREAM_BUFFER_SIZE (1024 * 16)
 
 static int write_zip_entry(struct archiver_args *args,
@@ -173,6 +197,7 @@ static int write_zip_entry(struct archiver_args *args,
 {
        struct zip_local_header header;
        struct zip_dir_header dirent;
+       struct zip_extra_mtime extra;
        unsigned long attr2;
        unsigned long compressed_size;
        unsigned long crc;
@@ -187,6 +212,13 @@ static int write_zip_entry(struct archiver_args *args,
 
        crc = crc32(0, NULL, 0);
 
+       if (!has_only_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);
@@ -246,8 +278,13 @@ static int write_zip_entry(struct archiver_args *args,
                }
        }
 
+       copy_le16(extra.magic, 0x5455);
+       copy_le16(extra.extra_size, ZIP_EXTRA_MTIME_PAYLOAD_SIZE);
+       extra.flags[0] = 1;     /* just mtime */
+       copy_le32(extra.mtime, args->time);
+
        /* make sure we have enough free space in the dictionary */
-       direntsize = ZIP_DIR_HEADER_SIZE + pathlen;
+       direntsize = ZIP_DIR_HEADER_SIZE + pathlen + ZIP_EXTRA_MTIME_SIZE;
        while (zip_dir_size < zip_dir_offset + direntsize) {
                zip_dir_size += ZIP_DIRECTORY_MIN_SIZE;
                zip_dir = xrealloc(zip_dir, zip_dir_size);
@@ -263,7 +300,7 @@ static int write_zip_entry(struct archiver_args *args,
        copy_le16(dirent.mdate, zip_date);
        set_zip_dir_data_desc(&dirent, size, compressed_size, crc);
        copy_le16(dirent.filename_length, pathlen);
-       copy_le16(dirent.extra_length, 0);
+       copy_le16(dirent.extra_length, ZIP_EXTRA_MTIME_SIZE);
        copy_le16(dirent.comment_length, 0);
        copy_le16(dirent.disk, 0);
        copy_le16(dirent.attr1, 0);
@@ -281,11 +318,13 @@ static int write_zip_entry(struct archiver_args *args,
        else
                set_zip_header_data_desc(&header, size, compressed_size, crc);
        copy_le16(header.filename_length, pathlen);
-       copy_le16(header.extra_length, 0);
+       copy_le16(header.extra_length, ZIP_EXTRA_MTIME_SIZE);
        write_or_die(1, &header, ZIP_LOCAL_HEADER_SIZE);
        zip_offset += ZIP_LOCAL_HEADER_SIZE;
        write_or_die(1, path, pathlen);
        zip_offset += pathlen;
+       write_or_die(1, &extra, ZIP_EXTRA_MTIME_SIZE);
+       zip_offset += ZIP_EXTRA_MTIME_SIZE;
        if (stream && method == 0) {
                unsigned char buf[STREAM_BUFFER_SIZE];
                ssize_t readlen;
@@ -382,6 +421,8 @@ static int write_zip_entry(struct archiver_args *args,
        zip_dir_offset += ZIP_DIR_HEADER_SIZE;
        memcpy(zip_dir + zip_dir_offset, path, pathlen);
        zip_dir_offset += pathlen;
+       memcpy(zip_dir + zip_dir_offset, &extra, ZIP_EXTRA_MTIME_SIZE);
+       zip_dir_offset += ZIP_EXTRA_MTIME_SIZE;
        zip_dir_entries++;
 
        return 0;