1
2 #include "ngx_http_zip_module.h"
3 #include "ngx_http_zip_file.h"
4 #include "ngx_http_zip_file_format.h"
5 #include "ngx_http_zip_endian.h"
6
7 #ifdef NGX_ZIP_HAVE_ICONV
8 #include <iconv.h>
9 #endif
10
11 #ifdef NGX_ZIP_HAVE_ICONV
12 static ngx_str_t ngx_http_zip_header_charset_name = ngx_string("upstream_http_x_archive_charset");
13 #endif
14 static ngx_str_t ngx_http_zip_header_name_separator = ngx_string("upstream_http_x_archive_name_sep");
15
16 #define NGX_MAX_UINT16_VALUE 0xffff
17
18
19 // Chunk templates for fast struct init:
20
21 static ngx_zip_extra_field_local_t ngx_zip_extra_field_local_template = {
22 0x5455, /* tag for this extra block type ("UT") */
23 sizeof(ngx_zip_extra_field_local_t) - 4,
24 /* total data size for this block */
25 0x03, /* info bits */
26 0, /* modification time */
27 0, /* access time */
28 };
29
30 static ngx_zip_extra_field_central_t ngx_zip_extra_field_central_template = {
31 0x5455, /* tag for this extra block type ("UT") */
32 sizeof(ngx_zip_extra_field_central_t) - 4,
33 /* total data size for this block */
34 0x03, /* info bits */
35 0, /* modification time */
36 };
37
38 static ngx_zip_extra_field_unicode_path_t ngx_zip_extra_field_unicode_path_template = {
39 0x7075, /* Info-ZIP Unicode Path tag */
40 0,
41 1, /* version of this extra field, currently 1 (c) */
42 0, /* crc-32 */
43 };
44
45 static ngx_zip_extra_field_zip64_sizes_only_t ngx_zip_extra_field_zip64_sizes_only_template = {
46 0x0001, //tag for zip64 extra field
47 sizeof(ngx_zip_extra_field_zip64_sizes_only_t) - 4,
48 0,
49 0,
50 };
51
52 static ngx_zip_extra_field_zip64_offset_only_t ngx_zip_extra_field_zip64_offset_only_template = {
53 0x0001, //tag for zip64 extra field
54 sizeof(ngx_zip_extra_field_zip64_offset_only_t) - 4,
55 0,
56 };
57
58 static ngx_zip_extra_field_zip64_sizes_offset_t ngx_zip_extra_field_zip64_sizes_offset_template = {
59 0x0001, //tag for zip64 extra field
60 sizeof(ngx_zip_extra_field_zip64_sizes_offset_t) - 4,
61 0,
62 0,
63 0
64 };
65
66 static ngx_zip_data_descriptor_t ngx_zip_data_descriptor_template = {
67 0x08074b50, /* data descriptor signature */
68 0, /* crc-32 */
69 0, /* compressed size */
70 0 /* uncompressed size */
71 };
72
73 static ngx_zip_data_descriptor_zip64_t ngx_zip_data_descriptor_zip64_template = {
74 0x08074b50, /* data descriptor signature */
75 0, /* crc-32 */
76 0, /* compressed size */
77 0 /* uncompressed size */
78 };
79
80 static ngx_zip_local_file_header_t ngx_zip_local_file_header_template = {
81 0x04034b50, /* local file header signature */
82 0x0a, /* version needed to extract */
83 zip_utf8_flag | zip_missing_crc32_flag, /* general purpose bit flag */
84 0, /* compression method */
85 0, /* last mod file date/time */
86 0, /* crc-32 */
87 0xffffffff, /* compressed size */
88 0xffffffff, /* uncompressed size */
89 0, /* file name length */
90 sizeof(ngx_zip_extra_field_local_t),
91 /* extra field length */
92 };
93
94 static ngx_zip_central_directory_file_header_t ngx_zip_central_directory_file_header_template = {
95 0x02014b50, /* central file header signature */
96 zip_version_zip64, /* version made by */
97 zip_version_default, /* version needed to extract */
98 zip_utf8_flag | zip_missing_crc32_flag, /* general purpose bit flag */
99 0, /* compression method */
100 0, /* last mod file time */
101 0, /* crc-32 */
102 0xffffffff, /* compressed size */
103 0xffffffff, /* uncompressed size */
104 0, /* file name length */
105 sizeof(ngx_zip_extra_field_central_t),
106 /* extra field length */
107 0, /* file comment length */
108 0, /* disk number start */
109 0, /* internal file attributes */
110 0x81a4000, /* external file attributes */
111 0xffffffff /* relative offset of local header */
112 };
113
114 static ngx_zip_end_of_central_directory_record_t ngx_zip_end_of_central_directory_record_template = {
115 0x06054b50, /* end of central dir signature */
116 0, /* number of this disk */
117 0, /* number of the disk with the start of the central directory */
118 0xffff, /* total number of entries in the central directory on this disk */
119 0xffff, /* total number of entries in the central directory */
120 0xFFFFFFFF, /* size of the central directory */
121 0xffffffff, /* offset of start of central directory w.r.t. starting disk # */
122 0 /* .ZIP file comment length */
123 };
124
125 static ngx_zip_zip64_end_of_central_directory_record_t ngx_zip_zip64_end_of_central_directory_record_template = {
126 0x06064b50, //signature for zip64 EOCD
127 sizeof(ngx_zip_zip64_end_of_central_directory_record_t)-12, //size of this record (+variable fields, but minus signature and this size field), Size = SizeOfFixedFields + SizeOfVariableData - 12
128 zip_version_zip64, //created by
129 zip_version_zip64, //needed
130 0, //this disk number
131 0, // num of disk with start of CD
132 0,
133 0,
134 0,
135 0, // cd offset with respect to starting disk number
136 };
137
138 static ngx_zip_zip64_end_of_central_directory_locator_t ngx_zip_zip64_end_of_central_directory_locator_template = {
139 0x07064b50, //signature
140 0, // number of disk with start of zip64 end of central directory
141 0, // offset of central directory
142 1 //dics number total
143 };
144
145 //-----------------------------------------------------------------------------------------------------------
146
147
148 // Convert UNIX timestamp to DOS timestamp
ngx_dos_time(time_t t)149 static ngx_uint_t ngx_dos_time(time_t t)
150 {
151 ngx_tm_t tm;
152 ngx_gmtime(t, &tm); // ngx_gmtime does the mon++ and year += 1900 for us
153
154 return (tm.ngx_tm_sec >> 1)
155 + (tm.ngx_tm_min << 5)
156 + (tm.ngx_tm_hour << 11)
157 + (tm.ngx_tm_mday << 16)
158 + (tm.ngx_tm_mon << 21)
159 + ((tm.ngx_tm_year-1980) << 25);
160 }
161
162 static void
ngx_http_zip_truncate_buffer(ngx_buf_t * b,ngx_http_zip_range_t * piece_range,ngx_http_zip_range_t * req_range)163 ngx_http_zip_truncate_buffer(ngx_buf_t *b,
164 ngx_http_zip_range_t *piece_range, ngx_http_zip_range_t *req_range)
165 {
166 if (req_range && piece_range && b) {
167 if (req_range->end < piece_range->end)
168 b->last -= piece_range->end - req_range->end;
169 if (req_range->start > piece_range->start)
170 b->pos += req_range->start - piece_range->start;
171 }
172 }
173
174 static const char *
ngx_http_zip_strnrstr(const char * str,ngx_uint_t n,const char * sub_str,ngx_uint_t sub_n)175 ngx_http_zip_strnrstr(const char * str, ngx_uint_t n,
176 const char * sub_str, ngx_uint_t sub_n)
177 {
178 ngx_int_t max_shift = n - sub_n;
179 ngx_uint_t i;
180 for(; max_shift >= 0; --max_shift) {
181 for(i = 0; i <= n; ++i) {
182 if(i == sub_n)
183 return &str[max_shift];
184 else if(str[max_shift + i] != sub_str[i])
185 break;
186 }
187 }
188 return NULL;
189 }
190
191 #ifndef ICONV_CSNMAXLEN
192 #define ICONV_CSNMAXLEN 64
193 #endif
194
195 // make our proposed ZIP-file chunk map
196 ngx_int_t
ngx_http_zip_generate_pieces(ngx_http_request_t * r,ngx_http_zip_ctx_t * ctx)197 ngx_http_zip_generate_pieces(ngx_http_request_t *r, ngx_http_zip_ctx_t *ctx)
198 {
199 ngx_uint_t i, piece_i;
200 off_t offset = 0;
201 time_t unix_time = 0;
202 ngx_uint_t dos_time = 0;
203 ngx_http_zip_file_t *file;
204 ngx_http_zip_piece_t *header_piece, *file_piece, *trailer_piece, *cd_piece;
205 ngx_http_variable_value_t *vv;
206
207 if ((vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t))) == NULL)
208 return NGX_ERROR;
209
210 ctx->unicode_path = 0;
211 #ifdef NGX_ZIP_HAVE_ICONV
212 iconv_t *iconv_cd = NULL;
213 #endif
214
215 // Let's try to find special header that contains separator string.
216 // What for this strange separator string you ask?
217 // Sometimes there might be a problem converting UTF-8 to zips native
218 // charset(CP866), because it's not 1:1 conversion. So my solution is to
219 // developers provide their own version of converted filename and pass it
220 // to mod_zip along with UTF-8 filename which will go straight to Unicode
221 // path extra field (thanks to tony2001). So separator is a solution that doesn't
222 // break current format. And allows passing file name in both formats as one string.
223 //
224 // Normally we pass:
225 // CRC32 <size> <path> <filename>\n
226 // ...
227 // * <filename> passed to archive as filename w/o conversion
228 // * UFT-8 flag for filename is set
229 //
230 // tony2001's X-Archive-Charset: <charset> way:
231 // CRC32 <size> <path> <filename>\n
232 // ...
233 // * <filename> is accepted to be UTF-8 string
234 // * <filename>, converted to <charset> and passed to archive as filename
235 // * <filename> passed to Unicode path extra field
236 // * UFT-8 flag for filename is not set
237 //
238 // My X-Archive-Name-Sep: <sep> solution:
239 // CRC32 <size> <path> <native-filename><sep><utf8-filename>\n
240 // ...
241 // * <native-filename> passed to archive as filename w/o conversion
242 // * <utf8-filename> passed to Unicode path extra field
243 // * UFT-8 flag for filename is not set
244 //
245 // You just need to provide separator that won't interfere with file names. I suggest using '/'
246 // as it is ASCII character and forbidden on most (if not all) platforms as a part of filename.
247 //
248 // Empty separator string means no UTF-8 version provided. Usefull when we need to pass only
249 // names encoded in native charset. It's equal to 'X-Archive-Charset: native;'.
250 // Note: Currently it is impossible after '[PATCH] Support for UTF-8 file names.'(4f61592b)
251 // because UFT-8 flag (zip_utf8_flag) is set default for templates.
252
253 if (ngx_http_variable_unknown_header(vv, &ngx_http_zip_header_name_separator,
254 &r->upstream->headers_in.headers.part, sizeof("upstream_http_")-1) == NGX_OK && !vv->not_found) {
255 ctx->native_charset = 1;
256 if(vv->len)
257 ctx->unicode_path = 1;
258 } else {
259 #ifdef NGX_ZIP_HAVE_ICONV
260 if (ngx_http_variable_unknown_header(vv, &ngx_http_zip_header_charset_name,
261 &r->upstream->headers_in.headers.part, sizeof("upstream_http_")-1) == NGX_OK
262 && !vv->not_found && ngx_strncmp(vv->data, "utf8", sizeof("utf8") - 1) != 0) {
263
264 if(ngx_strncmp(vv->data, "native", sizeof("native") - 1))
265 {
266 char encoding[ICONV_CSNMAXLEN];
267 snprintf(encoding, sizeof(encoding), "%s//TRANSLIT//IGNORE", vv->data);
268
269 iconv_cd = iconv_open((const char *)encoding, "utf-8");
270 if (iconv_cd == (iconv_t)(-1)) {
271 ngx_log_error(NGX_LOG_WARN, r->connection->log, errno,
272 "mod_zip: iconv_open('%s', 'utf-8') failed",
273 vv->data);
274 iconv_cd = NULL;
275 }
276 else
277 {
278 ctx->unicode_path = 1;
279 ctx->native_charset = 1;
280 }
281 }
282 else
283 ctx->native_charset = 1;
284 }
285 #endif
286 }
287
288 // pieces: for each file: header, data, footer (if needed) -> 2 or 3 per file
289 // plus file footer (CD + [zip64 end + zip64 locator +] end of cd) in one chunk
290 ctx->pieces_n = ctx->files.nelts * (2 + (!!ctx->missing_crc32)) + 1;
291
292 if ((ctx->pieces = ngx_palloc(r->pool, sizeof(ngx_http_zip_piece_t) * ctx->pieces_n)) == NULL)
293 return NGX_ERROR;
294
295 ctx->cd_size = 0;
296 unix_time = time(NULL);
297 dos_time = ngx_dos_time(unix_time);
298 for (piece_i = i = 0; i < ctx->files.nelts; i++) {
299 file = &((ngx_http_zip_file_t *)ctx->files.elts)[i];
300 file->offset = offset;
301 file->unix_time = unix_time;
302 file->dos_time = dos_time;
303
304 if(ctx->unicode_path) {
305 #ifdef NGX_ZIP_HAVE_ICONV
306 if (iconv_cd) {
307 size_t inlen = file->filename.len, outlen, outleft;
308 u_char *p, *in;
309
310 //inbuf
311 file->filename_utf8.data = ngx_pnalloc(r->pool, file->filename.len + 1);
312 ngx_memcpy(file->filename_utf8.data, file->filename.data, file->filename.len);
313 file->filename_utf8.len = file->filename.len;
314 file->filename_utf8.data[file->filename.len] = '\0';
315
316 //outbuf
317 outlen = outleft = inlen * sizeof(int) + 15;
318 file->filename.data = ngx_pnalloc(r->pool, outlen + 1);
319
320 in = file->filename_utf8.data;
321 p = file->filename.data;
322
323 //reset state
324 iconv(iconv_cd, NULL, NULL, NULL, NULL);
325
326 //convert the string
327 iconv(iconv_cd, (char **)&in, &inlen, (char **)&p, &outleft);
328 //XXX if (res == (size_t)-1) { ? }
329
330 file->filename.len = outlen - outleft;
331
332 file->filename_utf8_crc32 = ngx_crc32_long(file->filename_utf8.data, file->filename_utf8.len);
333 }
334 else
335 #endif
336 if(vv->len) {
337 const char * sep = ngx_http_zip_strnrstr((const char*)file->filename.data, file->filename.len,
338 (const char*)vv->data, vv->len);
339 if(sep) {
340 size_t utf8_len = file->filename.len - vv->len - (size_t)(sep - (const char *)file->filename.data);
341 file->filename_utf8.data = ngx_pnalloc(r->pool, utf8_len);
342 file->filename_utf8.len = utf8_len;
343 ngx_memcpy(file->filename_utf8.data, sep + vv->len, utf8_len);
344
345 file->filename.len -= utf8_len + vv->len;
346 file->filename_utf8_crc32 = ngx_crc32_long(file->filename_utf8.data, file->filename_utf8.len);
347 } /* else { } */ // Separator not found. Okay, no extra field for this one then.
348 }
349 }
350
351 if(offset >= (off_t) NGX_MAX_UINT32_VALUE)
352 ctx->zip64_used = file->need_zip64_offset = 1;
353 if(file->size >= (off_t) NGX_MAX_UINT32_VALUE)
354 ctx->zip64_used = file->need_zip64 = 1;
355
356 ctx->cd_size += sizeof(ngx_zip_central_directory_file_header_t) + file->filename.len + sizeof(ngx_zip_extra_field_central_t);
357 if (file->need_zip64) {
358 if (file->need_zip64_offset) {
359 ctx->cd_size += sizeof(ngx_zip_extra_field_zip64_sizes_offset_t);
360 } else {
361 ctx->cd_size += sizeof(ngx_zip_extra_field_zip64_sizes_only_t);
362 }
363 } else if (file->need_zip64_offset) {
364 ctx->cd_size += sizeof(ngx_zip_extra_field_zip64_offset_only_t);
365 }
366 if (ctx->unicode_path && file->filename_utf8.len)
367 ctx->cd_size += sizeof(ngx_zip_extra_field_unicode_path_t) + file->filename_utf8.len;
368
369 header_piece = &ctx->pieces[piece_i++];
370 header_piece->type = zip_header_piece;
371 header_piece->file = file;
372 header_piece->range.start = offset;
373
374 offset += sizeof(ngx_zip_local_file_header_t) + file->filename.len + sizeof(ngx_zip_extra_field_local_t);
375 if (file->need_zip64)
376 offset += sizeof(ngx_zip_extra_field_zip64_sizes_only_t);
377 if (ctx->unicode_path && file->filename_utf8.len)
378 offset += sizeof(ngx_zip_extra_field_unicode_path_t) + file->filename_utf8.len;
379 header_piece->range.end = offset;
380
381 file_piece = &ctx->pieces[piece_i++];
382 file_piece->type = zip_file_piece;
383 file_piece->file = file;
384 file_piece->range.start = offset;
385 file_piece->range.end = offset += file->size; //!note: (sizeless chunks): we need file size here / or mark it and modify ranges after
386
387 if (file->missing_crc32) { // if incomplete header -> add footer with that info to file
388 trailer_piece = &ctx->pieces[piece_i++];
389 trailer_piece->type = zip_trailer_piece;
390 trailer_piece->file = file;
391 trailer_piece->range.start = offset;
392 trailer_piece->range.end = offset += file->need_zip64? sizeof(ngx_zip_data_descriptor_zip64_t) : sizeof(ngx_zip_data_descriptor_t);
393 //!!TODO: if we want Ranges support - here we know it is impossible for this set
394 //? check conf/some state and abort?
395 }
396 }
397
398 #ifdef NGX_ZIP_HAVE_ICONV
399 if (iconv_cd) {
400 iconv_close(iconv_cd);
401 }
402 #endif
403
404 ctx->zip64_used |= offset >= (off_t) NGX_MAX_UINT32_VALUE || ctx->files.nelts >= NGX_MAX_UINT16_VALUE;
405
406 ctx->cd_size += sizeof(ngx_zip_end_of_central_directory_record_t);
407 if (ctx->zip64_used)
408 ctx->cd_size += sizeof(ngx_zip_zip64_end_of_central_directory_record_t) + sizeof(ngx_zip_zip64_end_of_central_directory_locator_t);
409
410
411 cd_piece = &ctx->pieces[piece_i++];
412 cd_piece->type = zip_central_directory_piece;
413 cd_piece->range.start = offset;
414 cd_piece->range.end = offset += ctx->cd_size;
415
416 ctx->pieces_n = piece_i; //!! nasty hack (truncating allocated array without reallocation)
417
418 ctx->archive_size = offset;
419
420 return NGX_OK;
421 }
422
423 // make Local File Header chunk with extra fields
424 ngx_chain_t*
ngx_http_zip_file_header_chain_link(ngx_http_request_t * r,ngx_http_zip_ctx_t * ctx,ngx_http_zip_piece_t * piece,ngx_http_zip_range_t * range)425 ngx_http_zip_file_header_chain_link(ngx_http_request_t *r, ngx_http_zip_ctx_t *ctx,
426 ngx_http_zip_piece_t *piece, ngx_http_zip_range_t *range)
427 {
428 ngx_chain_t *link;
429 ngx_buf_t *b;
430 u_char *p;
431
432 ngx_http_zip_file_t *file = piece->file;
433 ngx_zip_extra_field_local_t extra_field_local;
434 ngx_zip_extra_field_zip64_sizes_only_t extra_field_zip64;
435 ngx_zip_local_file_header_t local_file_header;
436 ngx_zip_extra_field_unicode_path_t extra_field_unicode_path;
437
438 size_t len = sizeof(ngx_zip_local_file_header_t) + file->filename.len + sizeof(ngx_zip_extra_field_local_t);
439 if (file->need_zip64)
440 len += sizeof(ngx_zip_extra_field_zip64_sizes_only_t);
441 if (ctx->unicode_path && file->filename_utf8.len)
442 len += sizeof(ngx_zip_extra_field_unicode_path_t) + file->filename_utf8.len;
443
444 if ((link = ngx_alloc_chain_link(r->pool)) == NULL || (b = ngx_calloc_buf(r->pool)) == NULL
445 || (b->pos = ngx_pcalloc(r->pool, len)) == NULL)
446 return NULL;
447
448 b->memory = 1;
449 b->last = b->pos + len;
450 #if (NGX_HTTP_SSL)
451 b->flush = !!r->connection->ssl;
452 #endif
453
454 /* A note about the ZIP format: in order to appease all ZIP software I
455 * could find, the local file header contains the file sizes but not the
456 * CRC-32, even though setting the third bit of the general purpose bit
457 * flag would indicate that all three fields should be zeroed out.
458 */
459
460 local_file_header = ngx_zip_local_file_header_template;
461 local_file_header.signature = htole32(local_file_header.signature);
462 local_file_header.version = htole16(local_file_header.version);
463 local_file_header.flags = htole16(local_file_header.flags);
464 local_file_header.mtime = htole32(file->dos_time);
465 local_file_header.filename_len = htole16(file->filename.len);
466 if (ctx->native_charset) {
467 local_file_header.flags &= htole16(~zip_utf8_flag);
468 }
469 extra_field_zip64 = ngx_zip_extra_field_zip64_sizes_only_template;
470 extra_field_zip64.tag = htole16(extra_field_zip64.tag);
471 extra_field_zip64.size = htole16(extra_field_zip64.size);
472
473 if (file->need_zip64) {
474 local_file_header.version = htole16(zip_version_zip64);
475 local_file_header.extra_field_len += sizeof(ngx_zip_extra_field_zip64_sizes_only_t);
476
477 extra_field_zip64.uncompressed_size = extra_field_zip64.compressed_size = htole64(file->size);
478 } else {
479 local_file_header.compressed_size = htole32(file->size);
480 local_file_header.uncompressed_size = htole32(file->size);
481 }
482
483 extra_field_unicode_path = ngx_zip_extra_field_unicode_path_template;
484 extra_field_unicode_path.tag = htole16(extra_field_unicode_path.tag);
485
486 if (ctx->unicode_path && file->filename_utf8.len) {
487 extra_field_unicode_path.crc32 = htole32(file->filename_utf8_crc32);
488 extra_field_unicode_path.size = htole16(sizeof(ngx_zip_extra_field_unicode_path_t) + file->filename_utf8.len);
489
490 local_file_header.extra_field_len += sizeof(ngx_zip_extra_field_unicode_path_t) + file->filename_utf8.len;
491 }
492 local_file_header.extra_field_len = htole16(local_file_header.extra_field_len);
493
494 if (!file->missing_crc32) {
495 local_file_header.flags &= htole16(~zip_missing_crc32_flag);
496 local_file_header.crc32 = htole32(file->crc32);
497 }
498
499 extra_field_local = ngx_zip_extra_field_local_template;
500 extra_field_local.tag = htole16(extra_field_local.tag);
501 extra_field_local.size = htole16(extra_field_local.size);
502 extra_field_local.mtime = htole32(file->unix_time);
503 extra_field_local.atime = htole32(file->unix_time);
504
505 p = b->pos;
506
507 ngx_memcpy(p, &local_file_header, sizeof(ngx_zip_local_file_header_t));
508 p += sizeof(ngx_zip_local_file_header_t);
509
510 ngx_memcpy(p, file->filename.data, file->filename.len);
511 p += file->filename.len;
512
513 ngx_memcpy(p, &extra_field_local, sizeof(ngx_zip_extra_field_local_t));
514 p += sizeof(ngx_zip_extra_field_local_t);
515
516 if (file->need_zip64) {
517 ngx_memcpy(p, &extra_field_zip64, sizeof(ngx_zip_extra_field_zip64_sizes_only_t));
518 p += sizeof(ngx_zip_extra_field_zip64_sizes_only_t);
519 }
520
521 if (ctx->unicode_path && file->filename_utf8.len) {
522 ngx_memcpy(p, &extra_field_unicode_path, sizeof(ngx_zip_extra_field_unicode_path_t));
523 p += sizeof(ngx_zip_extra_field_unicode_path_t);
524
525 ngx_memcpy(p, file->filename_utf8.data, file->filename_utf8.len);
526 p += file->filename_utf8.len;
527 }
528
529 ngx_http_zip_truncate_buffer(b, &piece->range, range);
530
531 link->buf = b;
532 link->next = NULL;
533
534 return link;
535 }
536
537
538 // make buffer with 32/64 bit Data Descriptor chunk, this follows files with incomplete headers
539 ngx_chain_t*
ngx_http_zip_data_descriptor_chain_link(ngx_http_request_t * r,ngx_http_zip_piece_t * piece,ngx_http_zip_range_t * range)540 ngx_http_zip_data_descriptor_chain_link(ngx_http_request_t *r, ngx_http_zip_piece_t *piece, ngx_http_zip_range_t *range)
541 {
542 ngx_chain_t *link;
543 ngx_buf_t *b;
544 ngx_http_zip_file_t *file = piece->file;
545 size_t struct_size = file->need_zip64? sizeof(ngx_zip_data_descriptor_zip64_t) : sizeof(ngx_zip_data_descriptor_t);
546 union {
547 ngx_zip_data_descriptor_t descriptor;
548 ngx_zip_data_descriptor_zip64_t descriptor64;
549 } data;
550
551 if ((link = ngx_alloc_chain_link(r->pool)) == NULL || (b = ngx_calloc_buf(r->pool)) == NULL
552 || (b->pos = ngx_palloc(r->pool, struct_size)) == NULL)
553 return NULL;
554 b->memory = 1;
555 b->last = b->pos + struct_size;
556
557 if (!file->need_zip64) {
558 data.descriptor = ngx_zip_data_descriptor_template;
559 data.descriptor.signature = htole32(data.descriptor.signature);
560 data.descriptor.crc32 = htole32(file->crc32);
561 data.descriptor.compressed_size = data.descriptor.uncompressed_size = htole32(file->size);
562 } else {
563 data.descriptor64 = ngx_zip_data_descriptor_zip64_template;
564 data.descriptor64.signature = htole32(data.descriptor64.signature);
565 data.descriptor64.crc32 = htole32(file->crc32);
566 data.descriptor64.compressed_size = data.descriptor64.uncompressed_size = htole64(file->size);
567 }
568
569 ngx_memcpy(b->pos, &data, struct_size);
570 ngx_http_zip_truncate_buffer(b, &piece->range, range);
571
572 link->buf = b;
573 link->next = NULL;
574
575 return link;
576 }
577
578
579 //make archive footer: Central Directory, Zip64 Central Directory End, Zip64 locator and Central Directory end chunks
580 ngx_chain_t *
ngx_http_zip_central_directory_chain_link(ngx_http_request_t * r,ngx_http_zip_ctx_t * ctx,ngx_http_zip_piece_t * piece,ngx_http_zip_range_t * range)581 ngx_http_zip_central_directory_chain_link(ngx_http_request_t *r, ngx_http_zip_ctx_t *ctx, ngx_http_zip_piece_t *piece, ngx_http_zip_range_t *range)
582 {
583 //nb: this is to be called only after 'generate pieces'
584 ngx_chain_t *trailer;
585 ngx_buf_t *trailer_buf;
586 u_char *p;
587 off_t cd_size;
588 ngx_uint_t i;
589 ngx_array_t *files;
590 ngx_zip_end_of_central_directory_record_t eocdr;
591 ngx_zip_zip64_end_of_central_directory_record_t eocdr64;
592 ngx_zip_zip64_end_of_central_directory_locator_t locator64;
593
594 if (!ctx || !ctx->cd_size || (trailer = ngx_alloc_chain_link(r->pool)) == NULL
595 || (trailer_buf = ngx_calloc_buf(r->pool)) == NULL
596 || (p = ngx_palloc(r->pool, ctx->cd_size)) == NULL)
597 return NULL;
598
599 files = &ctx->files;
600 trailer->buf = trailer_buf;
601 trailer->next = NULL;
602
603 trailer_buf->pos = p;
604 trailer_buf->last = p + ctx->cd_size;
605 trailer_buf->last_buf = 1;
606 trailer_buf->sync = 1;
607 trailer_buf->memory = 1;
608
609 for (i = 0; i < files->nelts; i++)
610 p = ngx_http_zip_write_central_directory_entry(p, &((ngx_http_zip_file_t *)files->elts)[i], ctx);
611
612 eocdr = ngx_zip_end_of_central_directory_record_template;
613 eocdr.signature = htole32(eocdr.signature);
614 if (files->nelts < NGX_MAX_UINT16_VALUE) {
615 eocdr.disk_entries_n = htole16(files->nelts);
616 eocdr.entries_n = htole16(files->nelts);
617 }
618
619 cd_size = ctx->cd_size - sizeof(ngx_zip_end_of_central_directory_record_t)
620 - (!!ctx->zip64_used)*(sizeof(ngx_zip_zip64_end_of_central_directory_record_t)
621 + sizeof(ngx_zip_zip64_end_of_central_directory_locator_t));
622
623 if (cd_size < (off_t) NGX_MAX_UINT32_VALUE)
624 eocdr.size = htole32(cd_size);
625 if (piece->range.start < (off_t) NGX_MAX_UINT32_VALUE)
626 eocdr.offset = htole32(piece->range.start);
627
628 if (ctx->zip64_used) {
629 eocdr64 = ngx_zip_zip64_end_of_central_directory_record_template;
630 eocdr64.signature = htole32(eocdr64.signature);
631 eocdr64.size = htole64(eocdr64.size);
632 eocdr64.version_made_by = htole16(eocdr64.version_made_by);
633 eocdr64.version_needed = htole16(eocdr64.version_made_by);
634
635 eocdr64.cd_n_entries_on_this_disk = eocdr64.cd_n_entries_total = htole64(files->nelts);
636 eocdr64.cd_size = htole64(cd_size);
637 eocdr64.cd_offset = htole64(piece->range.start);
638
639 ngx_memcpy(p, &eocdr64, sizeof(ngx_zip_zip64_end_of_central_directory_record_t));
640 p += sizeof(ngx_zip_zip64_end_of_central_directory_record_t);
641
642 locator64 = ngx_zip_zip64_end_of_central_directory_locator_template;
643 locator64.signature = htole32(locator64.signature);
644 locator64.disks_total_n = htole32(locator64.disks_total_n);
645 locator64.cd_relative_offset = htole64(piece->range.start + cd_size);
646 ngx_memcpy(p, &locator64, sizeof(ngx_zip_zip64_end_of_central_directory_locator_t));
647 p += sizeof(ngx_zip_zip64_end_of_central_directory_locator_t);
648 }
649
650 ngx_memcpy(p, &eocdr, sizeof(ngx_zip_end_of_central_directory_record_t));
651
652 ngx_http_zip_truncate_buffer(trailer->buf, &piece->range, range);
653 return trailer;
654 }
655
656
657 u_char *
ngx_http_zip_write_central_directory_entry(u_char * p,ngx_http_zip_file_t * file,ngx_http_zip_ctx_t * ctx)658 ngx_http_zip_write_central_directory_entry(u_char *p, ngx_http_zip_file_t *file,
659 ngx_http_zip_ctx_t *ctx)
660 {
661 ngx_zip_extra_field_central_t extra_field_central;
662 ngx_zip_central_directory_file_header_t central_directory_file_header;
663 ngx_zip_extra_field_zip64_offset_only_t extra_zip64_offset;
664 ngx_zip_extra_field_zip64_sizes_offset_t extra_zip64_offset_size;
665 ngx_zip_extra_field_zip64_sizes_only_t extra_zip64_size;
666 ngx_zip_extra_field_unicode_path_t extra_field_unicode_path;
667 void* extra_zip64_ptr = NULL; //!!
668 size_t extra_zip64_ptr_size = 0;
669
670 central_directory_file_header = ngx_zip_central_directory_file_header_template;
671 central_directory_file_header.signature = htole32(central_directory_file_header.signature);
672 central_directory_file_header.version_made_by = htole16(central_directory_file_header.version_made_by);
673 central_directory_file_header.version_needed = htole16(central_directory_file_header.version_needed);
674 central_directory_file_header.flags = htole16(central_directory_file_header.flags);
675 central_directory_file_header.attr_external = htole32(central_directory_file_header.attr_external);
676 central_directory_file_header.mtime = htole32(file->dos_time);
677 central_directory_file_header.crc32 = htole32(file->crc32);
678
679 if (ctx->native_charset) {
680 central_directory_file_header.flags &= htole16(~zip_utf8_flag);
681 }
682
683 if (!file->need_zip64) {
684 central_directory_file_header.compressed_size = htole32(file->size);
685 central_directory_file_header.uncompressed_size = htole32(file->size);
686 }
687 central_directory_file_header.filename_len = htole16(file->filename.len);
688 if (!file->need_zip64_offset)
689 central_directory_file_header.offset = htole32(file->offset);
690 if (!file->missing_crc32)
691 central_directory_file_header.flags &= htole16(~zip_missing_crc32_flag);
692
693 if (file->need_zip64) {
694 central_directory_file_header.version_needed = zip_version_zip64;
695 if (file->need_zip64_offset){
696 extra_zip64_offset_size = ngx_zip_extra_field_zip64_sizes_offset_template;
697 extra_zip64_offset_size.tag = htole16(extra_zip64_offset_size.tag);
698 extra_zip64_offset_size.size = htole16(extra_zip64_offset_size.size);
699 extra_zip64_offset_size.relative_header_offset = htole64(file->offset);
700 extra_zip64_offset_size.compressed_size = extra_zip64_offset_size.uncompressed_size = htole64(file->size);
701 extra_zip64_ptr = &extra_zip64_offset_size;
702 extra_zip64_ptr_size = sizeof(extra_zip64_offset_size);
703 } else { //zip64 only
704 extra_zip64_size = ngx_zip_extra_field_zip64_sizes_only_template;
705 extra_zip64_size.tag = htole16(extra_zip64_size.tag);
706 extra_zip64_size.size = htole16(extra_zip64_size.size);
707 extra_zip64_size.compressed_size = extra_zip64_size.uncompressed_size = htole64(file->size);
708 extra_zip64_ptr = &extra_zip64_size;
709 extra_zip64_ptr_size = sizeof(extra_zip64_size);
710 }
711 } else if (file->need_zip64_offset) {
712 extra_zip64_offset = ngx_zip_extra_field_zip64_offset_only_template;
713 extra_zip64_offset.tag = htole16(extra_zip64_offset.tag);
714 extra_zip64_offset.size = htole16(extra_zip64_offset.size);
715 extra_zip64_offset.relative_header_offset = htole64(file->offset);
716 extra_zip64_ptr = &extra_zip64_offset;
717 extra_zip64_ptr_size = sizeof(extra_zip64_offset);
718 }
719 central_directory_file_header.extra_field_len=sizeof(ngx_zip_extra_field_central_t)+extra_zip64_ptr_size;
720 extra_field_central = ngx_zip_extra_field_central_template;
721 extra_field_central.tag = htole16(extra_field_central.tag);
722 extra_field_central.size = htole16(extra_field_central.size);
723 extra_field_central.mtime = htole32(file->unix_time);
724
725 extra_field_unicode_path = ngx_zip_extra_field_unicode_path_template;
726 extra_field_unicode_path.tag = htole16(extra_field_unicode_path.tag);
727
728 if (ctx->unicode_path && file->filename_utf8.len) {
729 extra_field_unicode_path.crc32 = htole32(file->filename_utf8_crc32);
730 extra_field_unicode_path.size = htole16(sizeof(ngx_zip_extra_field_unicode_path_t) + file->filename_utf8.len);
731
732 central_directory_file_header.extra_field_len += sizeof(ngx_zip_extra_field_unicode_path_t) + file->filename_utf8.len;
733 }
734 central_directory_file_header.extra_field_len = htole16(central_directory_file_header.extra_field_len);
735
736 ngx_memcpy(p, ¢ral_directory_file_header, sizeof(ngx_zip_central_directory_file_header_t));
737 p += sizeof(ngx_zip_central_directory_file_header_t);
738
739 ngx_memcpy(p, file->filename.data, file->filename.len);
740 p += file->filename.len;
741
742 ngx_memcpy(p, &extra_field_central, sizeof(ngx_zip_extra_field_central_t));
743 p += sizeof(ngx_zip_extra_field_central_t);
744
745 if (extra_zip64_ptr) {
746 ngx_memcpy(p, extra_zip64_ptr, extra_zip64_ptr_size);
747 p += extra_zip64_ptr_size;
748 }
749
750 if (ctx->unicode_path && file->filename_utf8.len) {
751 ngx_memcpy(p, &extra_field_unicode_path, sizeof(ngx_zip_extra_field_unicode_path_t));
752 p += sizeof(ngx_zip_extra_field_unicode_path_t);
753
754 ngx_memcpy(p, file->filename_utf8.data, file->filename_utf8.len);
755 p += file->filename_utf8.len;
756 }
757 return p;
758 }
759