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, &central_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