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