Implemented a zip-writer to correspond to zip-reader
authorJohn Darrington <john@darrington.wattle.id.au>
Fri, 1 Jul 2011 15:22:02 +0000 (17:22 +0200)
committerJohn Darrington <john@darrington.wattle.id.au>
Fri, 1 Jul 2011 15:22:02 +0000 (17:22 +0200)
src/libpspp/automake.mk
src/libpspp/inflate.c [new file with mode: 0644]
src/libpspp/inflate.h [new file with mode: 0644]
src/libpspp/zip-private.h [new file with mode: 0644]
src/libpspp/zip-reader.c [new file with mode: 0644]
src/libpspp/zip-reader.h [new file with mode: 0644]
src/libpspp/zip-writer.c
tests/automake.mk
tests/libpspp/zip-test.c [new file with mode: 0644]
tests/libpspp/zip.at [new file with mode: 0644]

index 8845525f92f1df1eefd8b8f5d47f1d5916210515..805887410b13a31b9e185ab9c7a51c6e5080a080 100644 (file)
@@ -30,6 +30,8 @@ src_libpspp_libpspp_la_SOURCES = \
        src/libpspp/freaderror.h \
        src/libpspp/hash-functions.c \
        src/libpspp/hash-functions.h \
+       src/libpspp/inflate.c \
+       src/libpspp/inflate.h \
        src/libpspp/heap.c \
        src/libpspp/heap.h \
        src/libpspp/hmap.c \
@@ -87,6 +89,9 @@ src_libpspp_libpspp_la_SOURCES = \
        src/libpspp/u8-istream.c \
        src/libpspp/u8-istream.h \
        src/libpspp/version.h \
+       src/libpspp/zip-private.h \
+       src/libpspp/zip-reader.c \
+       src/libpspp/zip-reader.h \
        src/libpspp/zip-writer.c \
        src/libpspp/zip-writer.h
 
