work on docs
[pspp] / src / libpspp / zip-writer.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2010, 2012, 2014 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "libpspp/zip-writer.h"
20 #include "libpspp/zip-private.h"
21
22 #include <assert.h>
23 #include <byteswap.h>
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <time.h>
27 #include <unistd.h>
28
29 #include "gl/crc.h"
30 #include "gl/fwriteerror.h"
31 #include "gl/xalloc.h"
32
33 #include "libpspp/message.h"
34 #include "libpspp/temp-file.h"
35
36 #include "gettext.h"
37 #define _(msgid) gettext (msgid)
38
39 struct zip_writer
40   {
41     char *file_name;            /* File name, for use in error mesages. */
42     FILE *file;                 /* Output stream. */
43     uint32_t offset;            /* Offset in output stream. */
44
45     uint16_t date, time;        /* Date and time in MS-DOS format. */
46
47     bool ok;
48
49     /* Member being added to the file. */
50     char *m_name;
51     uint32_t m_start;
52     uint32_t m_size;
53     uint32_t m_crc;
54
55     /* Members already added to the file, so that we can summarize them to the
56        central directory at the end of the ZIP file. */
57     struct zip_member *members;
58     size_t n_members, allocated_members;
59   };
60
61 struct zip_member
62   {
63     uint32_t offset;            /* Starting offset in file. */
64     uint32_t size;              /* Length of member file data, in bytes. */
65     uint32_t crc;               /* CRC-32 of member file data.. */
66     char *name;                 /* Name of member file. */
67   };
68
69 static void
70 put_bytes (struct zip_writer *zw, const void *p, size_t n)
71 {
72   fwrite (p, 1, n, zw->file);
73   zw->offset += n;
74 }
75
76 static void
77 put_u16 (struct zip_writer *zw, uint16_t x)
78 {
79 #ifdef WORDS_BIGENDIAN
80   x = bswap_16 (x);
81 #endif
82   put_bytes (zw, &x, sizeof x);
83 }
84
85 static void
86 put_u32 (struct zip_writer *zw, uint32_t x)
87 {
88 #ifdef WORDS_BIGENDIAN
89   x = bswap_32 (x);
90 #endif
91   put_bytes (zw, &x, sizeof x);
92 }
93
94 /* Starts writing a new ZIP file named FILE_NAME.  Returns a new zip_writer if
95    successful, otherwise a null pointer. */
96 struct zip_writer *
97 zip_writer_create (const char *file_name)
98 {
99   FILE *file;
100   if (strcmp (file_name, "-"))
101     {
102       file = fopen (file_name, "wb");
103       if (file == NULL)
104         {
105           msg_error (errno, _("%s: error opening output file"), file_name);
106           return NULL;
107         }
108     }
109   else
110     {
111       if (isatty (STDOUT_FILENO))
112         {
113           msg (ME, _("%s: not writing ZIP file to terminal"), file_name);
114           return NULL;
115         }
116
117       file = stdout;
118     }
119
120   time_t now = time (NULL);
121   struct tm *tm = localtime (&now);
122
123   struct zip_writer *zw = xmalloc (sizeof *zw);
124   *zw = (struct zip_writer) {
125     .file_name = xstrdup (file_name),
126     .file = file,
127     .ok = true,
128     .date = tm->tm_mday + ((tm->tm_mon + 1) << 5) + ((tm->tm_year - 80) << 9),
129     .time = tm->tm_sec / 2 + (tm->tm_min << 5) + (tm->tm_hour << 11),
130   };
131   return zw;
132 }
133
134 static void
135 put_local_header (struct zip_writer *zw, const char *member_name, uint32_t crc,
136                   uint32_t size, int flag)
137 {
138   put_u32 (zw, MAGIC_LHDR);     /* local file header signature */
139   put_u16 (zw, 10);             /* version needed to extract */
140   put_u16 (zw, flag);           /* general purpose bit flag */
141   put_u16 (zw, 0);              /* compression method */
142   put_u16 (zw, zw->time);       /* last mod file time */
143   put_u16 (zw, zw->date);       /* last mod file date */
144   put_u32 (zw, crc);            /* crc-32 */
145   put_u32 (zw, size);           /* compressed size */
146   put_u32 (zw, size);           /* uncompressed size */
147   put_u16 (zw, strlen (member_name)); /* file name length */
148   put_u16 (zw, 0);                    /* extra field length */
149   put_bytes (zw, member_name, strlen (member_name));
150 }
151
152 /* Start adding a new member, named MEMBER_NAME, to ZW.  Add content to the
153    member by calling zip_writer_add_write() possibly multiple times, then
154    finish it off with zip_writer_add_finish().
155
156    Only one member may be open at a time. */
157 void
158 zip_writer_add_start (struct zip_writer *zw, const char *member_name)
159 {
160   assert (!zw->m_name);
161   zw->m_name = xstrdup (member_name);
162   zw->m_start = zw->offset;
163   zw->m_size = 0;
164   zw->m_crc = 0;
165   put_local_header (zw, member_name, 0, 0, 1 << 3);
166 }
167
168 /* Adds the N bytes in BUF to the currently open member in ZW. */
169 void
170 zip_writer_add_write (struct zip_writer *zw, const void *buf, size_t n)
171 {
172   assert (zw->m_name);
173   put_bytes (zw, buf, n);
174   zw->m_size += n;
175   zw->m_crc = crc32_update (zw->m_crc, buf, n);
176 }
177
178 /* Finishes writing the currently open member in ZW. */
179 void
180 zip_writer_add_finish (struct zip_writer *zw)
181 {
182   assert (zw->m_name);
183
184   /* Try to seek back to the local file header.  If successful, overwrite it
185      with the correct file size and CRC.  Otherwise, write data descriptor. */
186   if (fseeko (zw->file, zw->m_start, SEEK_SET) == 0)
187     {
188       uint32_t save_offset = zw->offset;
189       put_local_header (zw, zw->m_name, zw->m_crc, zw->m_size, 0);
190       if (fseeko (zw->file, zw->m_size, SEEK_CUR)
191           && zw->ok)
192         {
193           msg_error (errno, _("%s: error seeking in output file"), zw->file_name);
194           zw->ok = false;
195         }
196       zw->offset = save_offset;
197     }
198   else
199     {
200       put_u32 (zw, MAGIC_DDHD);
201       put_u32 (zw, zw->m_crc);
202       put_u32 (zw, zw->m_size);
203       put_u32 (zw, zw->m_size);
204     }
205
206   /* Add to set of members. */
207   if (zw->n_members >= zw->allocated_members)
208     zw->members = x2nrealloc (zw->members, &zw->allocated_members,
209                               sizeof *zw->members);
210   struct zip_member *member = &zw->members[zw->n_members++];
211   member->offset = zw->m_start;
212   member->size = zw->m_size;
213   member->crc = zw->m_crc;
214   member->name = zw->m_name;
215
216   zw->m_name = NULL;
217   zw->m_start = zw->m_size = zw->m_crc = 0;
218 }
219
220 /* Adds the contents of FILE, with name MEMBER_NAME, to ZW. */
221 void
222 zip_writer_add (struct zip_writer *zw, FILE *file, const char *member_name)
223 {
224   zip_writer_add_start (zw, member_name);
225
226   fseeko (file, 0, SEEK_SET);
227   for (;;)
228     {
229       char buf[4096];
230       size_t n = fread (buf, 1, sizeof buf, file);
231       if (!n)
232         break;
233       zip_writer_add_write (zw, buf, n);
234     }
235   zip_writer_add_finish (zw);
236 }
237
238 /* Adds a member named MEMBER_NAME whose contents is the null-terminated string
239    CONTENT. */
240 void
241 zip_writer_add_string (struct zip_writer *zw, const char *member_name,
242                        const char *content)
243 {
244   zip_writer_add_memory (zw, member_name, content, strlen (content));
245 }
246
247 /* Adds a member named MEMBER_NAME whose contents is the SIZE bytes of
248    CONTENT. */
249 void
250 zip_writer_add_memory (struct zip_writer *zw, const char *member_name,
251                        const void *content, size_t size)
252 {
253   zip_writer_add_start (zw, member_name);
254   zip_writer_add_write (zw, content, size);
255   zip_writer_add_finish (zw);
256 }
257
258 /* Finalizes the contents of ZW and closes it.  Returns true if successful,
259    false if a write error occurred while finalizing the file or at any earlier
260    time. */
261 bool
262 zip_writer_close (struct zip_writer *zw)
263 {
264   uint32_t dir_start, dir_end;
265   size_t i;
266   bool ok;
267
268   if (zw == NULL)
269     return true;
270
271   assert (!zw->m_name);
272
273   dir_start = zw->offset;
274   for (i = 0; i < zw->n_members; i++)
275     {
276       struct zip_member *m = &zw->members[i];
277
278       /* Central directory file header. */
279       put_u32 (zw, MAGIC_SOCD);       /* central file header signature */
280       put_u16 (zw, 63);               /* version made by */
281       put_u16 (zw, 10);               /* version needed to extract */
282       put_u16 (zw, 1 << 3);           /* general purpose bit flag */
283       put_u16 (zw, 0);                /* compression method */
284       put_u16 (zw, zw->time);         /* last mod file time */
285       put_u16 (zw, zw->date);         /* last mod file date */
286       put_u32 (zw, m->crc);           /* crc-32 */
287       put_u32 (zw, m->size);          /* compressed size */
288       put_u32 (zw, m->size);          /* uncompressed size */
289       put_u16 (zw, strlen (m->name)); /* file name length */
290       put_u16 (zw, 0);                /* extra field length */
291       put_u16 (zw, 0);                /* file comment length */
292       put_u16 (zw, 0);                /* disk number start */
293       put_u16 (zw, 0);                /* internal file attributes */
294       put_u32 (zw, 0);                /* external file attributes */
295       put_u32 (zw, m->offset);        /* relative offset of local header */
296       put_bytes (zw, m->name, strlen (m->name));
297       free (m->name);
298     }
299   free (zw->members);
300   dir_end = zw->offset;
301
302   /* End of central directory record. */
303   put_u32 (zw, MAGIC_EOCD);     /* end of central dir signature */
304   put_u16 (zw, 0);              /* number of this disk */
305   put_u16 (zw, 0);              /* number of the disk with the
306                                    start of the central directory */
307   put_u16 (zw, zw->n_members);  /* total number of entries in the
308                                    central directory on this disk */
309   put_u16 (zw, zw->n_members);  /* total number of entries in
310                                    the central directory */
311   put_u32 (zw, dir_end - dir_start); /* size of the central directory */
312   put_u32 (zw, dir_start);      /* offset of start of central
313                                    directory with respect to
314                                    the starting disk number */
315   put_u16 (zw, 0);              /* .ZIP file comment length */
316
317   ok = zw->ok;
318   if (ok && zw->file != stdout && fwriteerror (zw->file))
319     {
320       msg_error (errno, _("%s: write failed"), zw->file_name);
321       ok = false;
322     }
323
324   free (zw->file_name);
325   free (zw);
326
327   return ok;
328 }