From 9530bce28d817a0d106d34144289ba2b12d04c19 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Tue, 23 Feb 2010 22:07:44 -0800 Subject: [PATCH] odt: Remove dependency on external "zip" program. --- Smake | 1 + src/libpspp/automake.mk | 4 +- src/libpspp/zip-writer.c | 232 +++++++++++++++++++++++++++++++++++++++ src/libpspp/zip-writer.h | 27 +++++ src/output/odt.c | 133 ++++++++-------------- 5 files changed, 311 insertions(+), 86 deletions(-) create mode 100644 src/libpspp/zip-writer.c create mode 100644 src/libpspp/zip-writer.h diff --git a/Smake b/Smake index 10b51243..686fb756 100644 --- a/Smake +++ b/Smake @@ -11,6 +11,7 @@ GNULIB_MODULES = \ c-strtod \ close \ count-one-bits \ + crc \ crypto/md4 \ dirname \ environ \ diff --git a/src/libpspp/automake.mk b/src/libpspp/automake.mk index 0172d80f..4f3a7fc1 100644 --- a/src/libpspp/automake.mk +++ b/src/libpspp/automake.mk @@ -82,7 +82,9 @@ src_libpspp_libpspp_la_SOURCES = \ src/libpspp/tmpfile.h \ src/libpspp/tower.c \ src/libpspp/tower.h \ - src/libpspp/version.h + src/libpspp/version.h \ + src/libpspp/zip-writer.c \ + src/libpspp/zip-writer.h DISTCLEANFILES+=src/libpspp/version.c diff --git a/src/libpspp/zip-writer.c b/src/libpspp/zip-writer.c new file mode 100644 index 00000000..534e7eba --- /dev/null +++ b/src/libpspp/zip-writer.c @@ -0,0 +1,232 @@ +/* PSPP - a program for statistical analysis. + Copyright (C) 2010 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 . */ + +#include + +#include "libpspp/zip-writer.h" + +#include +#include +#include + +#include "libpspp/integer-format.h" + +#include "gl/crc.h" +#include "gl/error.h" +#include "gl/fwriteerror.h" +#include "gl/xalloc.h" + +#include "gettext.h" +#define _(msgid) gettext (msgid) + +struct zip_writer + { + char *file_name; /* File name, for use in error mesages. */ + FILE *file; /* Output stream. */ + + uint16_t date, time; /* Date and time in MS-DOS format. */ + + /* 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; + size_t n_members, allocated_members; + }; + +struct zip_member + { + uint32_t offset; /* Starting offset in file. */ + uint32_t size; /* Length of member file data, in bytes. */ + uint32_t crc; /* CRC-32 of member file data.. */ + char *name; /* Name of member file. */ + }; + +static void +put_bytes (struct zip_writer *zw, const void *p, size_t n) +{ + fwrite (p, 1, n, zw->file); +} + +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); + 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); + put_bytes (zw, &x, sizeof x); +} + +/* Starts writing a new ZIP file named FILE_NAME. Returns a new zip_writer if + successful, otherwise a null pointer. */ +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) + { + error (0, errno, _("%s: error opening output file"), 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); + + zw->members = NULL; + zw->n_members = 0; + zw->allocated_members = 0; + + 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) +{ + struct zip_member *member; + uint32_t offset, size; + size_t bytes_read; + uint32_t crc; + char buf[4096]; + + /* Local file header. */ + offset = ftell (zw->file); + put_u32 (zw, 0x04034b50); /* 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)); + + /* File data. */ + size = crc = 0; + fseek (file, 0, SEEK_SET); + while ((bytes_read = fread (buf, 1, sizeof buf, file)) > 0) + { + put_bytes (zw, buf, bytes_read); + size += bytes_read; + crc = crc32_update (crc, buf, bytes_read); + } + + /* 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); +} + +/* Finalizes the contents of ZW and closes it. Returns true if successful, + false if a write error occurred while finalizing the file or at any earlier + time. */ +bool +zip_writer_close (struct zip_writer *zw) +{ + uint32_t dir_start, dir_end; + size_t i; + bool ok; + + if (zw == NULL) + return true; + + dir_start = ftell (zw->file); + 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_u16 (zw, 63); /* version made by */ + 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, m->crc); /* crc-32 */ + put_u32 (zw, m->size); /* compressed size */ + put_u32 (zw, m->size); /* uncompressed size */ + put_u16 (zw, strlen (m->name)); /* file name length */ + put_u16 (zw, 0); /* extra field length */ + put_u16 (zw, 0); /* file comment length */ + put_u16 (zw, 0); /* disk number start */ + put_u16 (zw, 0); /* internal file attributes */ + put_u32 (zw, 0); /* external file attributes */ + put_u32 (zw, m->offset); /* relative offset of local header */ + put_bytes (zw, m->name, strlen (m->name)); + free (m->name); + } + free (zw->members); + dir_end = ftell (zw->file); + + /* End of central directory record. */ + put_u32 (zw, 0x06054b50); /* 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 */ + put_u16 (zw, zw->n_members); /* total number of entries in the + central directory on this disk */ + put_u16 (zw, zw->n_members); /* total number of entries in + the central directory */ + put_u32 (zw, dir_end - dir_start); /* size of the central directory */ + put_u32 (zw, dir_start); /* offset of start of central + directory with respect to + the starting disk number */ + put_u16 (zw, 0); /* .ZIP file comment length */ + + if (!fwriteerror (zw->file)) + ok = true; + else + { + error (0, errno, _("%s: write failed"), zw->file_name); + ok = false; + } + + free (zw->file_name); + free (zw); + + return ok; +} diff --git a/src/libpspp/zip-writer.h b/src/libpspp/zip-writer.h new file mode 100644 index 00000000..aff3f994 --- /dev/null +++ b/src/libpspp/zip-writer.h @@ -0,0 +1,27 @@ +/* PSPP - a program for statistical analysis. + Copyright (C) 2010 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 . */ + +#ifndef LIBPSPP_ZIP_WRITER_H +#define LIBPSPP_ZIP_WRITER_H 1 + +#include +#include + +struct zip_writer *zip_writer_create (const char *file_name); +void zip_writer_add (struct zip_writer *, FILE *, const char *member_name); +bool zip_writer_close (struct zip_writer *); + +#endif /* libpspp/zip-writer.h */ diff --git a/src/output/odt.c b/src/output/odt.c index 99f5e60d..49a55b4c 100644 --- a/src/output/odt.c +++ b/src/output/odt.c @@ -33,6 +33,7 @@ #include "libpspp/cast.h" #include "libpspp/str.h" #include "libpspp/version.h" +#include "libpspp/zip-writer.h" #include "output/driver-provider.h" #include "output/message-item.h" #include "output/options.h" @@ -53,17 +54,16 @@ struct odt_driver { struct output_driver driver; + struct zip_writer *zip; /* ZIP file writer. */ char *file_name; /* Output file name. */ - bool debug; - /* The name of the temporary directory used to construct the ODF */ - char *dirname; + /* content.xml */ + xmlTextWriterPtr content_wtr; /* XML writer. */ + FILE *content_file; /* Temporary file. */ - /* Writer for the content.xml file */ - xmlTextWriterPtr content_wtr; - - /* Writer fot the manifest.xml file */ - xmlTextWriterPtr manifest_wtr; + /* manifest.xml */ + xmlTextWriterPtr manifest_wtr; /* XML writer. */ + FILE *manifest_file; /* Temporary file. */ /* Number of tables so far. */ int table_num; @@ -83,52 +83,34 @@ odt_driver_cast (struct output_driver *driver) /* Create the "mimetype" file needed by ODF */ static bool -create_mimetype (const char *dirname) +create_mimetype (struct zip_writer *zip) { FILE *fp; - struct string filename; - ds_init_cstr (&filename, dirname); - ds_put_cstr (&filename, "/mimetype"); - fp = fopen (ds_cstr (&filename), "w"); + fp = tmpfile (); if (fp == NULL) { - error (0, errno, _("error opening output file \"%s\""), - ds_cstr (&filename)); - ds_destroy (&filename); + error (0, errno, _("error creating temporary file")); return false; } - ds_destroy (&filename); fprintf (fp, "application/vnd.oasis.opendocument.text"); + zip_writer_add (zip, fp, "mimetype"); fclose (fp); return true; } -/* Create a new XML file called FILENAME in the temp directory, and return a writer for it */ -static xmlTextWriterPtr -create_writer (const struct odt_driver *driver, const char *filename) +/* Creates a new temporary file and stores it in *FILE, then creates an XML + writer for it and stores it in *W. */ +static void +create_writer (FILE **file, xmlTextWriterPtr *w) { - char *copy = NULL; - xmlTextWriterPtr w; - struct string str; - ds_init_cstr (&str, driver->dirname); - ds_put_cstr (&str, "/"); - ds_put_cstr (&str, filename); - - /* dirname modifies its argument, so we must copy it */ - copy = xstrdup (ds_cstr (&str)); - mkdir (dirname (copy), 0700); - free (copy); - - w = xmlNewTextWriterFilename (ds_cstr (&str), 0); - - ds_destroy (&str); - - xmlTextWriterStartDocument (w, NULL, "UTF-8", NULL); + /* XXX this can fail */ + *file = tmpfile (); + *w = xmlNewTextWriter (xmlOutputBufferCreateFile (*file, NULL)); - return w; + xmlTextWriterStartDocument (*w, NULL, "UTF-8", NULL); } @@ -145,7 +127,10 @@ register_file (struct odt_driver *odt, const char *filename) static void write_style_data (struct odt_driver *odt) { - xmlTextWriterPtr w = create_writer (odt, "styles.xml"); + xmlTextWriterPtr w; + FILE *file; + + create_writer (&file, &w); register_file (odt, "styles.xml"); xmlTextWriterStartElement (w, _xml ("office:document-styles")); @@ -232,12 +217,17 @@ write_style_data (struct odt_driver *odt) xmlTextWriterEndDocument (w); xmlFreeTextWriter (w); + zip_writer_add (odt->zip, file, "styles.xml"); + fclose (file); } static void write_meta_data (struct odt_driver *odt) { - xmlTextWriterPtr w = create_writer (odt, "meta.xml"); + xmlTextWriterPtr w; + FILE *file; + + create_writer (&file, &w); register_file (odt, "meta.xml"); xmlTextWriterStartElement (w, _xml ("office:document-meta")); @@ -291,46 +281,37 @@ write_meta_data (struct odt_driver *odt) xmlTextWriterEndElement (w); xmlTextWriterEndDocument (w); xmlFreeTextWriter (w); -} - -enum -{ - output_file_arg, - boolean_arg, -}; - -static struct driver_option * -opt (struct output_driver *d, struct string_map *options, const char *key, - const char *default_value) -{ - return driver_option_get (d, options, key, default_value); + zip_writer_add (odt->zip, file, "meta.xml"); + fclose (file); } static struct output_driver * odt_create (const char *file_name, enum settings_output_devices device_type, - struct string_map *o) + struct string_map *o UNUSED) { struct output_driver *d; struct odt_driver *odt; + struct zip_writer *zip; + + zip = zip_writer_create (file_name); + if (zip == NULL) + return NULL; odt = xzalloc (sizeof *odt); d = &odt->driver; output_driver_init (d, &odt_driver_class, file_name, device_type); + odt->zip = zip; odt->file_name = xstrdup (file_name); - odt->debug = parse_boolean (opt (d, o, "debug", "false")); - - odt->dirname = xstrdup ("odt-XXXXXX"); - mkdtemp (odt->dirname); - if (!create_mimetype (odt->dirname)) + if (!create_mimetype (zip)) { output_driver_destroy (d); return NULL; } /* Create the manifest */ - odt->manifest_wtr = create_writer (odt, "META-INF/manifest.xml"); + create_writer (&odt->manifest_file, &odt->manifest_wtr); xmlTextWriterStartElement (odt->manifest_wtr, _xml("manifest:manifest")); xmlTextWriterWriteAttribute (odt->manifest_wtr, _xml("xmlns:manifest"), @@ -347,7 +328,7 @@ odt_create (const char *file_name, enum settings_output_devices device_type, write_meta_data (odt); write_style_data (odt); - odt->content_wtr = create_writer (odt, "content.xml"); + create_writer (&odt->content_file, &odt->content_wtr); register_file (odt, "content.xml"); @@ -373,6 +354,8 @@ odt_create (const char *file_name, enum settings_output_devices device_type, xmlTextWriterEndElement (odt->manifest_wtr); xmlTextWriterEndDocument (odt->manifest_wtr); xmlFreeTextWriter (odt->manifest_wtr); + zip_writer_add (odt->zip, odt->manifest_file, "META-INF/manifest.xml"); + fclose (odt->manifest_file); return d; } @@ -384,39 +367,19 @@ odt_destroy (struct output_driver *driver) if (odt->content_wtr != NULL) { - struct string zip_cmd; - xmlTextWriterEndElement (odt->content_wtr); /* office:text */ xmlTextWriterEndElement (odt->content_wtr); /* office:body */ xmlTextWriterEndElement (odt->content_wtr); /* office:document-content */ xmlTextWriterEndDocument (odt->content_wtr); xmlFreeTextWriter (odt->content_wtr); + zip_writer_add (odt->zip, odt->content_file, "content.xml"); + fclose (odt->content_file); - /* Zip up the directory */ - ds_init_empty (&zip_cmd); - ds_put_format (&zip_cmd, - "cd %s ; rm -f ../%s; zip -q -X ../%s mimetype; zip -q -X -u -r ../%s .", - odt->dirname, odt->file_name, odt->file_name, odt->file_name); - system (ds_cstr (&zip_cmd)); - ds_destroy (&zip_cmd); + zip_writer_close (odt->zip); } - - if ( !odt->debug ) - { - /* Remove the temp dir */ - struct string rm_cmd; - - ds_init_empty (&rm_cmd); - ds_put_format (&rm_cmd, "rm -r %s", odt->dirname); - system (ds_cstr (&rm_cmd)); - ds_destroy (&rm_cmd); - } - else - fprintf (stderr, "Not removing directory %s\n", odt->dirname); - + free (odt->command_name); - free (odt->dirname); free (odt); } -- 2.30.2