zip-writer: Write size and CRC in local directory when possible.
authorBen Pfaff <blp@cs.stanford.edu>
Thu, 4 Sep 2014 04:03:06 +0000 (21:03 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Thu, 4 Sep 2014 04:03:41 +0000 (21:03 -0700)
Google Drive rejects ODT files as corrupted unless their size and CRC are
in local file headers, even though the zip file specification says that it
is OK for them to follow the file data.  Thus, this commit changes the
zip writer to satisfy Google unless the output file is not seekable.

src/libpspp/zip-writer.c

index e286a7eb1c5f281ddc739d88ab9c3a33e57c512b..fd36fc523a029b61e0dee5122ef330bbb8c4a4ed 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2010, 2012 Free Software Foundation, Inc.
+   Copyright (C) 2010, 2012, 2014 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -40,6 +40,8 @@ struct zip_writer
 
     uint16_t date, time;        /* Date and time in MS-DOS format. */
 
+    bool ok;
+
     /* Members already added to the file, so that we can summarize them to the
        central directory at the end of the ZIP file. */
     struct zip_member *members;
@@ -99,6 +101,8 @@ zip_writer_create (const char *file_name)
   zw->file_name = xstrdup (file_name);
   zw->file = file;
 
+  zw->ok = true;
+
   now = time (NULL);
   tm = localtime (&now);
   zw->date = tm->tm_mday + ((tm->tm_mon + 1) << 5) + ((tm->tm_year - 80) << 9);
@@ -111,6 +115,24 @@ zip_writer_create (const char *file_name)
   return zw;
 }
 
+static void
+put_local_header (struct zip_writer *zw, const char *member_name, uint32_t crc,
+                  uint32_t size, int flag)
+{
+  put_u32 (zw, MAGIC_LHDR);     /* local file header signature */
+  put_u16 (zw, 10);             /* version needed to extract */
+  put_u16 (zw, flag);           /* general purpose bit flag */
+  put_u16 (zw, 0);              /* compression method */
+  put_u16 (zw, zw->time);       /* last mod file time */
+  put_u16 (zw, zw->date);       /* last mod file date */
+  put_u32 (zw, crc);            /* crc-32 */
+  put_u32 (zw, size);           /* compressed size */
+  put_u32 (zw, size);           /* uncompressed size */
+  put_u16 (zw, strlen (member_name)); /* file name length */
+  put_u16 (zw, 0);                    /* extra field length */
+  put_bytes (zw, member_name, strlen (member_name));
+}
+
 /* Adds the contents of FILE, with name MEMBER_NAME, to ZW. */
 void
 zip_writer_add (struct zip_writer *zw, FILE *file, const char *member_name)
@@ -123,18 +145,7 @@ zip_writer_add (struct zip_writer *zw, FILE *file, const char *member_name)
 
   /* Local file header. */
   offset = ftello (zw->file);
-  put_u32 (zw, MAGIC_LHDR);     /* local file header signature */
-  put_u16 (zw, 10);             /* version needed to extract */
-  put_u16 (zw, 1 << 3);         /* general purpose bit flag */
-  put_u16 (zw, 0);              /* compression method */
-  put_u16 (zw, zw->time);       /* last mod file time */
-  put_u16 (zw, zw->date);       /* last mod file date */
-  put_u32 (zw, 0);              /* crc-32 */
-  put_u32 (zw, 0);              /* compressed size */
-  put_u32 (zw, 0);              /* uncompressed size */
-  put_u16 (zw, strlen (member_name)); /* file name length */
-  put_u16 (zw, 0);                    /* extra field length */
-  put_bytes (zw, member_name, strlen (member_name));
+  put_local_header (zw, member_name, 0, 0, 1 << 3);
 
   /* File data. */
   size = crc = 0;
@@ -146,11 +157,25 @@ zip_writer_add (struct zip_writer *zw, FILE *file, const char *member_name)
       crc = crc32_update (crc, buf, bytes_read);
     }
 
-  /* Data descriptor. */
-  put_u32 (zw, MAGIC_DDHD);
-  put_u32 (zw, crc);
-  put_u32 (zw, size);
-  put_u32 (zw, size);
+  /* Try to seek back to the local file header.  If successful, overwrite it
+     with the correct file size and CRC.  Otherwise, write data descriptor. */
+  if (fseeko (zw->file, offset, SEEK_SET) == 0)
+    {
+      put_local_header (zw, member_name, crc, size, 0);
+      if (fseeko (zw->file, size, SEEK_CUR)
+          && zw->ok)
+        {
+          msg_error (errno, _("%s: error seeking in output file"), zw->file_name);
+          zw->ok = false;
+        }
+    }
+  else
+    {
+      put_u32 (zw, MAGIC_DDHD);
+      put_u32 (zw, crc);
+      put_u32 (zw, size);
+      put_u32 (zw, size);
+    }
 
   /* Add to set of members. */
   if (zw->n_members >= zw->allocated_members)
@@ -220,9 +245,8 @@ zip_writer_close (struct zip_writer *zw)
                                    the starting disk number */
   put_u16 (zw, 0);              /* .ZIP file comment length */
 
-  if (!fwriteerror (zw->file))
-    ok = true;
-  else
+  ok = zw->ok;
+  if (ok && fwriteerror (zw->file))
     {
       msg_error (errno, _("%s: write failed"), zw->file_name);
       ok = false;