str: New function ds_put_substring_multiple().
[pspp] / src / libpspp / zip-writer.c
index 7e1a6ccc178f5b3e3956c0d3d1e5b1c8f469248b..69244cc84e4b9b9aca8d4cb907196ad131e004b0 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2010 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
 #include <config.h>
 
 #include "libpspp/zip-writer.h"
+#include "libpspp/zip-private.h"
 
+#include <assert.h>
+#include <byteswap.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <time.h>
-
-#include "libpspp/integer-format.h"
+#include <unistd.h>
 
 #include "gl/crc.h"
-#include "gl/error.h"
 #include "gl/fwriteerror.h"
 #include "gl/xalloc.h"
 
+#include "libpspp/message.h"
+#include "libpspp/temp-file.h"
+
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 
@@ -36,9 +40,18 @@ struct zip_writer
   {
     char *file_name;            /* File name, for use in error mesages. */
     FILE *file;                 /* Output stream. */
+    uint32_t offset;            /* Offset in output stream. */
 
     uint16_t date, time;        /* Date and time in MS-DOS format. */
 
+    bool ok;
+
+    /* Member being added to the file. */
+    char *m_name;
+    uint32_t m_start;
+    uint32_t m_size;
+    uint32_t m_crc;
+
     /* 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;
@@ -57,21 +70,24 @@ static void
 put_bytes (struct zip_writer *zw, const void *p, size_t n)
 {
   fwrite (p, 1, n, zw->file);
+  zw->offset += n;
 }
 
 static void
 put_u16 (struct zip_writer *zw, uint16_t x)
 {
-  if (INTEGER_NATIVE != INTEGER_LSB_FIRST)
-    integer_convert (INTEGER_NATIVE, &x, INTEGER_MSB_FIRST, &x, sizeof x);
+#ifdef WORDS_BIGENDIAN
+  x = bswap_16 (x);
+#endif
   put_bytes (zw, &x, sizeof x);
 }
 
 static void
 put_u32 (struct zip_writer *zw, uint32_t x)
 {
-  if (INTEGER_NATIVE != INTEGER_LSB_FIRST)
-    integer_convert (INTEGER_NATIVE, &x, INTEGER_MSB_FIRST, &x, sizeof x);
+#ifdef WORDS_BIGENDIAN
+  x = bswap_32 (x);
+#endif
   put_bytes (zw, &x, sizeof x);
 }
 
@@ -80,84 +96,163 @@ put_u32 (struct zip_writer *zw, uint32_t x)
 struct zip_writer *
 zip_writer_create (const char *file_name)
 {
-  struct zip_writer *zw;
-  struct tm *tm;
-  time_t now;
   FILE *file;
-
-  file = fopen (file_name, "wb");
-  if (file == NULL)
+  if (strcmp (file_name, "-"))
     {
-      error (0, errno, _("%s: error opening output file"), file_name);
-      return NULL;
+      file = fopen (file_name, "wb");
+      if (file == NULL)
+        {
+          msg_error (errno, _("%s: error opening output file"), file_name);
+          return NULL;
+        }
     }
+  else
+    {
+      if (isatty (STDOUT_FILENO))
+        {
+          msg (ME, _("%s: not writing ZIP file to terminal"), file_name);
+          return NULL;
+        }
 
-  zw = xmalloc (sizeof *zw);
-  zw->file_name = xstrdup (file_name);
-  zw->file = file;
-
-  now = time (NULL);
-  tm = localtime (&now);
-  zw->date = tm->tm_mday + ((tm->tm_mon + 1) << 5) + ((tm->tm_year - 80) << 9);
-  zw->time = tm->tm_sec / 2 + (tm->tm_min << 5) + (tm->tm_hour << 11);
+      file = stdout;
+    }
 
-  zw->members = NULL;
-  zw->n_members = 0;
-  zw->allocated_members = 0;
+  time_t now = time (NULL);
+  struct tm *tm = localtime (&now);
 
+  struct zip_writer *zw = xmalloc (sizeof *zw);
+  *zw = (struct zip_writer) {
+    .file_name = xstrdup (file_name),
+    .file = file,
+    .ok = true,
+    .date = tm->tm_mday + ((tm->tm_mon + 1) << 5) + ((tm->tm_year - 80) << 9),
+    .time = tm->tm_sec / 2 + (tm->tm_min << 5) + (tm->tm_hour << 11),
+  };
   return zw;
 }
 
-/* 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)
+static void
+put_local_header (struct zip_writer *zw, const char *member_name, uint32_t crc,
+                  uint32_t size, int flag)
 {
-  struct zip_member *member;
-  uint32_t offset, size;
-  size_t bytes_read;
-  uint32_t crc;
-  char buf[4096];
-
-  /* Local file header. */
-  offset = ftello (zw->file);
-  put_u32 (zw, 0x04034b50);     /* local file header signature */
+  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, 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, 0);              /* crc-32 */
-  put_u32 (zw, 0);              /* compressed size */
-  put_u32 (zw, 0);              /* uncompressed size */
+  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));
+}
 
