534e7ebab5734f1a4b572bb7c1abcc24de3af29a
[pspp-builds.git] / src / libpspp / zip-writer.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2010 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
21 #include <errno.h>
22 #include <stdlib.h>
23 #include <time.h>
24
25 #include "libpspp/integer-format.h"
26
27 #include "gl/crc.h"
28 #include "gl/error.h"
29 #include "gl/fwriteerror.h"
30 #include "gl/xalloc.h"
31
32 #include "gettext.h"
33 #define _(msgid) gettext (msgid)
34
35 struct zip_writer
36   {
37     char *file_name;            /* File name, for use in error mesages. */
38     FILE *file;                 /* Output stream. */
39
40     uint16_t date, time;        /* Date and time in MS-DOS format. */
41
42     /* Members already added to the file, so that we can summarize them to the
43        central directory at the end of the ZIP file. */
44     struct zip_member *members;
45     size_t n_members, allocated_members;
46   };
47
48 struct zip_member
49   {
50     uint32_t offset;            /* Starting offset in file. */
51     uint32_t size;              /* Length of member file data, in bytes. */
52     uint32_t crc;               /* CRC-32 of member file data.. */
53     char *name;                 /* Name of member file. */
54   };
55
56 static void
57 put_bytes (struct zip_writer *zw, const void *p, size_t n)
58 {
59   fwrite (p, 1, n, zw->file);
60 }
61
62 static void
63 put_u16 (struct zip_writer *zw, uint16_t x)
64 {
65   if (INTEGER_NATIVE != INTEGER_LSB_FIRST)
66     integer_convert (INTEGER_NATIVE, &x, INTEGER_MSB_FIRST, &x, sizeof x);
67   put_bytes (zw, &x, sizeof x);
68 }
69
70 static void
71 put_u32 (struct zip_writer *zw, uint32_t x)
72 {
73   if (INTEGER_NATIVE != INTEGER_LSB_FIRST)
74     integer_convert (INTEGER_NATIVE, &x, INTEGER_MSB_FIRST, &x, sizeof x);
75   put_bytes (zw, &x, sizeof x);
76 }
77
78 /* Starts writing a new ZIP file named FILE_NAME.  Returns a new zip_writer if
79    successful, otherwise a null pointer. */
80 struct zip_writer *
81 zip_writer_create (const char *file_name)
82 {
83   struct zip_writer *zw;
84   struct tm *tm;
85   time_t now;
86   FILE *file;
87
88   file = fopen (file_name, "wb");
89   if (file == NULL)
90     {
91       error (0, errno, _("%s: error opening output file"), file_name);
92       return NULL;
93     }
94
95   zw = xmalloc (sizeof *zw);
96   zw->file_name = xstrdup (file_name);
97   zw->file = file;
98
99   now = time (NULL);
100   tm = localtime (&now);
101   zw->date = tm->tm_mday + ((tm->tm_mon + 1) << 5) + ((tm->tm_year - 80) << 9);
102   zw->time = tm->tm_sec / 2 + (tm->tm_min << 5) + (tm->tm_hour << 11);
103
104   zw->members = NULL;
105   zw->n_members = 0;
106   zw->allocated_members = 0;
107
108   return zw;
109 }
110
111 /* Adds the contents of FILE, with name MEMBER_NAME, to ZW. */
112 void
113 zip_writer_add (struct zip_writer *zw, FILE *file, const char *member_name)
114 {
115   struct zip_member *member;
116   uint32_t offset, size;
117   size_t bytes_read;
118   uint32_t crc;
119   char buf[4096];
120
121   /* Local file header. */
122   offset = ftell (zw->file);
123   put_u32 (zw, 0x04034b50);     /* local file header signature */
124   put_u16 (zw, 10);             /* version needed to extract */
125   put_u16 (zw, 1 << 3);         /* general purpose bit flag */
126   put_u16 (zw, 0);              /* compression method */
127   put_u16 (zw, zw->time);       /* last mod file time */
128   put_u16 (zw, zw->date);       /* last mod file date */
129   put_u32 (zw, 0);              /* crc-32 */
130   put_u32 (zw, 0);              /* compressed size */
131   put_u32 (zw, 0);              /* uncompressed size */
132   put_u16 (zw, strlen (member_name)); /* file name length */
133   put_u16 (zw, 0);                    /* extra field length */
134   put_bytes (zw, member_name, strlen (member_name));
135
136   /* File data. */
137   size = crc = 0;
138   fseek (file, 0, SEEK_SET);
139   while ((bytes_read = fread (buf, 1, sizeof buf, file)) > 0)
140     {
141       put_bytes (zw, buf, bytes_read);
142       size += bytes_read;
143       crc = crc32_update (crc, buf, bytes_read);
144     }
145
146   /* Data descriptor. */
147   put_u32 (zw, 0x08074b50);
148   put_u32 (zw, crc);
149   put_u32 (zw, size);
150   put_u32 (zw, size);
151
152   /* Add to set of members. */
153   if (zw->n_members >= zw->allocated_members)
154     zw->members = x2nrealloc (zw->members, &zw->allocated_members,
155                               sizeof *zw->members);
156   member = &zw->members[zw->n_members++];
157   member->offset = offset;
158   member->size = size;
159   member->crc = crc;
160   member->name = xstrdup (member_name);
161 }
162
163 /* Finalizes the contents of ZW and closes it.  Returns true if successful,
164    false if a write error occurred while finalizing the file or at any earlier
165    time. */
166 bool
167 zip_writer_close (struct zip_writer *zw)
168 {
169   uint32_t dir_start, dir_end;
170   size_t i;
171   bool ok;
172
173   if (zw == NULL)
174     return true;
175
176   dir_start = ftell (zw->file);
177   for (i = 0; i < zw->n_members; i++)
178     {
179       struct zip_member *m = &zw->members[i];
180
181       /* Central directory file header. */
182       put_u32 (zw, 0x02014b50);       /* central file header signature */
183       put_u16 (zw, 63);               /* version made by */
184       put_u16 (zw, 10);               /* version needed to extract */
185       put_u16 (zw, 1 << 3);           /* general purpose bit flag */
186       put_u16 (zw, 0);                /* compression method */
187       put_u16 (zw, zw->time);         /* last mod file time */
188       put_u16 (zw, zw->date);         /* last mod file date */
189       put_u32 (zw, m->crc);           /* crc-32 */
190       put_u32 (zw, m->size);          /* compressed size */
191       put_u32 (zw, m->size);          /* uncompressed size */
192       put_u16 (zw, strlen (m->name)); /* file name length */
193       put_u16 (zw, 0);                /* extra field length */
194       put_u16 (zw, 0);                /* file comment length */
195       put_u16 (zw, 0);                /* disk number start */
196       put_u16 (zw, 0);                /* internal file attributes */
197       put_u32 (zw, 0);                /* external file attributes */
198       put_u32 (zw, m->offset);        /* relative offset of local header */
199       put_bytes (zw, m->name, strlen (m->name));
200       free (m->name);
201     }
202   free (zw->members);
203   dir_end = ftell (zw->file);
204
205   /* End of central directory record. */
206   put_u32 (zw, 0x06054b50);     /* end of central dir signature */
207   put_u16 (zw, 0);              /* number of this disk */
208   put_u16 (zw, 0);              /* number of the disk with the
209                                    start of the central directory */
210   put_u16 (zw, zw->n_members);  /* total number of entries in the
211                                    central directory on this disk */
212   put_u16 (zw, zw->n_members);  /* total number of entries in
213                                    the central directory */
214   put_u32 (zw, dir_end - dir_start); /* size of the central directory */
215   put_u32 (zw, dir_start);      /* offset of start of central
216                                    directory with respect to
217                                    the starting disk number */
218   put_u16 (zw, 0);              /* .ZIP file comment length */
219
220   if (!fwriteerror (zw->file))
221     ok = true;
222   else
223     {
224       error (0, errno, _("%s: write failed"), zw->file_name);
225       ok = false;
226     }
227
228   free (zw->file_name);
229   free (zw);
230
231   return ok;
232 }