diff --git a/src/libpspp/inflate.c b/src/libpspp/inflate.c
new file mode 100644 (file)
index 0000000..4787478
--- /dev/null
@@ -0,0 +1,148 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2011 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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+
+#include <config.h>
+
+#include "inflate.h"
+
+#if HAVE_ZLIB_H
+
+#include <xalloc.h>
+#include <zlib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "zip-reader.h"
+
+#include "str.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) (msgid)
+
+#define UCOMPSIZE 4096
+
+struct inflator
+{
+  z_stream zss;
+  int state;
+  unsigned char ucomp[UCOMPSIZE];
+  size_t bytes_uncomp;
+  size_t ucomp_bytes_read;
+
+  /* Two bitfields as defined by RFC1950 */
+  uint16_t cmf_flg ;
+};
+
+void
+inflate_finish (struct zip_member *zm)
+{
+  struct inflator *inf = zm->aux;
+
+  inflateEnd (&inf->zss);
+
+  free (inf);
+}
+
+bool
+inflate_init (struct zip_member *zm)
+{
+  int r;
+  struct inflator *inf = xzalloc (sizeof *inf);
+  
+  uint16_t flg = 0 ; 
+  uint16_t cmf = 0x8; /* Always 8 for inflate */
+
+  const uint16_t cinfo = 7;  /* log_2(Window size) - 8 */
+
+  cmf |= cinfo << 4;     /* Put cinfo into the high nibble */
+
+  /* make these into a 16 bit word */
+  inf->cmf_flg = (cmf << 8 ) | flg;
+
+  /* Set the check bits */
+  inf->cmf_flg += 31 - (inf->cmf_flg % 31);
+  assert (inf->cmf_flg % 31 == 0);
+
+  inf->zss.next_in = Z_NULL;
+  inf->zss.avail_in = 0;
+  inf->zss.zalloc = Z_NULL;
+  inf->zss.zfree  = Z_NULL;
+  inf->zss.opaque = Z_NULL;
+  r = inflateInit (&inf->zss);
+
+  if ( Z_OK != r)
+    {
+      ds_put_format (zm->errs, _("Cannot initialize inflator: %s"), zError (r));
+      return false;
+    }
+
+  zm->aux = inf;
+
+  return true;
+}
+
+
+int
+inflate_read (struct zip_member *zm, void *buf, size_t n)
+{
+  struct inflator *inf = zm->aux;
+
+  if (inf->zss.avail_in == 0)
+    {
+      int bytes_read;
+      int bytes_to_read;
+      int pad = 0;
+
+      if ( inf->state == 0)
+       {
+         inf->ucomp[1] = inf->cmf_flg ;
+         inf->ucomp[0] = inf->cmf_flg >> 8 ;
+
+         pad = 2;
+         inf->state++;
+       }
+
+      bytes_to_read = zm->comp_size - inf->ucomp_bytes_read;
+      
+      if (bytes_to_read == 0)
+       return 0;
+
+      if (bytes_to_read > UCOMPSIZE)
+       bytes_to_read = UCOMPSIZE;
+
+      bytes_read = fread (inf->ucomp + pad, 1, bytes_to_read - pad, zm->fp);
+
+      inf->ucomp_bytes_read += bytes_read;
+
+      inf->zss.avail_in = bytes_read + pad;
+      inf->zss.next_in = inf->ucomp;
+    }
+  inf->zss.avail_out = n;
+  inf->zss.next_out = buf;
+
+  int r = inflate (&inf->zss, Z_NO_FLUSH);
+  if ( Z_OK == r)
+    {
+      return n - inf->zss.avail_out;
+    }
+  
+  ds_put_format (zm->errs, _("Error inflating: %s"), zError (r));
+
+  return -1;
+}
+
+#endif
diff --git a/src/libpspp/inflate.h b/src/libpspp/inflate.h
new file mode 100644 (file)
index 0000000..c487687
--- /dev/null
@@ -0,0 +1,32 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2011 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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef INFLATE_H
+#define INFLATE_H 1
+
+#include <stddef.h>
+#include <stdbool.h>
+
+struct zip_member ;
+
+bool inflate_init (struct zip_member *zm);
+
+int inflate_read (struct zip_member *zm, void *buf, size_t n);
+
+void inflate_finish (struct zip_member *zm);
+
+
+#endif
diff --git a/src/libpspp/zip-private.h b/src/libpspp/zip-private.h
new file mode 100644 (file)
index 0000000..5c956dd
--- /dev/null
@@ -0,0 +1,26 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2011 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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef ZIP_PRIVATE_H
+#define ZIP_PRIVATE_H 1
+
+#define MAGIC_EOCD ( (uint32_t) 0x06054b50) /* End of directory */
+#define MAGIC_SOCD ( (uint32_t) 0x02014b50) /* Start of directory */
+#define MAGIC_LHDR ( (uint32_t) 0x04034b50) /* Local Header */
+#define MAGIC_DDHD ( (uint32_t) 0x08074b50) /* Data Descriptor Header */
+
+#endif
+
diff --git a/src/libpspp/zip-reader.c b/src/libpspp/zip-reader.c
new file mode 100644 (file)
index 0000000..bee7e2f
--- /dev/null
@@ -0,0 +1,559 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2011 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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <errno.h>
+#include <xalloc.h>
+#include <libpspp/assertion.h>
+
+#include <crc.h>
+
+#include "inflate.h"
+
+#include "str.h"
+
+#include "zip-reader.h"
+#include "zip-private.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) (msgid)
+
+
+static bool find_eocd (FILE *fp, off_t *off);
+
+static int
+stored_read (struct zip_member *zm, void *buf, size_t n)
+{
+  return fread (buf, 1, n, zm->fp);
+}
+
+static bool
+stored_init (struct zip_member *zm UNUSED)
+{
+  return true;
+}
+
+static void
+stored_finish (struct zip_member *zm UNUSED)
+{
+  /* Nothing required */
+}
+
+
+static struct decompressor decompressors[n_COMPRESSION] = 
+  {
+    {stored_init, stored_read, stored_finish},
+#if HAVE_ZLIB_H
+    {inflate_init, inflate_read, inflate_finish}
+#endif
+  };
+
+static enum compression
+comp_code (struct zip_member *zm, uint16_t c)
+{
+  enum compression which;
+  switch (c)
+    {
+    case 0:
+      which = COMPRESSION_STORED;
+      break;
+#if HAVE_ZLIB_H
+    case 8:
+      which = COMPRESSION_INFLATE;
+      break;
+#endif
+    default:
+      ds_put_format (zm->errs, _("Unsupported compression type (%d)"), c);
+      which = n_COMPRESSION;
+      break;
+    }
+  return which;
+}
+
+
+struct zip_reader
+{
+  char *filename;                  /* The name of the file from which the data is read */
+  FILE *fr;                        /* The stream from which the meta data is read */
+  uint16_t n_members;              /* The number of members in this archive */
+  struct zip_member **members;     /* The members (may be null pointers until the headers have been read */
+  int nm;
+  struct string *errs;
+};
+
+void
+zip_member_finish (struct zip_member *zm)
+{
+  ds_clear (zm->errs);
+  /*  Probably not useful, because we would have to read right to the end of the member
+  if (zm->expected_crc != zm->crc)
+    {
+      ds_put_cstr (zm->errs, _("CRC error reading zip"));
+    }
+  */
+  zip_member_unref (zm);
+}
+
+
+
+/* Destroy the zip reader */
+void
+zip_reader_destroy (struct zip_reader *zr)
+{
+  int i;
+  if (zr == NULL) 
+    return;
+
+  fclose (zr->fr);
+  free (zr->filename);
+
+  for (i = 0; i < zr->n_members; ++i)
+    {
+      zip_member_unref (zr->members[i]);
+    }
+  free (zr->members);
+  free (zr);
+}
+
+
+void
+zm_dump (const struct zip_member *zm)
+{
+  printf ("%d\t%08x\t %s\n", zm->ucomp_size, zm->expected_crc, zm->name);
+}
+
+
+/* Skip N bytes in F */
+static void
+skip_bytes (FILE *f, size_t n)
+{
+  fseeko (f, n, SEEK_CUR);
+}
+
+/* Read N bytes from F, storing the result in X */
+static void
+get_bytes (FILE *f, void *x, size_t n)
+{
+  fread (x, 1, n, f);
+}
+
+/* Read a 32 bit value from F */
+static void
+get_u32 (FILE *f, uint32_t *x)
+{
+  get_bytes (f, x, sizeof *x);
+}
+
+
+/* Read 32 bit integer and compare it with EXPECTED.
+   place an error string in ERR if necessary. */
+static bool
+check_magic (FILE *f, uint32_t expected, struct string *err)
+{
+  uint32_t magic;
+
+  get_u32 (f, &magic);
+
+  if ((expected != magic))
+    {
+      ds_put_format (err,
+                    _("Corrupt file at 0x%llx: Expected %"PRIx32"; got %"PRIx32), 
+                    (long long int) ftello (f) - sizeof (uint32_t), expected, magic);
+
+      return false;
+    }
+  return true;
+}
+
+
+/* Read a 16 bit value from F */
+static void
+get_u16 (FILE *f, uint16_t *x)
+{
+  get_bytes (f, x, sizeof *x);
+}
+
+/* Reads upto BYTES bytes from ZM and puts them in BUF.
+   Returns the number of bytes read, or -1 on error */
+int
+zip_member_read (struct zip_member *zm, void *buf, size_t bytes)
+{
+  int bytes_read = 0;
+
+  ds_clear (zm->errs);
+
+  if ( bytes > zm->bytes_unread)
+    bytes = zm->bytes_unread;
+
+  bytes_read  = decompressors[zm->compression].read (zm, buf, bytes);
+  if ( bytes_read < 0)
+    return bytes_read;
+
+  zm->crc = crc32_update (zm->crc, buf, bytes_read);
+
+  zm->bytes_unread -= bytes_read;
+
+  return bytes_read;
+}
+
+
+/*
+  Read a local file header from ZR and add it to ZR's internal array.
+  Returns a pointer to the member read.  This pointer belongs to ZR.
+  If the caller wishes to control it, she should ref it with 
+  zip_member_ref.
+*/
+static struct zip_member *
+zip_header_read_next (struct zip_reader *zr)
+{
+  struct zip_member *zm = xzalloc (sizeof *zm);
+
+  uint16_t v, nlen, extralen;
+  uint16_t gp, time, date;
+  
+  uint16_t clen, diskstart, iattr;
+  uint32_t eattr;
+  uint16_t comp_type;
+
+  ds_clear (zr->errs);
+
+  if ( ! check_magic (zr->fr, MAGIC_SOCD, zr->errs))
+    return NULL;
+
+  get_u16 (zr->fr, &v);
+
+  get_u16 (zr->fr, &v);
+  get_u16 (zr->fr, &gp);
+  get_u16 (zr->fr, &comp_type);
+
+  zm->compression = comp_code (zm, comp_type);
+
+  get_u16 (zr->fr, &time);
+  get_u16 (zr->fr, &date);
+  get_u32 (zr->fr, &zm->expected_crc);
+  get_u32 (zr->fr, &zm->comp_size);
+  get_u32 (zr->fr, &zm->ucomp_size);
+  get_u16 (zr->fr, &nlen);
+  get_u16 (zr->fr, &extralen);
+  get_u16 (zr->fr, &clen);
+  get_u16 (zr->fr, &diskstart);
+  get_u16 (zr->fr, &iattr);
+  get_u32 (zr->fr, &eattr);
+  get_u32 (zr->fr, &zm->offset);
+
+  zm->name = calloc (nlen + 1, 1);
+  get_bytes (zr->fr, zm->name, nlen);
+
+  skip_bytes (zr->fr, extralen);
+  
+  zr->members[zr->nm++] = zm;
+
+  zm->fp = fopen (zr->filename, "r");
+  zm->ref_cnt = 1;
+  zm->errs = zr->errs;
+
+  return zm;
+}
+
+
+/* Create a reader from the zip called FILENAME */
+struct zip_reader *
+zip_reader_create (const char *filename, struct string *errs)
+{
+  uint16_t disknum, total_members;
+  off_t offset = 0;
+  uint32_t central_dir_start, central_dir_length;
+
+  struct zip_reader *zr = malloc (sizeof *zr);
+  zr->errs = errs;
+  if ( zr->errs)
+    ds_init_empty (zr->errs);
+
+  zr->nm = 0;
+
+  zr->fr = fopen (filename, "r");
+  if (NULL == zr->fr)
+    {
+      ds_put_cstr (zr->errs, strerror (errno));
+      free (zr);
+      return NULL;
+    }
+
+  if ( ! check_magic (zr->fr, MAGIC_LHDR, zr->errs))
+    {
+      fclose (zr->fr);
+      free (zr);
+      return NULL;
+    }
+
+  if ( ! find_eocd (zr->fr, &offset))
+    {
+      ds_put_format (zr->errs, _("Cannot find central directory"));
+      fclose (zr->fr);
+      free (zr);
+      return NULL;
+    }
+
+  if ( 0 != fseeko (zr->fr, offset, SEEK_SET))
+    {
+      const char *mm = strerror (errno);
+      ds_put_format (zr->errs, _("Failed to seek to end of central directory record: %s"), mm);
+      fclose (zr->fr);
+      free (zr);
+      return NULL;
+    }
+
+
+  if ( ! check_magic (zr->fr, MAGIC_EOCD, zr->errs))
+    {
+      fclose (zr->fr);
+      free (zr);
+      return NULL;
+    }
+  
+  get_u16 (zr->fr, &disknum);
+  get_u16 (zr->fr, &disknum);
+
+  get_u16 (zr->fr, &zr->n_members);
+  get_u16 (zr->fr, &total_members);
+
+  get_u32 (zr->fr, &central_dir_length);
+  get_u32 (zr->fr, &central_dir_start);
+
+  if ( 0 != fseeko (zr->fr, central_dir_start, SEEK_SET))
+    {
+      const char *mm = strerror (errno);
+      ds_put_format (zr->errs, _("Failed to seek to central directory: %s"), mm);
+      fclose (zr->fr);
+      free (zr);
+      return NULL;
+    }
+
+  zr->members = calloc (zr->n_members, sizeof (*zr->members));
+
+  zr->filename = strdup (filename);
+
+  return zr;
+}
+
+
+
+/* Return the member called MEMBER from the reader ZR  */
+struct zip_member *
+zip_member_open (struct zip_reader *zr, const char *member)
+{
+  uint16_t v, nlen, extra_len;
+  uint16_t gp, comp_type, time, date;
+  uint32_t ucomp_size, comp_size;
+  
+  uint32_t crc;
+
+  char *name = NULL;
+
+  int i;
+  struct zip_member *zm = NULL;
+
+  if ( zr == NULL)
+    return NULL;
+
+  for (i = 0 ; i < zr->n_members; ++i)
+  {
+    zm = zr->members[i] = zip_header_read_next (zr);
+    if (zm && 0 == strcmp (zm->name, member))
+      {
+       break;
+      }
+    else
+      {
+       zm = NULL;
+      }
+  }
+  
+  if ( zm == NULL)
+    return NULL;
+
+  if ( 0 != fseeko (zm->fp, zm->offset, SEEK_SET))
+    {
+      const char *mm = strerror (errno);
+      ds_put_format (zm->errs, _("Failed to seek to start of member `%s': %s"), zm->name, mm);
+      return NULL;
+    }
+
+  if ( ! check_magic (zm->fp, MAGIC_LHDR, zr->errs))
+    {
+      return NULL;
+    }
+
+  get_u16 (zm->fp, &v);
+  get_u16 (zm->fp, &gp);
+  get_u16 (zm->fp, &comp_type);
+  zm->compression = comp_code (zm, comp_type);
+  get_u16 (zm->fp, &time);
+  get_u16 (zm->fp, &date);
+  get_u32 (zm->fp, &crc);
+  get_u32 (zm->fp, &comp_size);
+
+  get_u32 (zm->fp, &ucomp_size);
+  get_u16 (zm->fp, &nlen);
+  get_u16 (zm->fp, &extra_len);
+
+  name = calloc (nlen + 1, sizeof (char));
+
+  get_bytes (zm->fp, name, nlen);
+
+  skip_bytes (zm->fp, extra_len);
+
+  if (strcmp (name, zm->name) != 0)
+    {
+      ds_put_format (zm->errs,
+                    _("Name mismatch in zip archive. Central directory says `%s'; local file header says `%s'"),
+                    zm->name, name);
+      free (name);
+      free (zm);
+      return NULL;
+    }
+
+  free (name);
+
+  zm->bytes_unread = zm->ucomp_size;
+
+  if ( !  decompressors[zm->compression].init (zm) )
+    return NULL;
+
+  return zm;
+}
+
+void
+zip_member_ref (struct zip_member *zm)
+{
+  zm->ref_cnt++;
+}
+
+
+
+
+void
+zip_member_unref (struct zip_member *zm)
+{
+  if ( zm == NULL)
+    return;
+
+  if (--zm->ref_cnt == 0)
+    {
+      decompressors[zm->compression].finish (zm);
+      if (zm->fp)
+       fclose (zm->fp);
+      free (zm->name);
+      free (zm);
+    }
+}
+
+
+\f
+
+static bool probe_magic (FILE *fp, uint32_t magic, off_t start, off_t stop, off_t *off);
+
+
+/* Search for something that looks like the End Of Central Directory in FP.
+   If found, the offset of the record will be placed in OFF.
+   Returns true if found false otherwise.
+*/
+static bool
+find_eocd (FILE *fp, off_t *off)
+{
+  off_t start, stop;
+  const uint32_t magic = MAGIC_EOCD;
+  bool found = false;
+
+  /* The magic cannot be more than 22 bytes from the end of the file, 
+     because that is the minimum length of the EndOfCentralDirectory
+     record.
+   */
+  if ( 0 > fseeko (fp, -22, SEEK_END))
+    {
+      return false;
+    }
+  start = ftello (fp);
+  stop = start + sizeof (magic);
+  do 
+    {
+      found = probe_magic (fp, magic, start, stop, off);
+      /* FIXME: For extra confidence lookup the directory start record here*/
+      if ( start == 0)
+       break;
+      stop = start + sizeof (magic);
+      start >>= 1;
+    }
+  while (!found );
+
+  return found;
+}
+
+
+/*
+  Search FP for MAGIC starting at START and reaching until STOP.
+  Returns true iff MAGIC is found.  False otherwise.
+  OFF receives the location of the magic.
+*/
+static bool
+probe_magic (FILE *fp, uint32_t magic, off_t start, off_t stop, off_t *off)
+{
+  int i;
+  int state = 0;
+  unsigned char seq[4];
+  unsigned char byte;
+
+  if ( 0 > fseeko (fp, start, SEEK_SET))
+    {
+      return -1;
+    }
+
+  for (i = 0; i < 4 ; ++i)
+    {
+      seq[i] = (magic >> i * 8) & 0xFF;
+    }
+
+  do
+    {
+      fread (&byte, 1, 1, fp);
+
+      if ( byte == seq[state])
+       state++;
+      else
+       state = 0;
+      
+      if ( state == 4)
+       {
+         *off = ftello (fp) - 4;
+         return true;
+       }
+      start++;
+      if ( start >= stop)
+       break;
+    }
+  while (!feof (fp));
+
+  return false;
+}
+
diff --git a/src/libpspp/zip-reader.h b/src/libpspp/zip-reader.h
new file mode 100644 (file)
index 0000000..966bd59
--- /dev/null
@@ -0,0 +1,87 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2011 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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+
+#ifndef ZIP_READER_H
+#define ZIP_READER_H 1
+
+#include <inttypes.h>
+
+struct zip_reader;
+struct string;
+
+enum compression
+  {
+    COMPRESSION_STORED = 0,
+#if HAVE_ZLIB_H
+    COMPRESSION_INFLATE,
+#endif
+    n_COMPRESSION
+  };
+
+struct zip_member
+{
+  FILE *fp;                   /* The stream from which the data is read */
+  uint32_t offset;            /* Starting offset in file. */
+  uint32_t comp_size;         /* Length of member file data, in bytes. */
+  uint32_t ucomp_size;        /* Uncompressed length of member file data, in bytes. */
+  uint32_t expected_crc;      /* CRC-32 of member file data.. */
+  char *name;                 /* Name of member file. */
+  uint32_t crc;
+  enum compression compression;
+
+  size_t bytes_unread;       /* Number of bytes left in the member available for reading */
+  int ref_cnt;
+  struct string *errs;
+  void *aux;
+};
+
+struct decompressor
+{
+  bool (*init) (struct zip_member *);
+  int  (*read) (struct zip_member *, void *, size_t);
+  void (*finish) (struct zip_member *);
+};
+
+
+void zm_dump (const struct zip_member *zm);
+
+/* Create zip reader to read the file called FILENAME.
+   If ERRS is non-null if will be used to contain any error messages
+   which the reader wishes to report.
+ */
+struct zip_reader *zip_reader_create (const char *filename, struct string *errs);
+
+/* Destroy the zip reader */
+void zip_reader_destroy (struct zip_reader *zr);
+
+/* Return the zip member in the reader ZR, called MEMBER */
+struct zip_member *zip_member_open (struct zip_reader *zr, const char *member);
+
+/* Read upto N bytes from ZM, storing them in BUF.
+   Returns the number of bytes read, or -1 on error */
+int zip_member_read (struct zip_member *zm, void *buf, size_t n);
+
+/* Unref (and possibly destroy) the zip member ZM */
+void zip_member_unref (struct zip_member *zm);
+
+/* Ref the zip member */
+void zip_member_ref (struct zip_member *zm);
+
+void zip_member_finish (struct zip_member *zm);
+
+
+#endif
index 7e1a6ccc178f5b3e3956c0d3d1e5b1c8f469248b..e93ec887e8be952e08a3db28cd79c4b139ec8249 100644 (file)
@@ -17,6 +17,7 @@
 #include <config.h>
 
 #include "libpspp/zip-writer.h"
