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 <byteswap.h>
23 #include <errno.h>
24 #include <stdlib.h>
25 #include <time.h>
26
27 #include "gl/crc.h"
28 #include "gl/fwriteerror.h"
29 #include "gl/xalloc.h"
30
31 #include "libpspp/message.h"
32 #include "libpspp/temp-file.h"
33
34 #include "gettext.h"
35 #define _(msgid) gettext (msgid)
36
37 struct zip_writer
38 {
39 char *file_name; /* File name, for use in error mesages. */
40 FILE *file; /* Output stream. */
41
42 uint16_t date, time; /* Date and time in MS-DOS format. */
43
44 bool ok;
45
46 /* Members already added to the file, so that we can summarize them to the
47 central directory at the end of the ZIP file. */
48 struct zip_member *members;
49 size_t n_members, allocated_members;
50 };
51
52 struct zip_member
53 {
54 uint32_t offset; /* Starting offset in file. */
55 uint32_t size; /* Length of member file data, in bytes. */
56 uint32_t crc; /* CRC-32 of member file data.. */
57 char *name; /* Name of member file. */
58 };
59
60 static void
put_bytes(struct zip_writer * zw,const void * p,size_t n)61 put_bytes (struct zip_writer *zw, const void *p, size_t n)
62 {
63 fwrite (p, 1, n, zw->file);
64 }
65
66 static void
put_u16(struct zip_writer * zw,uint16_t x)67 put_u16 (struct zip_writer *zw, uint16_t x)
68 {
69 #ifdef WORDS_BIGENDIAN
70 x = bswap_16 (x);
71 #endif
72 put_bytes (zw, &x, sizeof x);
73 }
74
75 static void
put_u32(struct zip_writer * zw,uint32_t x)76 put_u32 (struct zip_writer *zw, uint32_t x)
77 {
78 #ifdef WORDS_BIGENDIAN
79 x = bswap_32 (x);
80 #endif
81 put_bytes (zw, &x, sizeof x);
82 }
83
84 /* Starts writing a new ZIP file named FILE_NAME. Returns a new zip_writer if
85 successful, otherwise a null pointer. */
86 struct zip_writer *
zip_writer_create(const char * file_name)87 zip_writer_create (const char *file_name)
88 {
89 struct zip_writer *zw;
90 struct tm *tm;
91 time_t now;
92 FILE *file;
93
94 file = fopen (file_name, "wb");
95 if (file == NULL)
96 {
97 msg_error (errno, _("%s: error opening output file"), file_name);
98 return NULL;
99 }
100
101 zw = xmalloc (sizeof *zw);
102 zw->file_name = xstrdup (file_name);
103 zw->file = file;
104
105 zw->ok = true;
106
107 now = time (NULL);
108 tm = localtime (&now);
109 zw->date = tm->tm_mday + ((tm->tm_mon + 1) << 5) + ((tm->tm_year - 80) << 9);
110 zw->time = tm->tm_sec / 2 + (tm->tm_min << 5) + (tm->tm_hour << 11);
111
112 zw->members = NULL;
113 zw->n_members = 0;
114 zw->allocated_members = 0;
115
116 return zw;
117 }
118
119 static void
put_local_header(struct zip_writer * zw,const char * member_name,uint32_t crc,uint32_t size,int flag)120 put_local_header (struct zip_writer *zw, const char *member_name, uint32_t crc,
121 uint32_t size, int flag)
122 {
123 put_u32 (zw, MAGIC_LHDR); /* local file header signature */
124 put_u16 (zw, 10); /* version needed to extract */
125 put_u16 (zw, flag); /* 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, crc); /* crc-32 */
130 put_u32 (zw, size); /* compressed size */
131 put_u32 (zw, size); /* 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
137 /* Adds the contents of FILE, with name MEMBER_NAME, to ZW. */
138 void
zip_writer_add(struct zip_writer * zw,FILE * file,const char * member_name)139 zip_writer_add (struct zip_writer *zw, FILE *file, const char *member_name)
140 {
141 struct zip_member *member;
142 uint32_t offset, size;
143 size_t bytes_read;
144 uint32_t crc;
145 char buf[4096];
146
147 /* Local file header. */
148 offset = ftello (zw->file);
149 put_local_header (zw, member_name, 0, 0, 1 << 3);
150
151 /* File data. */
152 size = crc = 0;
153 fseeko (file, 0, SEEK_SET);
154 while ((bytes_read = fread (buf, 1, sizeof buf, file)) > 0)
155 {
156 put_bytes (zw, buf, bytes_read);
157 size += bytes_read;
158 crc = crc32_update (crc, buf, bytes_read);
159 }
160
161 /* Try to seek back to the local file header. If successful, overwrite it
162 with the correct file size and CRC. Otherwise, write data descriptor. */
163 if (fseeko (zw->file, offset, SEEK_SET) == 0)
164 {
165 put_local_header (zw, member_name, crc, size, 0);
166 if (fseeko (zw->file, size, SEEK_CUR)
167 && zw->ok)
168 {
169 msg_error (errno, _("%s: error seeking in output file"), zw->file_name);
170 zw->ok = false;
171 }
172 }
173 else
174 {
175 put_u32 (zw, MAGIC_DDHD);
176 put_u32 (zw, crc);
177 put_u32 (zw, size);
178 put_u32 (zw, size);
179 }
180
181 /* Add to set of members. */
182 if (zw->n_members >= zw->allocated_members)
183 zw->members = x2nrealloc (zw->members, &zw->allocated_members,
184 sizeof *zw->members);
185 member = &zw->members[zw->n_members++];
186 member->offset = offset;
187 member->size = size;
188 member->crc = crc;
189 member->name = xstrdup (member_name);
190 }
191
192 /* Adds a member named MEMBER_NAME whose contents is the null-terminated string
193 CONTENT. */
194 void
zip_writer_add_string(struct zip_writer * zw,const char * member_name,const char * content)195 zip_writer_add_string (struct zip_writer *zw, const char *member_name,
196 const char *content)
197 {
198 zip_writer_add_memory (zw, member_name, content, strlen (content));
199 }
200
201 /* Adds a member named MEMBER_NAME whose contents is the SIZE bytes of
202 CONTENT. */
203 void
zip_writer_add_memory(struct zip_writer * zw,const char * member_name,const void * content,size_t size)204 zip_writer_add_memory (struct zip_writer *zw, const char *member_name,
205 const void *content, size_t size)
206 {
207 FILE *fp = create_temp_file ();
208 if (fp == NULL)
209 {
210 msg_error (errno, _("error creating temporary file"));
211 zw->ok = false;
212 return;
213 }
214 fwrite (content, size, 1, fp);
215 zip_writer_add (zw, fp, member_name);
216 close_temp_file (fp);
217 }
218
219 /* Finalizes the contents of ZW and closes it. Returns true if successful,
220 false if a write error occurred while finalizing the file or at any earlier
221 time. */
222 bool
zip_writer_close(struct zip_writer * zw)223 zip_writer_close (struct zip_writer *zw)
224 {
225 uint32_t dir_start, dir_end;
226 size_t i;
227 bool ok;
228
229 if (zw == NULL)
230 return true;
231
232 dir_start = ftello (zw->file);
233 for (i = 0; i < zw->n_members; i++)
234 {
235 struct zip_member *m = &zw->members[i];
236
237 /* Central directory file header. */
238 put_u32 (zw, MAGIC_SOCD); /* central file header signature */
239 put_u16 (zw, 63); /* version made by */
240 put_u16 (zw, 10); /* version needed to extract */
241 put_u16 (zw, 1 << 3); /* general purpose bit flag */
242 put_u16 (zw, 0); /* compression method */
243 put_u16 (zw, zw->time); /* last mod file time */
244 put_u16 (zw, zw->date); /* last mod file date */
245 put_u32 (zw, m->crc); /* crc-32 */
246 put_u32 (zw, m->size); /* compressed size */
247 put_u32 (zw, m->size); /* uncompressed size */
248 put_u16 (zw, strlen (m->name)); /* file name length */
249 put_u16 (zw, 0); /* extra field length */
250 put_u16 (zw, 0); /* file comment length */
251 put_u16 (zw, 0); /* disk number start */
252 put_u16 (zw, 0); /* internal file attributes */
253 put_u32 (zw, 0); /* external file attributes */
254 put_u32 (zw, m->offset); /* relative offset of local header */
255 put_bytes (zw, m->name, strlen (m->name));
256 free (m->name);
257 }
258 free (zw->members);
259 dir_end = ftello (zw->file);
260
261 /* End of central directory record. */
262 put_u32 (zw, MAGIC_EOCD); /* end of central dir signature */
263 put_u16 (zw, 0); /* number of this disk */
264 put_u16 (zw, 0); /* number of the disk with the
265 start of the central directory */
266 put_u16 (zw, zw->n_members); /* total number of entries in the
267 central directory on this disk */
268 put_u16 (zw, zw->n_members); /* total number of entries in
269 the central directory */
270 put_u32 (zw, dir_end - dir_start); /* size of the central directory */
271 put_u32 (zw, dir_start); /* offset of start of central
272 directory with respect to
273 the starting disk number */
274 put_u16 (zw, 0); /* .ZIP file comment length */
275
276 ok = zw->ok;
277 if (ok && fwriteerror (zw->file))
278 {
279 msg_error (errno, _("%s: write failed"), zw->file_name);
280 ok = false;
281 }
282
283 free (zw->file_name);
284 free (zw);
285
286 return ok;
287 }
288