-  /* File data. */
-  size = crc = 0;
-  fseeko (file, 0, SEEK_SET);
-  while ((bytes_read = fread (buf, 1, sizeof buf, file)) > 0)
+/* Start adding a new member, named MEMBER_NAME, to ZW.  Add content to the
+   member by calling zip_writer_add_write() possibly multiple times, then
+   finish it off with zip_writer_add_finish().
+
+   Only one member may be open at a time. */
+void
+zip_writer_add_start (struct zip_writer *zw, const char *member_name)
+{
+  assert (!zw->m_name);
+  zw->m_name = xstrdup (member_name);
+  zw->m_start = zw->offset;
+  zw->m_size = 0;
+  zw->m_crc = 0;
+  put_local_header (zw, member_name, 0, 0, 1 << 3);
+}
+
+/* Adds the N bytes in BUF to the currently open member in ZW. */
+void
+zip_writer_add_write (struct zip_writer *zw, const void *buf, size_t n)
+{
+  assert (zw->m_name);
+  put_bytes (zw, buf, n);
+  zw->m_size += n;
+  zw->m_crc = crc32_update (zw->m_crc, buf, n);
+}
+
+/* Finishes writing the currently open member in ZW. */
+void
+zip_writer_add_finish (struct zip_writer *zw)
+{
+  assert (zw->m_name);
+
+  /* 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, zw->m_start, SEEK_SET) == 0)
     {
-      put_bytes (zw, buf, bytes_read);
-      size += bytes_read;
-      crc = crc32_update (crc, buf, bytes_read);
+      uint32_t save_offset = zw->offset;
+      put_local_header (zw, zw->m_name, zw->m_crc, zw->m_size, 0);
+      if (fseeko (zw->file, zw->m_size, SEEK_CUR)
+          && zw->ok)
+        {
+          msg_error (errno, _("%s: error seeking in output file"), zw->file_name);
+          zw->ok = false;
+        }
+      zw->offset = save_offset;
+    }
+  else
+    {
+      put_u32 (zw, MAGIC_DDHD);
+      put_u32 (zw, zw->m_crc);
+      put_u32 (zw, zw->m_size);
+      put_u32 (zw, zw->m_size);
     }
-
-  /* Data descriptor. */
-  put_u32 (zw, 0x08074b50);
-  put_u32 (zw, crc);
-  put_u32 (zw, size);
-  put_u32 (zw, size);
 
   /* Add to set of members. */
   if (zw->n_members >= zw->allocated_members)
     zw->members = x2nrealloc (zw->members, &zw->allocated_members,
                               sizeof *zw->members);
-  member = &zw->members[zw->n_members++];
-  member->offset = offset;
-  member->size = size;
-  member->crc = crc;
-  member->name = xstrdup (member_name);
+  struct zip_member *member = &zw->members[zw->n_members++];
+  member->offset = zw->m_start;
+  member->size = zw->m_size;
+  member->crc = zw->m_crc;
+  member->name = zw->m_name;
+
+  zw->m_name = NULL;
+  zw->m_start = zw->m_size = zw->m_crc = 0;
+}
+
+/* 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)
+{
+  zip_writer_add_start (zw, member_name);
+
+  fseeko (file, 0, SEEK_SET);
+  for (;;)
+    {
+      char buf[4096];
+      size_t n = fread (buf, 1, sizeof buf, file);
+      if (!n)
+        break;
+      zip_writer_add_write (zw, buf, n);
+    }
+  zip_writer_add_finish (zw);
+}
+
+/* Adds a member named MEMBER_NAME whose contents is the null-terminated string
+   CONTENT. */
+void
+zip_writer_add_string (struct zip_writer *zw, const char *member_name,
+                       const char *content)
+{
+  zip_writer_add_memory (zw, member_name, content, strlen (content));
+}
+
+/* Adds a member named MEMBER_NAME whose contents is the SIZE bytes of
+   CONTENT. */
+void
+zip_writer_add_memory (struct zip_writer *zw, const char *member_name,
+                       const void *content, size_t size)
+{
+  zip_writer_add_start (zw, member_name);
+  zip_writer_add_write (zw, content, size);
+  zip_writer_add_finish (zw);
 }
 
 /* Finalizes the contents of ZW and closes it.  Returns true if successful,
@@ -173,13 +268,15 @@ zip_writer_close (struct zip_writer *zw)
   if (zw == NULL)
     return true;
 
-  dir_start = ftello (zw->file);
+  assert (!zw->m_name);
+
+  dir_start = zw->offset;
   for (i = 0; i < zw->n_members; i++)
     {
       struct zip_member *m = &zw->members[i];
 
       /* Central directory file header. */
-      put_u32 (zw, 0x02014b50);       /* central file header signature */
+      put_u32 (zw, MAGIC_SOCD);       /* central file header signature */
       put_u16 (zw, 63);               /* version made by */
       put_u16 (zw, 10);               /* version needed to extract */
       put_u16 (zw, 1 << 3);           /* general purpose bit flag */
@@ -200,10 +297,10 @@ zip_writer_close (struct zip_writer *zw)
       free (m->name);
     }
   free (zw->members);
-  dir_end = ftello (zw->file);
+  dir_end = zw->offset;
 
   /* End of central directory record. */
-  put_u32 (zw, 0x06054b50);     /* end of central dir signature */
+  put_u32 (zw, MAGIC_EOCD);     /* end of central dir signature */
   put_u16 (zw, 0);              /* number of this disk */
   put_u16 (zw, 0);              /* number of the disk with the
                                    start of the central directory */
@@ -217,11 +314,10 @@ 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 && zw->file != stdout && fwriteerror (zw->file))
     {
-      error (0, errno, _("%s: write failed"), zw->file_name);
+      msg_error (errno, _("%s: write failed"), zw->file_name);
       ok = false;
     }