+#include "libpspp/zip-private.h"
 
 #include <errno.h>
 #include <stdlib.h>
@@ -120,7 +121,7 @@ zip_writer_add (struct zip_writer *zw, FILE *file, const char *member_name)
 
   /* 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, 0);              /* compression method */
@@ -144,7 +145,7 @@ zip_writer_add (struct zip_writer *zw, FILE *file, const char *member_name)
     }
 
   /* Data descriptor. */
-  put_u32 (zw, 0x08074b50);
+  put_u32 (zw, MAGIC_DDHD);
   put_u32 (zw, crc);
   put_u32 (zw, size);
   put_u32 (zw, size);
@@ -179,7 +180,7 @@ zip_writer_close (struct zip_writer *zw)
       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 */
@@ -203,7 +204,7 @@ zip_writer_close (struct zip_writer *zw)
   dir_end = ftello (zw->file);
 
   /* 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 */
index c6c98f6dc7c3223ca1eef75bbb230e0783fe2d23..7d4afef7071fa4fb6778292ad3e3b3f6fb10d3ea 100644 (file)
@@ -27,6 +27,7 @@ check_PROGRAMS += \
        tests/libpspp/stringi-set-test \
        tests/libpspp/tower-test \
        tests/libpspp/u8-istream-test \
+       tests/libpspp/zip-test \
        tests/output/render-test
 
 check-programs: $(check_PROGRAMS)
