SmartPDF - lightweight pdf viewer app for rosapps
[reactos.git] / rosapps / smartpdf / fitz / samus / sa_zip.c
1 /*
2 * Support for a subset of PKZIP format v4.5:
3 * - no encryption
4 * - no multi-disk
5 * - only Store and Deflate
6 * - ZIP64 format (long long sizes and offsets) [TODO]
7 *
8 * TODO for Metro compliance: compare file names by unescaping %XX
9 * and then converting to upper-case NFC.
10 */
11
12 #include "fitz.h"
13 #include "samus.h"
14
15 typedef struct sa_zipent_s sa_zipent;
16
17 struct sa_zipent_s
18 {
19 unsigned offset;
20 unsigned csize;
21 unsigned usize;
22 char *name;
23 };
24
25 struct sa_zip_s
26 {
27 fz_stream *file;
28 int len;
29 sa_zipent *table;
30 };
31
32 static inline unsigned int read2(fz_stream *f)
33 {
34 unsigned char a = fz_readbyte(f);
35 unsigned char b = fz_readbyte(f);
36 return (b << 8) | a;
37 }
38
39 static inline unsigned int read4(fz_stream *f)
40 {
41 unsigned char a = fz_readbyte(f);
42 unsigned char b = fz_readbyte(f);
43 unsigned char c = fz_readbyte(f);
44 unsigned char d = fz_readbyte(f);
45 return (d << 24) | (c << 16) | (b << 8) | a;
46 }
47
48 static fz_error *readzipdir(sa_zip *zip, int startoffset)
49 {
50 unsigned sign;
51 unsigned csize, usize;
52 unsigned namesize, metasize, comsize;
53 unsigned offset;
54 int i;
55
56 fz_seek(zip->file, startoffset, 0);
57
58 for (i = 0; i < zip->len; i++)
59 {
60 sign = read4(zip->file);
61 if (sign != 0x02014b50)
62 return fz_throw("ioerror: unknown zip signature");
63
64 (void) read2(zip->file); /* version made by */
65 (void) read2(zip->file); /* version to extract */
66 (void) read2(zip->file); /* general */
67 (void) read2(zip->file); /* method */
68 (void) read2(zip->file); /* last mod file time */
69 (void) read2(zip->file); /* last mod file date */
70 (void) read4(zip->file); /* crc-32 */
71 csize = read4(zip->file);
72 usize = read4(zip->file);
73 namesize = read2(zip->file);
74 metasize = read2(zip->file);
75 comsize = read2(zip->file);
76 (void) read2(zip->file); /* disk number start */
77 (void) read2(zip->file); /* int file atts */
78 (void) read4(zip->file); /* ext file atts */
79 offset = read4(zip->file);
80
81 zip->table[i].offset = offset;
82 zip->table[i].csize = csize;
83 zip->table[i].usize = usize;
84
85 zip->table[i].name = fz_malloc(namesize + 1);
86 if (!zip->table[i].name)
87 return fz_outofmem;
88 fz_read(zip->file, zip->table[i].name, namesize);
89 zip->table[i].name[namesize] = 0;
90
91 fz_seek(zip->file, metasize, 1);
92 fz_seek(zip->file, comsize, 1);
93 }
94
95 return nil;
96 }
97
98 static fz_error *readzipendofdir(sa_zip *zip, int startoffset)
99 {
100 unsigned sign;
101 unsigned count;
102 unsigned offset;
103
104 fz_seek(zip->file, startoffset, 0);
105
106 sign = read4(zip->file);
107 if (sign != 0x06054b50)
108 return fz_throw("ioerror: unknown zip signature");
109
110 (void) read2(zip->file); /* this disk */
111 (void) read2(zip->file); /* start disk */
112 (void) read2(zip->file); /* ents in this disk */
113 count = read2(zip->file); /* ents in central directory */
114 (void) read4(zip->file); /* size of central directory */
115 offset = read4(zip->file); /* offset to central directory */
116
117 zip->len = count;
118 zip->table = fz_malloc(zip->len * sizeof(sa_zipent));
119 if (!zip->table)
120 return fz_outofmem;
121
122 memset(zip->table, 0, zip->len * sizeof(sa_zipent));
123
124 return readzipdir(zip, offset);
125 }
126
127 static fz_error *findzipendofdir(sa_zip *zip)
128 {
129 char buf[512];
130 int back, maxback, filesize;
131 int n, i;
132
133 filesize = fz_seek(zip->file, 0, 2);
134 if (filesize < 0)
135 return fz_ioerror(zip->file);
136
137 maxback = MIN(filesize, 0xFFFF + sizeof buf);
138 back = MIN(maxback, sizeof buf);
139
140 while (back < maxback)
141 {
142 fz_seek(zip->file, filesize - back, 0);
143 n = fz_read(zip->file, buf, sizeof buf);
144 if (n < 0)
145 return fz_ioerror(zip->file);
146
147 for (i = n - 4; i > 0; i--)
148 if (!memcmp(buf + i, "\120\113\5\6", 4))
149 return readzipendofdir(zip, filesize - back + i);
150
151 back += sizeof buf - 4;
152 }
153
154 return fz_throw("ioerror: could not find central directory in zip");
155 }
156
157 /*
158 * Open a ZIP archive for reading.
159 * Load the table of contents.
160 */
161 fz_error *
162 sa_openzip(sa_zip **zipp, char *filename)
163 {
164 fz_error *error;
165 sa_zip *zip;
166
167 zip = *zipp = fz_malloc(sizeof(sa_zip));
168 if (!zip)
169 return fz_outofmem;
170
171 zip->file = nil;
172 zip->len = 0;
173 zip->table = nil;
174
175 error = fz_openrfile(&zip->file, filename);
176 if (error)
177 return error;
178
179 return findzipendofdir(zip);
180 }
181
182 /*
183 * Free the table of contents and close the underlying file.
184 */
185 void
186 sa_closezip(sa_zip *zip)
187 {
188 int i;
189
190 if (zip->file)
191 fz_dropstream(zip->file);
192
193 for (i = 0; i < zip->len; i++)
194 if (zip->table[i].name)
195 fz_free(zip->table[i].name);
196
197 fz_free(zip->table);
198 }
199
200 /*
201 * Print a table of contents of the zip archive
202 */
203 void
204 sa_debugzip(sa_zip *zip)
205 {
206 int i;
207
208 for (i = 0; i < zip->len; i++)
209 {
210 printf("%8u ", zip->table[i].usize);
211 if (zip->table[i].usize)
212 printf("%3d%% ", zip->table[i].csize * 100 / zip->table[i].usize);
213 else
214 printf(" --- ");
215 printf("%s\n", zip->table[i].name);
216 }
217 }
218
219 int
220 sa_accesszipentry(sa_zip *zip, char *name)
221 {
222 int i;
223 for (i = 0; i < zip->len; i++)
224 if (!sa_strcmp(name, zip->table[i].name))
225 return 1;
226 return 0;
227 }
228
229 /*
230 * Seek and push decoding filter to read an individual file in the zip archive.
231 */
232 static fz_error *reallyopenzipentry(fz_stream **stmp, sa_zip *zip, int idx)
233 {
234 fz_error *error;
235 fz_filter *filter;
236 fz_obj *obj;
237 unsigned sign, version, general, method;
238 unsigned csize, usize;
239 unsigned namesize, metasize;
240 int t;
241
242 t = fz_seek(zip->file, zip->table[idx].offset, 0);
243 if (t < 0)
244 return fz_ioerror(zip->file);
245
246 sign = read4(zip->file);
247 if (sign != 0x04034b50)
248 return fz_throw("ioerror: unknown zip signature");
249
250 version = read2(zip->file);
251 general = read2(zip->file);
252 method = read2(zip->file);
253 (void) read2(zip->file); /* time */
254 (void) read2(zip->file); /* date */
255 (void) read4(zip->file); /* crc-32 */
256 csize = read4(zip->file);
257 usize = read4(zip->file);
258 namesize = read2(zip->file);
259 metasize = read2(zip->file);
260
261 if ((version & 0xff) > 45)
262 return fz_throw("ioerror: unsupported zip version");
263
264 if (general & 0x0001)
265 return fz_throw("ioerror: encrypted zip entry");
266
267 t = fz_seek(zip->file, namesize + metasize, 1);
268 if (t < 0)
269 return fz_ioerror(zip->file);
270
271 switch (method)
272 {
273 case 0:
274 error = fz_newnullfilter(&filter, csize);
275 if (error)
276 return error;
277 break;
278
279 case 8:
280 error = fz_packobj(&obj, "<</ZIP true>>");
281 if (error)
282 return error;
283 error = fz_newflated(&filter, obj);
284 fz_dropobj(obj);
285 if (error)
286 return error;
287 break;
288
289 default:
290 return fz_throw("ioerror: unsupported compression method");
291 break;
292 }
293
294 error = fz_openrfilter(stmp, filter, zip->file);
295 fz_dropfilter(filter);
296 if (error)
297 return error;
298
299 return nil;
300 }
301
302 fz_error *
303 sa_openzipentry(fz_stream **stmp, sa_zip *zip, char *name)
304 {
305 int i;
306
307 for (i = 0; i < zip->len; i++)
308 if (!sa_strcmp(name, zip->table[i].name))
309 return reallyopenzipentry(stmp, zip, i);
310
311 return fz_throw("ioerror: file not found in zip: '%s'", name);
312 }
313