@@ -213,6 +214,18 @@ tests_language_lexer_segment_test_SOURCES = \
        tests/language/lexer/segment-test.c
 tests_language_lexer_segment_test_CFLAGS = $(AM_CFLAGS)
 
+check_PROGRAMS += tests/libpspp/zip-test
+tests_libpspp_zip_test_SOURCES = \
+       src/libpspp/str.c \
+       src/libpspp/pool.c \
+       src/libpspp/temp-file.c \
+       src/libpspp/inflate.c \
+       src/libpspp/zip-reader.c \
+       src/libpspp/zip-writer.c \
+       tests/libpspp/zip-test.c
+tests_libpspp_zip_test_CFLAGS = $(AM_CFLAGS)
+
+
 check_PROGRAMS += tests/output/render-test
 tests_output_render_test_SOURCES = tests/output/render-test.c
 tests_output_render_test_LDADD = \
@@ -349,6 +362,7 @@ TESTSUITE_AT = \
        tests/libpspp/stringi-set.at \
        tests/libpspp/tower.at \
        tests/libpspp/u8-istream.at \
+       tests/libpspp/zip.at \
        tests/math/moments.at \
        tests/math/randist.at \
        tests/output/ascii.at \
diff --git a/tests/libpspp/zip-test.c b/tests/libpspp/zip-test.c
new file mode 100644 (file)
index 0000000..7a2fcb1
--- /dev/null
@@ -0,0 +1,95 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2011 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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+
+/* A simple program to zip or unzip a file */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdio.h>
+#include "libpspp/assertion.h"
+#include <libpspp/compiler.h>
+#include <libpspp/zip-writer.h>
+#include <libpspp/zip-reader.h>
+#include <libpspp/str.h>
+
+#include "xalloc.h"
+\f
+/* Exit with a failure code.
+   (Place a breakpoint on this function while debugging.) */
+static void
+check_die (void)
+{
+  exit (EXIT_FAILURE);
+}
+
+int
+main (int argc, char **argv)
+{
+  if ( argc < 4)
+    {
+      fprintf (stderr, "Usage zip-test: {r|w} archive file0 file1 ... filen\n");
+      check_die ();
+    }
+
+  if ( 0 == strcmp ("w", argv[1]))
+    {
+      int i;
+      struct zip_writer *zw = zip_writer_create (argv[2]);
+      for (i = 3; i < argc; ++i)
+       {
+         FILE *fp = fopen (argv[i], "r");
+         if (!fp ) check_die ();
+         zip_writer_add (zw, fp, argv[i]);
+       }
+      zip_writer_close (zw);
+    }
+  else if ( 0  == strcmp ("r", argv[1]))
+    {
+      const int BUFSIZE=256;
+      char buf[BUFSIZE];
+      int i;
+      struct string str;
+      struct zip_reader *zr = zip_reader_create (argv[2], &str);
+      for (i = 3; i < argc; ++i)
+       {
+         int x = 0;
+         struct zip_member *zm = zip_member_open (zr, argv[i]);
+         FILE *fp = fopen (argv[i], "w");
+
+         while ((x = zip_member_read (zm, buf, BUFSIZE)) > 0)
+           {
+             fwrite (buf, x, 1, fp);
+           }
+         fclose (fp);
+         if ( x < 0)
+           {
+             fprintf (stderr, "Unzip failed: %s", ds_cstr (&str));
+             check_die ();
+           }
+         
+         zip_member_unref (zm);
+       }
+      zip_reader_destroy (zr);
+    }
+  else 
+    exit (1);
+
+  return 0;
+}
diff --git a/tests/libpspp/zip.at b/tests/libpspp/zip.at
new file mode 100644 (file)
index 0000000..a725049
--- /dev/null
@@ -0,0 +1,43 @@
+AT_BANNER([zip])
+
+AT_SETUP([Basic zip - unzip test])
+AT_KEYWORDS([compression])
+
+AT_CHECK([dnl
+here=`pwd`
+dir1=$here/original
+dir2=$here/recovered
+
+mkdir -p $dir1
+
+# Generate files of differing sizes with random data in them
+names=""
+s=1;
+while test $s -le 8192 ; do
+       name=`mktemp -p $dir1`;
+       dd if=/dev/urandom of=$name count=1 bs=$s 2> /dev/null
+       s=$(($s * 2));
+       bn=`basename $name`;
+        names="$names $bn";
+done
+
+(cd $dir1 && $abs_top_builddir/tests/libpspp/zip-test w foo.zip $names)
+
+
+mkdir -p $dir2
+cp $dir1/foo.zip $dir2
+cd $dir2 
+$abs_top_builddir/tests/libpspp/zip-test r foo.zip $names
+
+# Compare the files to their originals
+for f in $names; do
+ diff $dir1/$f $dir2/$f;
+ if test $? -ne 0 ; then exit 1; fi;
+done
+
+exit 0
+])
+
+
+AT_CLEANUP
+