Permalink
Browse files

First commit.

  • Loading branch information...
0 parents commit 72f60ce16024d2144198973abb935a98e676fc9b Evan Miller committed Oct 19, 2009
74 CHANGES
@@ -0,0 +1,74 @@
+Changes with mod_zip 1.1.5 13 Aug 2009
+
+ *) Bugfix: compatibility with nginx 0.7.25 and later
+
+ *) Bugfix: Range works with local, Memcached, and FastCGI files
+
+Changes with mod_zip 1.1.4 10 Feb 2009
+
+ *) Bugfix: compilation error on FreeBSD
+
+ *) Feature: Range end is optional (e.g. "bytes=0-")
+
+Changes with mod_zip 1.1.3 13 Aug 2008
+
+ *) Bugfix: crash when subrequests returned 404
+
+ *) Bugfix: compatibility with BOMArchiveHelper.app on Mac OS X (but only
+ when CRC-32's are supplied).
+
+ *) Feature: CRC-32's are optional. Use "-" to have mod_zip calculate them
+ on the fly. However, note that without CRC-32's up front, mod_zip will
+ not honor "Range" requests.
+
+
+Changes with mod_zip 1.1.2 06 Aug 2008
+
+ *) Bugfix: compilation error with nginx 0.6.x
+
+
+Changes with mod_zip 1.1.1 05 Aug 2008
+
+ *) Bugfix: compilation error on some platforms.
+
+
+Changes with mod_zip 1.1 04 Aug 2008
+
+ *) Feature: "Range" header support.
+
+ *) Change: new file list syntax. See README.
+
+
+Changes with mod_zip 1.0.8 13 Dec 2007
+
+ *) Bugfix: strip "Range" header from subrequests
+
+
+Changes with mod_zip 1.0.7 29 Nov 2007
+
+ *) Bugfix: clear outgoing "Accept-Ranges" header
+
+
+Changes with mod_zip 1.0.5 18 Sep 2007
+
+ *) Bugfix: support archives larger than 2GB
+
+
+Changes with mod_zip 1.0.4 31 Aug 2007
+
+ *) Bugfix: support empty files in an archive
+
+
+Changes with mod_zip 1.0.3 16 Aug 2007
+
+ *) Feature: support for Content-Length when X-Archive-Files-* headers are provided
+
+
+Changes with mod_zip 1.0.2 06 Aug 2007
+
+ *) Bugfix: Fixed compilation with no --with-http-debug flag
+
+
+Changes with mod_zip 1.0.1 04 Jul 2007
+
+ *) Initial public release
39 README
@@ -0,0 +1,39 @@
+mod_zip
+-------
+
+mod_zip assembles ZIP archives dynamically. It can stream component files from
+upstream servers with nginx's native proxying code, so that the process never
+takes up more than a few KB of RAM at a time, even while assembling archives that
+are (potentially) hundreds of megabytes.
+
+To install, compile nginx with the following option:
+
+ --add-module=/path/to/this/directory
+
+nginx 0.6.17 or later is required.
+
+The module is activated when the original response (presumably from an
+upstream) includes the following HTTP header:
+
+ X-Archive-Files: zip
+
+It then scans the response body for a list of files. The syntax is a
+space-separated list of the file checksum (CRC-32), size (in bytes), location
+(properly URL-encoded), and file name. One file per line. The file location
+corresponds to a location in your nginx.conf; the file can be on disk, from an
+upstream, or from another module. The file name can include a directory path,
+and is what will be extracted from the ZIP file. Example:
+
+1034ab38 428 /foo.txt My Document1.txt
+83e8110b 100339 /bar.txt My Other Document1.txt
+
+Files are retrieved and encoded in order. If a file cannot be found or the file
+request returns any sort of error, the download is aborted.
+
+The CRC-32 is optional. Put "-" if you don't know the CRC-32; note that in this
+case mod_zip will disable support the "Range" header.
+
+Tip: add a header "Content-Disposition: attachment; filename=foobar.zip" in the
+upstream response if you would like the client to name the file "foobar.zip"
+
+Questions/patches may be directed to Evan Miller, emmiller@gmail.com.
6 config
@@ -0,0 +1,6 @@
+ngx_addon_name=ngx_http_zip_module
+HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_zip_module"
+NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_zip_module.c"
+NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_zip_parsers.c"
+NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_zip_file.c"
+NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_zip_headers.c"
384 ngx_http_zip_file.c
@@ -0,0 +1,384 @@
+
+#include "ngx_http_zip_module.h"
+#include "ngx_http_zip_file.h"
+#include "ngx_http_zip_file_format.h"
+
+static ngx_zip_extra_field_local_t ngx_zip_extra_field_local_template = {
+ 0x5455, /* tag for this extra block type ("UT") */
+ sizeof(ngx_zip_extra_field_local_t) - 4,
+ /* total data size for this block */
+ 0x03, /* info bits */
+ 0, /* modification time */
+ 0, /* access time */
+};
+
+static ngx_zip_extra_field_central_t ngx_zip_extra_field_central_template = {
+ 0x5455, /* tag for this extra block type ("UT") */
+ sizeof(ngx_zip_extra_field_central_t) - 4,
+ /* total data size for this block */
+ 0x03, /* info bits */
+ 0, /* modification time */
+};
+
+static ngx_zip_data_descriptor_t ngx_zip_data_descriptor_template = {
+ 0x08074b50, /* data descriptor signature */
+ 0, /* crc-32 */
+ 0, /* compressed size */
+ 0 /* uncompressed size */
+};
+
+static ngx_zip_local_file_header_t ngx_zip_local_file_header_template = {
+ 0x04034b50, /* local file header signature */
+ 0x0a, /* version needed to extract */
+ 0x08, /* general purpose bit flag */
+ 0, /* compression method */
+ 0, /* last mod file date/time */
+ 0, /* crc-32 */
+ 0, /* compressed size */
+ 0, /* uncompressed size */
+ 0, /* file name length */
+ sizeof(ngx_zip_extra_field_local_t),
+ /* extra field length */
+};
+
+static ngx_zip_central_directory_file_header_t ngx_zip_central_directory_file_header_template = {
+ 0x02014b50, /* central file header signature */
+ 0x0300, /* version made by */
+ 0x0a, /* version needed to extract */
+ 0x08, /* general purpose bit flag */
+ 0, /* compression method */
+ 0, /* last mod file time */
+ 0, /* crc-32 */
+ 0, /* compressed size */
+ 0, /* uncompressed size */
+ 0, /* file name length */
+ sizeof(ngx_zip_extra_field_central_t),
+ /* extra field length */
+ 0, /* file comment length */
+ 0, /* disk number start */
+ 0, /* internal file attributes */
+ 0x81a40000, /* external file attributes */
+ 0 /* relative offset of local header */
+};
+
+static ngx_zip_end_of_central_directory_record_t ngx_zip_end_of_central_directory_record_template = {
+ 0x06054b50, /* end of central dir signature */
+ 0, /* number of this disk */
+ 0, /* number of the disk with the start of the central directory */
+ 0, /* total number of entries in the central directory on this disk */
+ 0, /* total number of entries in the central directory */
+ 0, /* size of the central directory */
+ 0, /* offset of start of central directory w.r.t. starting disk # */
+ 0 /* .ZIP file comment length */
+};
+
+static ngx_uint_t ngx_dos_time();
+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);
+
+/*
+ * Takes a UNIX timestamp and returns a DOS timestamp
+ */
+static ngx_uint_t ngx_dos_time(time_t t)
+{
+ ngx_tm_t tm;
+
+ /* ngx_gmtime does the mon++ and year += 1900 for us */
+ ngx_gmtime(t, &tm);
+
+ return (tm.ngx_tm_sec >> 1)
+ + (tm.ngx_tm_min << 5)
+ + (tm.ngx_tm_hour << 11)
+ + (tm.ngx_tm_mday << 16)
+ + (tm.ngx_tm_mon << 21)
+ + ((tm.ngx_tm_year-1980) << 25);
+}
+
+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)
+{
+ if (req_range && piece_range && b) {
+ if (req_range->end < piece_range->end)
+ b->last -= piece_range->end - req_range->end;
+ if (req_range->start > piece_range->start)
+ b->pos += req_range->start - piece_range->start;
+ }
+}
+
+off_t
+ngx_http_zip_calculate_central_directory_size(off_t files_n,
+ off_t filename_s)
+{
+ return
+ files_n * sizeof(ngx_zip_central_directory_file_header_t)
+ + filename_s
+ + files_n * sizeof(ngx_zip_extra_field_central_t)
+ + sizeof(ngx_zip_end_of_central_directory_record_t);
+}
+
+ngx_int_t
+ngx_http_zip_generate_pieces(ngx_http_request_t *r, ngx_http_zip_ctx_t *ctx)
+{
+ ngx_uint_t i, piece_i;
+ off_t offset = 0;
+ off_t filenames_len = 0;
+ ngx_http_zip_file_t *file;
+ ngx_http_zip_piece_t *header_piece, *file_piece, *trailer_piece, *cd_piece;
+
+ ctx->pieces_n = ctx->files.nelts * (2 + ctx->missing_crc32) + 1;
+
+ if ((ctx->pieces = ngx_palloc(r->pool, sizeof(ngx_http_zip_piece_t) *
+ ctx->pieces_n)) == NULL) {
+ return NGX_ERROR;
+ }
+
+ for (piece_i=i=0; i<ctx->files.nelts; i++) {
+ file = &((ngx_http_zip_file_t *)ctx->files.elts)[i];
+ filenames_len += file->filename.len;
+ file->offset = offset;
+
+ header_piece = &ctx->pieces[piece_i++];
+ header_piece->type = zip_header_piece;
+ header_piece->file = file;
+ header_piece->range.start = offset;
+ header_piece->range.end = offset += sizeof(ngx_zip_local_file_header_t)
+ + file->filename.len + sizeof(ngx_zip_extra_field_local_t);
+
+ file_piece = &ctx->pieces[piece_i++];
+ file_piece->type = zip_file_piece;
+ file_piece->file = file;
+ file_piece->range.start = offset;
+ file_piece->range.end = offset += file->size;
+
+ if (ctx->missing_crc32) {
+ trailer_piece = &ctx->pieces[piece_i++];
+ trailer_piece->type = zip_trailer_piece;
+ trailer_piece->file = file;
+ trailer_piece->range.start = offset;
+ trailer_piece->range.end = offset += sizeof(ngx_zip_data_descriptor_t);
+ }
+ }
+
+ cd_piece = &ctx->pieces[piece_i];
+ cd_piece->type = zip_central_directory_piece;
+ cd_piece->range.start = offset;
+ cd_piece->range.end = offset +=
+ ngx_http_zip_calculate_central_directory_size(ctx->files.nelts,
+ filenames_len);
+
+ ctx->archive_size = offset;
+
+ return NGX_OK;
+}
+
+/* Craft a header for a file in a ZIP archive
+ */
+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)
+{
+ ngx_chain_t *link;
+ ngx_buf_t *b;
+ size_t len;
+ ngx_http_zip_file_t *file = piece->file;
+ ngx_zip_extra_field_local_t extra_field_local;
+ ngx_zip_local_file_header_t local_file_header;
+
+ if ((link = ngx_alloc_chain_link(r->pool)) == NULL) {
+ return NULL;
+ }
+
+ if ((b = ngx_calloc_buf(r->pool)) == NULL) {
+ return NULL;
+ }
+
+ len = sizeof(ngx_zip_local_file_header_t)
+ + file->filename.len + sizeof(ngx_zip_extra_field_local_t);
+
+ if ((b->pos = ngx_pcalloc(r->pool, len)) == NULL) {
+ return NULL;
+ }
+
+ b->memory = 1;
+ b->last = b->pos + len;
+
+ file->unix_time = time(NULL);
+ file->dos_time = ngx_dos_time(file->unix_time);
+
+ /* A note about the ZIP format: in order to appease all ZIP software I
+ * could find, the local file header contains the file sizes but not the
+ * CRC-32, even though setting the third bit of the general purpose bit
+ * flag would indicate that all three fields should be zeroed out.
+ */
+
+ local_file_header = ngx_zip_local_file_header_template;
+ local_file_header.mtime = file->dos_time;
+ local_file_header.compressed_size = file->size;
+ local_file_header.uncompressed_size = file->size;
+ local_file_header.filename_len = file->filename.len;
+ if (!ctx->missing_crc32) {
+ local_file_header.flags = 0;
+ local_file_header.crc32 = file->crc32;
+ }
+
+ extra_field_local = ngx_zip_extra_field_local_template;
+ extra_field_local.mtime = file->unix_time;
+ extra_field_local.atime = file->unix_time;
+
+ ngx_memcpy(b->pos,
+ &local_file_header, sizeof(ngx_zip_local_file_header_t));
+ ngx_memcpy(b->pos + sizeof(ngx_zip_local_file_header_t),
+ file->filename.data, file->filename.len);
+ ngx_memcpy(b->pos + sizeof(ngx_zip_local_file_header_t) + file->filename.len,
+ &extra_field_local, sizeof(ngx_zip_extra_field_local_t));
+
+ ngx_http_zip_truncate_buffer(b, &piece->range, range);
+
+ link->buf = b;
+ link->next = NULL;
+
+ return link;
+}
+
+/* Craft a trailer for a file in a ZIP archive
+ */
+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)
+{
+ ngx_chain_t *link;
+ ngx_buf_t *b;
+ ngx_http_zip_file_t *file = piece->file;
+ ngx_zip_data_descriptor_t data_descriptor;
+
+ if ((link = ngx_alloc_chain_link(r->pool)) == NULL) {
+ return NULL;
+ }
+
+ if ((b = ngx_calloc_buf(r->pool)) == NULL) {
+ return NULL;
+ }
+
+ b->pos = ngx_palloc(r->pool, sizeof(ngx_zip_data_descriptor_t));
+ if (b->pos == NULL) {
+ return NULL;
+ }
+
+ b->memory = 1;
+ b->last = b->pos + sizeof(ngx_zip_data_descriptor_t);
+
+ data_descriptor = ngx_zip_data_descriptor_template;
+ data_descriptor.crc32 = file->crc32;
+ data_descriptor.compressed_size = file->size;
+ data_descriptor.uncompressed_size = file->size;
+
+ ngx_memcpy(b->pos, &data_descriptor, sizeof(ngx_zip_data_descriptor_t));
+
+ ngx_http_zip_truncate_buffer(b, &piece->range, range);
+
+ link->buf = b;
+ link->next = NULL;
+
+ return link;
+}
+
+/* Attach the trailer to the whole ZIP archive */
+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)
+{
+ ngx_chain_t *trailer;
+ ngx_buf_t *trailer_buf;
+ size_t cd_len = 0, filenames_len = 0;
+ u_char *p;
+ ngx_uint_t i;
+ ngx_http_zip_file_t *file;
+ ngx_array_t *files;
+ ngx_zip_end_of_central_directory_record_t eocdr;
+
+ if (ctx == NULL) {
+ return NULL;
+ }
+
+ files = &ctx->files;
+
+ if ((trailer = ngx_alloc_chain_link(r->pool)) == NULL) {
+ return NULL;
+ }
+
+ if ((trailer_buf = ngx_calloc_buf(r->pool)) == NULL) {
+ return NULL;
+ }
+
+ trailer->buf = trailer_buf;
+ trailer->next = NULL;
+
+ for (i=0; i < files->nelts; i++) {
+ file = &((ngx_http_zip_file_t *)files->elts)[i];
+ filenames_len += file->filename.len;
+ }
+
+ cd_len = ngx_http_zip_calculate_central_directory_size(files->nelts,
+ filenames_len);
+
+ if ((p = ngx_palloc(r->pool, cd_len)) == NULL) {
+ return NULL;
+ }
+
+ trailer_buf->pos = p;
+ trailer_buf->last = p + cd_len;
+ trailer_buf->last_buf = 1;
+ trailer_buf->sync = 1;
+ trailer_buf->memory = 1;
+
+ for (i=0; i < files->nelts; i++) {
+ p = ngx_http_zip_write_central_directory_entry(p,
+ &((ngx_http_zip_file_t *)files->elts)[i], ctx);
+ }
+
+ eocdr = ngx_zip_end_of_central_directory_record_template;
+ eocdr.disk_entries_n = files->nelts;
+ eocdr.entries_n = files->nelts;
+ eocdr.size = cd_len - sizeof(ngx_zip_end_of_central_directory_record_t);
+ eocdr.offset = piece->range.start;
+
+ ngx_memcpy(p, &eocdr, sizeof(ngx_zip_end_of_central_directory_record_t));
+
+ ngx_http_zip_truncate_buffer(trailer->buf, &piece->range, range);
+
+ return trailer;
+}
+
+u_char *
+ngx_http_zip_write_central_directory_entry(u_char *p, ngx_http_zip_file_t *file,
+ ngx_http_zip_ctx_t *ctx)
+{
+ ngx_zip_extra_field_central_t extra_field_central;
+ ngx_zip_central_directory_file_header_t central_directory_file_header;
+
+ central_directory_file_header = ngx_zip_central_directory_file_header_template;
+ central_directory_file_header.mtime = file->dos_time;
+ central_directory_file_header.crc32 = file->crc32;
+ central_directory_file_header.compressed_size = file->size;
+ central_directory_file_header.uncompressed_size = file->size;
+ central_directory_file_header.filename_len = file->filename.len;
+ central_directory_file_header.offset = file->offset;
+ if (!ctx->missing_crc32) {
+ central_directory_file_header.flags = 0;
+ }
+
+ extra_field_central = ngx_zip_extra_field_central_template;
+ extra_field_central.mtime = file->unix_time;
+
+ ngx_memcpy(p, &central_directory_file_header,
+ sizeof(ngx_zip_central_directory_file_header_t));
+
+ ngx_memcpy(p += sizeof(ngx_zip_central_directory_file_header_t),
+ file->filename.data, file->filename.len);
+
+ ngx_memcpy(p += file->filename.len,
+ &extra_field_central, sizeof(ngx_zip_extra_field_central_t));
+
+ return p + sizeof(ngx_zip_extra_field_central_t);
+}
12 ngx_http_zip_file.h
@@ -0,0 +1,12 @@
+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);
+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);
+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);
+u_char *ngx_http_zip_write_central_directory_entry(u_char *p,
+ ngx_http_zip_file_t *file, ngx_http_zip_ctx_t *ctx);
+off_t ngx_http_zip_calculate_central_directory_size(off_t files_n,
+ off_t filename_s);
+ngx_int_t ngx_http_zip_generate_pieces(ngx_http_request_t *r,
+ ngx_http_zip_ctx_t *ctx);
73 ngx_http_zip_file_format.h
@@ -0,0 +1,73 @@
+/* useful byte arrays for rolling a ZIP archive */
+/* see http://www.pkware.com/documents/casestudies/APPNOTE.TXT */
+/* also http://www.unix-ag.uni-kl.de/~conrad/krypto/pkcrack/doc/appnote.iz.txt */
+
+/* byte-align structs to conform to ZIP format */
+#pragma pack(push, 1)
+
+typedef struct {
+ uint16_t tag;
+ uint16_t size;
+ uint8_t info;
+ uint32_t mtime;
+ uint32_t atime;
+} ngx_zip_extra_field_local_t;
+
+typedef struct {
+ uint16_t tag;
+ uint16_t size;
+ uint8_t info;
+ uint32_t mtime;
+} ngx_zip_extra_field_central_t;
+
+typedef struct {
+ uint32_t signature;
+ uint32_t crc32;
+ uint32_t compressed_size;
+ uint32_t uncompressed_size;
+} ngx_zip_data_descriptor_t;
+
+typedef struct {
+ uint32_t signature;
+ uint16_t version;
+ uint16_t flags;
+ uint16_t compression_method;
+ uint32_t mtime;
+ uint32_t crc32;
+ uint32_t compressed_size;
+ uint32_t uncompressed_size;
+ uint16_t filename_len;
+ uint16_t extra_field_len;
+} ngx_zip_local_file_header_t;
+
+typedef struct {
+ uint32_t signature;
+ uint16_t version_made_by;
+ uint16_t version_needed;
+ uint16_t flags;
+ uint16_t compression_method;
+ uint32_t mtime;
+ uint32_t crc32;
+ uint32_t compressed_size;
+ uint32_t uncompressed_size;
+ uint16_t filename_len;
+ uint16_t extra_field_len;
+ uint16_t comment_len;
+ uint16_t disk_n;
+ uint16_t attr_internal;
+ uint32_t attr_external;
+ uint32_t offset;
+} ngx_zip_central_directory_file_header_t;
+
+typedef struct {
+ uint32_t signature;
+ uint16_t disk_n;
+ uint16_t cd_disk_n;
+ uint16_t disk_entries_n;
+ uint16_t entries_n;
+ uint32_t size;
+ uint32_t offset;
+ uint16_t comment_len;
+} ngx_zip_end_of_central_directory_record_t;
+
+#pragma pack(pop)
213 ngx_http_zip_headers.c
@@ -0,0 +1,213 @@
+#include "ngx_http_zip_module.h"
+#include "ngx_http_zip_headers.h"
+#include "ngx_http_zip_file.h"
+
+ngx_int_t
+ngx_http_zip_add_cache_control(ngx_http_request_t *r)
+{
+ ngx_table_elt_t **ccp, *cc;
+ ngx_uint_t i;
+
+ /* convoluted way of adding Cache-Control: max-age=0 */
+ /* The header is necessary so IE doesn't barf */
+ ccp = r->headers_out.cache_control.elts;
+
+ if (ccp == NULL) {
+ if (ngx_array_init(&r->headers_out.cache_control, r->pool,
+ 1, sizeof(ngx_table_elt_t *))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ ccp = ngx_array_push(&r->headers_out.cache_control);
+ if (ccp == NULL) {
+ return NGX_ERROR;
+ }
+
+ cc = ngx_list_push(&r->headers_out.headers);
+ if (cc == NULL) {
+ return NGX_ERROR;
+ }
+
+ cc->hash = 1;
+ cc->key.len = sizeof("Cache-Control") - 1;
+ cc->key.data = (u_char *) "Cache-Control";
+
+ *ccp = cc;
+
+ } else {
+ for (i = 1; i < r->headers_out.cache_control.nelts; i++) {
+ ccp[i]->hash = 0;
+ }
+
+ cc = ccp[0];
+ }
+
+ cc->value.len = sizeof("max-age=0") - 1;
+ cc->value.data = (u_char *) "max-age=0";
+
+ return NGX_OK;
+}
+
+ngx_int_t
+ngx_http_zip_add_content_range_header(ngx_http_request_t *r)
+{
+ ngx_table_elt_t *content_range;
+
+ content_range = ngx_list_push(&r->headers_out.headers);
+ if (content_range == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->headers_out.content_range = content_range;
+
+ content_range->hash = 1;
+ content_range->key.len = sizeof("Content-Range") - 1;
+ content_range->key.data = (u_char *) "Content-Range";
+
+ if (r->headers_out.content_length) {
+ r->headers_out.content_length->hash = 0;
+ r->headers_out.content_length = NULL;
+ }
+
+ return NGX_OK;
+}
+
+ngx_int_t
+ngx_http_zip_add_full_content_range(ngx_http_request_t *r)
+{
+ ngx_table_elt_t *content_range;
+
+ if (ngx_http_zip_add_content_range_header(r) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ content_range = r->headers_out.content_range;
+ if (content_range == NULL) {
+ return NGX_ERROR;
+ }
+
+ content_range->value.data = ngx_palloc(r->pool,
+ sizeof("bytes */") - 1 + NGX_OFF_T_LEN);
+ if (content_range->value.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ content_range->value.len = ngx_sprintf(content_range->value.data,
+ "bytes */%O", r->headers_out.content_length_n)
+ - content_range->value.data;
+
+ return NGX_OK;
+}
+
+ngx_int_t
+ngx_http_zip_init_multipart_range(ngx_http_request_t *r,
+ ngx_http_zip_ctx_t *ctx)
+{
+ ngx_uint_t i;
+ ngx_http_zip_range_t *range;
+ size_t len, message_len = 0;
+
+ len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
+ + sizeof(CRLF "Content-Type: " NGX_ZIP_MIME_TYPE) - 1
+ + sizeof(CRLF "Content-Range: bytes ") - 1
+ + sizeof(/* start */ "-" /* end */ "/" /* size */ CRLF CRLF) - 1
+ + 3 * NGX_OFF_T_LEN;
+
+ ctx->boundary = ngx_next_temp_number(0);
+
+ r->headers_out.content_type.data = ngx_palloc(r->pool,
+ sizeof("Content-Type: multipart/byteranges; boundary=") - 1
+ + NGX_ATOMIC_T_LEN);
+ if (r->headers_out.content_type.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->headers_out.content_type.len =
+ ngx_sprintf(r->headers_out.content_type.data,
+ "multipart/byteranges; boundary=%0muA", ctx->boundary)
+ - r->headers_out.content_type.data;
+
+ for (i=0; i < ctx->ranges.nelts; i++) {
+ range = &((ngx_http_zip_range_t *)ctx->ranges.elts)[i];
+ range->boundary_header.data = ngx_palloc(r->pool, len);
+ if (range->boundary_header.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ range->boundary_header.len = ngx_sprintf(range->boundary_header.data,
+ CRLF "--%0muA" CRLF
+ "Content-Type: " NGX_ZIP_MIME_TYPE CRLF
+ "Content-Range: bytes %O-%O/%O" CRLF CRLF,
+ ctx->boundary, range->start, range->end - 1, ctx->archive_size)
+ - range->boundary_header.data;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: Allocating boundary for range start=%O end=%O (size %d)",
+ range->start, range->end, range->boundary_header.len);
+
+ message_len += range->boundary_header.len + (range->end - range->start);
+ }
+
+ message_len += sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof("--" CRLF) - 1;
+
+ r->headers_out.content_length_n = message_len;
+
+ return NGX_OK;
+}
+
+ngx_int_t
+ngx_http_zip_add_partial_content_range(ngx_http_request_t *r,
+ ngx_http_zip_ctx_t *ctx)
+{
+ ngx_table_elt_t *content_range;
+ ngx_http_zip_range_t *range;
+
+ if (ngx_http_zip_add_content_range_header(r) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ content_range = r->headers_out.content_range;
+
+ range = &((ngx_http_zip_range_t *)ctx->ranges.elts)[0];
+
+ if (content_range == NULL) {
+ return NGX_ERROR;
+ }
+
+ content_range->value.data = ngx_palloc(r->pool,
+ sizeof("bytes " /* start */ "-" /* end */ "/" /* total */) - 1
+ + 3 * NGX_OFF_T_LEN);
+ if (content_range->value.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ content_range->value.len = ngx_sprintf(content_range->value.data,
+ "bytes %O-%O/%O", range->start, range->end - 1,
+ r->headers_out.content_length_n)
+ - content_range->value.data;
+
+ r->headers_out.content_length_n = range->end - range->start;
+
+ return NGX_OK;
+}
+
+ngx_int_t
+ngx_http_zip_strip_range_header(ngx_http_request_t *r)
+{
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header;
+
+ part = &r->headers_in.headers.part;
+ header = r->headers_in.range;
+
+ if (header) {
+ header->key.data = (u_char *)"X-Range";
+ header->key.len = sizeof("X-Range") - 1;
+ header->lowcase_key = (u_char *)"x-range";
+ }
+
+ return NGX_OK;
+}
+
12 ngx_http_zip_headers.h
@@ -0,0 +1,12 @@
+ngx_int_t ngx_http_zip_strip_range_header(ngx_http_request_t *r);
+ngx_int_t ngx_http_zip_add_cache_control(ngx_http_request_t *r);
+ngx_int_t ngx_http_zip_set_range_header(ngx_http_request_t *r,
+ ngx_http_zip_range_t *piece_range, ngx_http_zip_range_t *range);
+ngx_int_t ngx_http_zip_add_content_range_header(ngx_http_request_t *r);
+ngx_int_t ngx_http_zip_add_full_content_range(ngx_http_request_t *r);
+ngx_int_t ngx_http_zip_add_partial_content_range(ngx_http_request_t *r,
+ ngx_http_zip_ctx_t *ctx);
+
+ngx_int_t ngx_http_zip_init_multipart_range(ngx_http_request_t *r,
+ ngx_http_zip_ctx_t *ctx);
+
770 ngx_http_zip_module.c
@@ -0,0 +1,770 @@
+/*
+ * mod_zip
+ *
+ * Copyright (C) Evan Miller
+ *
+ * This module may be distributed under the same terms as Nginx itself.
+ */
+
+#include "ngx_http_zip_module.h"
+#include "ngx_http_zip_parsers.h"
+#include "ngx_http_zip_file.h"
+#include "ngx_http_zip_headers.h"
+
+static size_t ngx_chain_length(ngx_chain_t *chain_link);
+static ngx_chain_t *ngx_chain_last_link(ngx_chain_t *chain_link);
+static ngx_int_t ngx_http_zip_discard_chain(ngx_http_request_t *r,
+ ngx_chain_t *in);
+
+static ngx_int_t ngx_http_zip_ranges_intersect(ngx_http_zip_range_t *range1,
+ ngx_http_zip_range_t *range2);
+
+static ngx_int_t ngx_http_zip_copy_unparsed_request(ngx_http_request_t *r,
+ ngx_chain_t *in, ngx_http_zip_ctx_t *ctx);
+static ngx_int_t ngx_http_zip_set_headers(ngx_http_request_t *r,
+ ngx_http_zip_ctx_t *ctx);
+
+static ngx_int_t ngx_http_zip_header_filter(ngx_http_request_t *r);
+static ngx_int_t ngx_http_zip_body_filter(ngx_http_request_t *r,
+ ngx_chain_t *in);
+static ngx_int_t ngx_http_zip_main_request_body_filter(ngx_http_request_t *r,
+ ngx_chain_t *in);
+static ngx_int_t ngx_http_zip_subrequest_body_filter(ngx_http_request_t *r,
+ ngx_chain_t *in);
+
+static ngx_chain_t* ngx_http_zip_subrequest_range(ngx_http_request_t *r, ngx_chain_t *in,
+ ngx_http_zip_sr_ctx_t *sr_ctx);
+static ngx_int_t ngx_http_zip_subrequest_update_crc32(ngx_chain_t *in,
+ ngx_http_zip_file_t *file);
+
+static ngx_int_t ngx_http_zip_send_pieces(ngx_http_request_t *r,
+ ngx_http_zip_ctx_t *ctx);
+static ngx_int_t ngx_http_zip_send_piece(ngx_http_request_t *r,
+ ngx_http_zip_ctx_t *ctx, ngx_http_zip_piece_t *piece,
+ ngx_http_zip_range_t *range);
+
+static ngx_int_t ngx_http_zip_send_boundary(ngx_http_request_t *r,
+ ngx_http_zip_ctx_t *ctx, ngx_http_zip_range_t *range);
+static ngx_int_t ngx_http_zip_send_final_boundary(ngx_http_request_t *r,
+ ngx_http_zip_ctx_t *ctx);
+
+static ngx_int_t ngx_http_zip_init(ngx_conf_t *cf);
+static ngx_int_t ngx_http_zip_init_crc32(ngx_array_t *files);
+
+static ngx_int_t ngx_http_zip_main_request_header_filter(ngx_http_request_t *r);
+static ngx_int_t ngx_http_zip_subrequest_header_filter(ngx_http_request_t *r);
+
+static ngx_str_t ngx_http_zip_header_variable_name = ngx_string("upstream_http_x_archive_files");
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
+
+static ngx_http_module_t ngx_http_zip_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_zip_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+ngx_module_t ngx_http_zip_module = {
+ NGX_MODULE_V1,
+ &ngx_http_zip_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+static size_t ngx_chain_length(ngx_chain_t *chain_link)
+{
+ size_t len;
+ for (len=0; chain_link; chain_link = chain_link->next) {
+ len += chain_link->buf->last - chain_link->buf->pos;
+ }
+ return len;
+}
+
+static ngx_chain_t *ngx_chain_last_link(ngx_chain_t *chain_link)
+{
+ ngx_chain_t *cl;
+ for (cl = chain_link; cl->next; cl = cl->next) {
+ /* void */
+ }
+ return cl;
+}
+
+static ngx_int_t
+ngx_http_zip_discard_chain(ngx_http_request_t *r,
+ ngx_chain_t *in)
+{
+ ngx_chain_t *chain_link;
+
+ for (chain_link = in; chain_link; chain_link = chain_link->next) {
+ chain_link->buf->flush = 1;
+ chain_link->buf->sync = 1;
+ chain_link->buf->temporary = 0;
+ chain_link->buf->memory = 0;
+ chain_link->buf->mmap = 0;
+ chain_link->buf->last = chain_link->buf->pos;
+ }
+
+ return ngx_http_next_body_filter(r, in);
+}
+
+static ngx_int_t
+ngx_http_zip_ranges_intersect(ngx_http_zip_range_t *range1, ngx_http_zip_range_t *range2)
+{
+ return (range1 == NULL) || (range2 == NULL) ||
+ !(range1->start >= range2->end || range2->start >= range1->end);
+}
+
+static ngx_int_t ngx_http_zip_copy_unparsed_request(ngx_http_request_t *r,
+ ngx_chain_t *in, ngx_http_zip_ctx_t *ctx)
+{
+ ngx_str_t *old_unparsed_request;
+ ngx_chain_t *chain_link;
+ size_t len, offset = 0;
+
+ old_unparsed_request = ctx->unparsed_request;
+
+ len = ngx_chain_length(in);
+
+ if (old_unparsed_request != NULL) {
+ len += old_unparsed_request->len;
+ }
+
+ if ((ctx->unparsed_request = ngx_palloc(r->pool, sizeof(ngx_str_t))) == NULL) {
+ return NGX_ERROR;
+ }
+
+ if ((ctx->unparsed_request->data = ngx_palloc(r->pool, len)) == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (old_unparsed_request != NULL) {
+ ngx_memcpy(ctx->unparsed_request->data, old_unparsed_request->data, old_unparsed_request->len);
+ offset += old_unparsed_request->len;
+ }
+
+ for (chain_link = in; chain_link; chain_link = chain_link->next ) {
+ ngx_memcpy(ctx->unparsed_request->data + offset, chain_link->buf->pos,
+ chain_link->buf->last - chain_link->buf->pos);
+ offset += chain_link->buf->last - chain_link->buf->pos;
+ }
+
+ ctx->unparsed_request->len = offset;
+
+ chain_link = ngx_chain_last_link(in);
+
+ return chain_link->buf->last_buf ? NGX_OK: NGX_AGAIN;
+}
+
+/*
+ * The header filter looks for "X-Archive-Files: zip" and allocates
+ * a module context struct if found
+ */
+static ngx_int_t ngx_http_zip_header_filter(ngx_http_request_t *r)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: entering header filter");
+
+ if (r != r->main) {
+ return ngx_http_zip_subrequest_header_filter(r);
+ }
+
+ return ngx_http_zip_main_request_header_filter(r);
+}
+
+static ngx_int_t
+ngx_http_zip_main_request_header_filter(ngx_http_request_t *r)
+{
+ ngx_http_variable_value_t *vv;
+ ngx_http_zip_ctx_t *ctx;
+
+ if ((ctx = ngx_http_get_module_ctx(r, ngx_http_zip_module)) != NULL) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ if ((vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t))) == NULL) {
+ return NGX_ERROR;
+ }
+
+ /* Look for X-Archive-Files */
+ if (ngx_http_upstream_header_variable(r, vv,
+ (uintptr_t)(&ngx_http_zip_header_variable_name)) != NGX_OK
+ || vv->not_found
+ || ngx_strncmp(vv->data, "zip", sizeof("zip") - 1) != 0)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: X-Archive-Files found");
+
+ if ((ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_zip_ctx_t))) == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_array_init(&ctx->files, r->pool, 1, sizeof(ngx_http_zip_file_t))
+ == NGX_ERROR)
+ {
+ return NGX_ERROR;
+ }
+
+ if (ngx_array_init(&ctx->ranges, r->pool, 1, sizeof(ngx_http_zip_range_t))
+ == NGX_ERROR)
+ {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_zip_module);
+
+ return NGX_OK;
+}
+
+static ngx_int_t
+ngx_http_zip_subrequest_header_filter(ngx_http_request_t *r)
+{
+ ngx_http_zip_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r->main, ngx_http_zip_module);
+ if (ctx != NULL) {
+ if (r->headers_out.status != NGX_HTTP_OK
+ && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "mod_zip: a subrequest returned %d, aborting...",
+ r->headers_out.status);
+ ctx->abort = 1;
+ return NGX_ERROR;
+ }
+ if (ctx->missing_crc32) {
+ r->filter_need_in_memory = 1;
+ }
+ }
+ return ngx_http_next_header_filter(r);
+}
+
+static ngx_int_t
+ngx_http_zip_set_headers(ngx_http_request_t *r, ngx_http_zip_ctx_t *ctx)
+{
+ if (ngx_http_zip_add_cache_control(r) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ r->headers_out.content_type.len = sizeof(NGX_ZIP_MIME_TYPE) - 1;
+ r->headers_out.content_type.data = (u_char *)NGX_ZIP_MIME_TYPE;
+ ngx_http_clear_content_length(r);
+ ngx_http_clear_last_modified(r);
+
+ if (ctx->missing_crc32) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: Clearing Accept-Ranges header");
+ ngx_http_clear_accept_ranges(r);
+ }
+ r->headers_out.content_length_n = ctx->archive_size;
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: Archive will be %O bytes", ctx->archive_size);
+ if (r->headers_in.range && !r->headers_in.if_range) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: Range found");
+ if (ctx->missing_crc32) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: Missing checksums, ignoring Range");
+ return NGX_OK;
+ }
+ if (ngx_http_zip_parse_range(r, &r->headers_in.range->value, ctx)
+ == NGX_ERROR) {
+ r->headers_out.status = NGX_HTTP_RANGE_NOT_SATISFIABLE;
+ if (ngx_http_zip_add_full_content_range(r) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: Range not satisfiable");
+ ctx->ranges.nelts = 0;
+ return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+ }
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: Range is satisfiable");
+ if (ctx->ranges.nelts == 1) {
+ if (ngx_http_zip_add_partial_content_range(r, ctx) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ } else {
+ if (ngx_http_zip_init_multipart_range(r, ctx) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ }
+ r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
+ r->headers_out.status_line.len = 0;
+ }
+
+ return NGX_OK;
+}
+
+static ngx_int_t
+ngx_http_zip_body_filter(ngx_http_request_t *r,
+ ngx_chain_t *in)
+{
+ if (r != r->main) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: entering subrequest body filter");
+ return ngx_http_zip_subrequest_body_filter(r, in);
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: entering main request body filter");
+ return ngx_http_zip_main_request_body_filter(r, in);
+}
+
+static ngx_int_t
+ngx_http_zip_subrequest_body_filter(ngx_http_request_t *r,
+ ngx_chain_t *in)
+{
+ ngx_http_zip_ctx_t *ctx;
+ ngx_http_zip_sr_ctx_t *sr_ctx;
+ ngx_chain_t *out;
+
+ ctx = ngx_http_get_module_ctx(r->main, ngx_http_zip_module);
+ sr_ctx = ngx_http_get_module_ctx(r, ngx_http_zip_module);
+
+ if (ctx == NULL || sr_ctx == NULL || in == NULL) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ if (sr_ctx->range != NULL) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: cutting subrequest to fit range");
+ if ((out = ngx_http_zip_subrequest_range(r, in, sr_ctx)) == NULL) {
+ return NGX_OK;
+ }
+ return ngx_http_next_body_filter(r, out);
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: No range for subrequest to satisfy");
+
+ if (ctx->missing_crc32) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: updating CRC-32");
+ ngx_http_zip_subrequest_update_crc32(in, sr_ctx->requesting_file);
+ }
+
+ return ngx_http_next_body_filter(r, in);
+}
+
+/* more or less copied from ngx_http_range_filter_module.c */
+static ngx_chain_t*
+ngx_http_zip_subrequest_range(ngx_http_request_t *r, ngx_chain_t *in,
+ ngx_http_zip_sr_ctx_t *sr_ctx)
+{
+ ngx_chain_t *out, *cl, **ll;
+ ngx_buf_t *buf;
+ ngx_http_zip_range_t *range;
+ off_t start, last;
+
+ range = sr_ctx->range;
+
+ out = NULL;
+ ll = &out;
+
+ for (cl = in; cl != NULL; cl = cl->next) {
+ buf = cl->buf;
+
+ start = sr_ctx->subrequest_pos;
+ last = sr_ctx->subrequest_pos + ngx_buf_size(buf);
+
+ sr_ctx->subrequest_pos = last;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: Buffer range %O-%O Request range %O-%O", start, last, range->start, range->end);
+
+ if (range->end <= start || range->start >= last) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: range body skip");
+
+ if (buf->in_file) {
+ buf->file_pos = buf->file_last;
+ }
+
+ buf->pos = buf->last;
+ buf->sync = 1;
+
+ continue;
+ }
+
+ if (range->start > start) {
+ if (buf->in_file) {
+ buf->file_pos += range->start - start;
+ }
+
+ if (ngx_buf_in_memory(buf)) {
+ buf->pos += (size_t) (range->start - start);
+ }
+ }
+
+ if (range->end <= last) {
+ if (buf->in_file) {
+ buf->file_last -= last - range->end;
+ }
+
+ if (ngx_buf_in_memory(buf)) {
+ buf->last -= (size_t) (last - range->end);
+ }
+
+ buf->last_buf = 1;
+ *ll = cl;
+ cl->next = NULL;
+
+ break;
+ }
+
+ *ll = cl;
+ ll = &cl->next;
+ }
+
+ return out;
+}
+
+static ngx_int_t
+ngx_http_zip_subrequest_update_crc32(ngx_chain_t *in,
+ ngx_http_zip_file_t *file)
+{
+ ngx_chain_t *cl;
+ size_t len;
+ u_char *p;
+
+ if (file == NULL) {
+ return NGX_ERROR;
+ }
+
+ for (cl = in; cl != NULL; cl = cl->next) {
+ p = cl->buf->pos;
+ len = cl->buf->last - p;
+ while(len--) {
+ file->crc32 = ngx_crc32_table256[(file->crc32 ^ *p++) & 0xff]
+ ^ (file->crc32 >> 8);
+ }
+ }
+
+ return NGX_OK;
+}
+
+static ngx_int_t
+ngx_http_zip_main_request_body_filter(ngx_http_request_t *r,
+ ngx_chain_t *in)
+{
+ ngx_http_zip_ctx_t *ctx;
+ ngx_chain_t *chain_link;
+ int rc;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_zip_module);
+
+ if (ctx == NULL || ctx->trailer_sent) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ if (ctx->abort) {
+ return NGX_ERROR;
+ }
+
+ if (r->headers_out.status != NGX_HTTP_OK &&
+ r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ if (ctx->parsed) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: restarting subrequests");
+ return ngx_http_zip_send_pieces(r, ctx);
+ }
+
+ if (in == NULL) {
+ return ngx_http_next_body_filter(r, NULL);
+ }
+
+ rc = ngx_http_zip_copy_unparsed_request(r, in, ctx);
+ if (rc == NGX_AGAIN) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: not the last buf");
+ return ngx_http_zip_discard_chain(r, in);
+ } else if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: about to parse list");
+
+ if (ngx_http_zip_parse_request(ctx) == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "mod_zip: invalid file list from upstream");
+ return NGX_ERROR;
+ }
+
+ if (ctx->missing_crc32) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: initializing CRC-32");
+ if (ngx_http_zip_init_crc32(&ctx->files) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ }
+
+ if (ngx_http_zip_generate_pieces(r, ctx) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (!r->header_sent) {
+ rc = ngx_http_zip_set_headers(r, ctx);
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ if (rc == NGX_HTTP_RANGE_NOT_SATISFIABLE) {
+ return ngx_http_special_response_handler(r, rc);
+ }
+ if ((rc = ngx_http_send_header(r)) != NGX_OK) {
+ return rc;
+ }
+ }
+
+ chain_link = ngx_chain_last_link(in);
+ chain_link->buf->last_buf = 0;
+
+ if (ngx_http_zip_strip_range_header(r) == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "mod_zip: failed to strip Range: header from request");
+ return NGX_ERROR;
+ }
+
+ return ngx_http_zip_send_pieces(r, ctx);
+}
+
+
+static ngx_int_t
+ngx_http_zip_init_crc32(ngx_array_t *files)
+{
+ ngx_uint_t i;
+ ngx_http_zip_file_t *file;
+
+ if (files == NULL) {
+ return NGX_ERROR;
+ }
+ for (i=0; i<files->nelts; i++) {
+ file = &((ngx_http_zip_file_t *)files->elts)[i];
+ file->crc32 = 0xffffffff;
+ }
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_zip_send_piece(ngx_http_request_t *r, ngx_http_zip_ctx_t *ctx,
+ ngx_http_zip_piece_t *piece, ngx_http_zip_range_t *range)
+{
+ ngx_chain_t *link;
+ ngx_http_request_t *sr;
+ ngx_http_zip_sr_ctx_t *sr_ctx;
+ ngx_int_t rc;
+
+ if (piece->type == zip_header_piece) {
+ if ((link = ngx_http_zip_file_header_chain_link(r, ctx, piece, range)) == NULL)
+ return NGX_ERROR;
+ return ngx_http_next_body_filter(r, link);
+ }
+
+ if (piece->type == zip_file_piece) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: subrequest for \"%V?%V\"",
+ &piece->file->uri, &piece->file->args);
+ rc = ngx_http_subrequest(r, &piece->file->uri,
+ &piece->file->args, &sr, NULL, 0);
+ if ((sr_ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_zip_ctx_t))) == NULL) {
+ return NGX_ERROR;
+ }
+ sr_ctx->requesting_file = piece->file;
+ sr_ctx->subrequest_pos = piece->range.start;
+ sr_ctx->range = range;
+ ngx_http_set_ctx(sr, sr_ctx, ngx_http_zip_module);
+ return rc;
+ }
+
+ if (piece->type == zip_trailer_piece) {
+ if (ctx->missing_crc32) {
+ piece->file->crc32 ^= 0xffffffff;
+ } else { /* no trailer piece if we already had the CRC-32s */
+ return NGX_OK;
+ }
+ if ((link = ngx_http_zip_data_descriptor_chain_link(r, piece, range)) == NULL)
+ return NGX_ERROR;
+ return ngx_http_next_body_filter(r, link);
+ }
+
+ if (piece->type == zip_central_directory_piece) {
+ if ((link = ngx_http_zip_central_directory_chain_link(
+ r, ctx, piece, range)) == NULL)
+ return NGX_ERROR;
+ return ngx_http_next_body_filter(r, link);
+ }
+ return NGX_ERROR;
+}
+
+static ngx_int_t
+ngx_http_zip_send_boundary(ngx_http_request_t *r, ngx_http_zip_ctx_t *ctx,
+ ngx_http_zip_range_t *range)
+{
+ ngx_chain_t *link;
+ ngx_buf_t *b;
+
+ if (range->boundary_sent) {
+ return NGX_OK;
+ }
+
+ if ((link = ngx_alloc_chain_link(r->pool)) == NULL) {
+ return NGX_ERROR;
+ }
+
+ if ((b = ngx_calloc_buf(r->pool)) == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->memory = 1;
+ b->pos = range->boundary_header.data;
+ b->last = b->pos + range->boundary_header.len;
+
+ link->buf = b;
+ link->next = NULL;
+
+ range->boundary_sent = 1;
+ return ngx_http_next_body_filter(r, link);
+}
+
+static ngx_int_t
+ngx_http_zip_send_final_boundary(ngx_http_request_t *r,
+ ngx_http_zip_ctx_t *ctx)
+{
+ size_t len;
+ ngx_chain_t *link;
+ ngx_buf_t *b;
+
+ if ((link = ngx_alloc_chain_link(r->pool)) == NULL) {
+ return NGX_ERROR;
+ }
+
+ if ((b = ngx_calloc_buf(r->pool)) == NULL) {
+ return NGX_ERROR;
+ }
+
+ len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof("--" CRLF) - 1;
+
+ b->memory = 1;
+ b->pos = ngx_palloc(r->pool, len);
+ if (b->pos == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->last = ngx_sprintf(b->pos,
+ CRLF "--%0muA--" CRLF, ctx->boundary);
+
+ link->buf = b;
+ link->next = NULL;
+
+ return ngx_http_next_body_filter(r, link);
+}
+
+/* Initiate one or more subrequests for files to put in the ZIP archive */
+static ngx_int_t
+ngx_http_zip_send_pieces(ngx_http_request_t *r,
+ ngx_http_zip_ctx_t *ctx)
+{
+ ngx_int_t rc = NGX_OK, pieces_sent = 0;
+ ngx_http_zip_piece_t *piece;
+ ngx_http_zip_range_t *range = NULL;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: sending pieces, starting with piece %d", ctx->pieces_i);
+
+ switch(ctx->ranges.nelts) {
+ case 0:
+ while (rc == NGX_OK && ctx->pieces_i < ctx->pieces_n) {
+ piece = &ctx->pieces[ctx->pieces_i++];
+ pieces_sent++;
+ rc = ngx_http_zip_send_piece(r, ctx, piece, NULL);
+ }
+ break;
+ case 1:
+ range = &((ngx_http_zip_range_t *)ctx->ranges.elts)[0];
+ while (rc == NGX_OK && ctx->pieces_i < ctx->pieces_n) {
+ piece = &ctx->pieces[ctx->pieces_i++];
+ if (ngx_http_zip_ranges_intersect(&piece->range, range)) {
+ pieces_sent++;
+ rc = ngx_http_zip_send_piece(r, ctx, piece, range);
+ }
+ }
+ break;
+ default:
+ while (rc == NGX_OK && ctx->ranges_i < ctx->ranges.nelts) {
+ range = &((ngx_http_zip_range_t *)ctx->ranges.elts)[ctx->ranges_i];
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: sending range #%d start=%O end=%O (size %d)",
+ ctx->ranges_i, range->start, range->end, range->boundary_header.len);
+ rc = ngx_http_zip_send_boundary(r, ctx, range);
+ while (rc == NGX_OK && ctx->pieces_i < ctx->pieces_n) {
+ piece = &ctx->pieces[ctx->pieces_i++];
+ if (ngx_http_zip_ranges_intersect(&piece->range, range)) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: sending range=%d piece=%d",
+ ctx->ranges_i, pieces_sent);
+ pieces_sent++;
+ rc = ngx_http_zip_send_piece(r, ctx, piece, range);
+ }
+ }
+
+ if (rc == NGX_OK) {
+ ctx->ranges_i++;
+ ctx->pieces_i = 0;
+ }
+ }
+
+ if (rc == NGX_OK) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: sending final boundary");
+ rc = ngx_http_zip_send_final_boundary(r, ctx);
+ }
+ break;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "mod_zip: sent %d pieces, last rc = %d", pieces_sent, rc);
+
+ if (rc == NGX_OK) {
+ ctx->trailer_sent = 1;
+ return ngx_http_send_special(r, NGX_HTTP_LAST);
+ }
+
+ /* NGX_DONE, NGX_AGAIN or NGX_ERROR */
+ return rc;
+}
+
+/* Install the module filters */
+static ngx_int_t
+ngx_http_zip_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_zip_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_zip_body_filter;
+
+ return NGX_OK;
+}
80 ngx_http_zip_module.h
@@ -0,0 +1,80 @@
+/* data structures and useful byte arrays */
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <time.h>
+
+#define NGX_ZIP_MIME_TYPE "application/zip"
+#define ngx_http_zip_current_file(ctx) ctx->pieces[ctx->pieces_i].file
+
+extern uint32_t ngx_crc32_table256[];
+
+typedef struct {
+ ngx_uint_t crc32;
+ ngx_str_t uri;
+ ngx_str_t args;
+ ngx_uint_t index;
+ ngx_uint_t dos_time;
+ ngx_uint_t unix_time;
+ ngx_str_t filename;
+ ngx_uint_t size;
+ size_t offset;
+
+ unsigned header_sent:1;
+ unsigned trailer_sent:1;
+} ngx_http_zip_file_t;
+
+typedef struct {
+ off_t start;
+ off_t end;
+ ngx_str_t boundary_header;
+
+ unsigned boundary_sent:1;
+} ngx_http_zip_range_t;
+
+typedef struct {
+ ngx_http_zip_range_t range;
+ ngx_http_zip_file_t *file;
+ ngx_int_t type;
+} ngx_http_zip_piece_t;
+
+typedef struct {
+ ngx_str_t *unparsed_request;
+ ngx_http_zip_piece_t *pieces;
+ ngx_array_t files;
+ ngx_array_t ranges;
+ ngx_uint_t ranges_i;
+ ngx_uint_t pieces_i;
+ ngx_uint_t pieces_n;
+ ngx_atomic_uint_t boundary;
+ off_t archive_size;
+
+ unsigned parsed:1;
+ unsigned trailer_sent:1;
+ unsigned abort:1;
+ unsigned missing_crc32:1;
+ unsigned missing_size:1;
+} ngx_http_zip_ctx_t;
+
+typedef struct {
+ ngx_http_zip_file_t *requesting_file;
+ ngx_http_zip_range_t *range;
+ off_t subrequest_pos;
+} ngx_http_zip_sr_ctx_t;
+
+typedef enum {
+ zip_start_state = 0,
+ zip_filename_state,
+ zip_size_state,
+ zip_uri_state,
+ zip_args_state,
+ zip_eol_state
+} ngx_http_zip_state_e;
+
+typedef enum {
+ zip_header_piece,
+ zip_file_piece,
+ zip_trailer_piece,
+ zip_central_directory_piece
+} ngx_http_zip_piece_e;
501 ngx_http_zip_parsers.c
@@ -0,0 +1,501 @@
+#line 1 "ngx_http_zip_parsers.rl"
+/* Parser definitions for mod_zip */
+
+#include "ngx_http_zip_module.h"
+#include "ngx_http_zip_parsers.h"
+
+static void
+ngx_http_zip_file_init(ngx_http_zip_file_t *parsing_file)
+{
+ parsing_file->uri.data = NULL;
+ parsing_file->uri.len = 0;
+
+ parsing_file->args.data = NULL;
+ parsing_file->args.len = 0;
+
+ parsing_file->filename.data = NULL;
+ parsing_file->filename.len = 0;
+
+ parsing_file->header_sent = 0;
+ parsing_file->trailer_sent = 0;
+
+ parsing_file->crc32 = 0;
+ parsing_file->size = 0;
+}
+
+static ngx_int_t
+ngx_http_zip_clean_range(ngx_http_zip_range_t *range,
+ int prefix, int suffix, ngx_http_zip_ctx_t *ctx)
+{
+ if (suffix) {
+ range->end = ctx->archive_size;
+ range->start = ctx->archive_size - range->start;
+ } else if (prefix) {
+ range->end = ctx->archive_size;
+ } else {
+ range->end++;
+ /*
+ * Download Accelerator sends the last byte position
+ * that equals to the file length
+ */
+ if (range->end >= ctx->archive_size) {
+ range->end = ctx->archive_size;
+ }
+ }
+ if (range->start < 0) {
+ return NGX_ERROR;
+ }
+ if (range->start >= ctx->archive_size) {
+ return NGX_ERROR;
+ }
+ return NGX_OK;
+}
+
+
+#line 56 "ngx_http_zip_parsers.c"
+static const char _request_actions[] = {
+ 0, 1, 1, 1, 2, 1, 3, 1,
+ 4, 1, 5, 1, 6, 1, 7, 1,
+ 8, 2, 0, 6
+};
+
+static const char _request_key_offsets[] = {
+ 0, 0, 7, 8, 11, 14, 16, 18,
+ 19, 22, 25, 32, 33, 34
+};
+
+static const char _request_trans_keys[] = {
+ 45, 48, 57, 65, 70, 97, 102, 32,
+ 32, 48, 57, 32, 48, 57, 32, 63,
+ 32, 63, 32, 0, 10, 13, 0, 10,
+ 13, 32, 48, 57, 65, 70, 97, 102,
+ 32, 32, 10, 13, 45, 48, 57, 65,
+ 70, 97, 102, 0
+};
+
+static const char _request_single_lengths[] = {
+ 0, 1, 1, 1, 1, 2, 2, 1,
+ 3, 3, 1, 1, 1, 3
+};
+
+static const char _request_range_lengths[] = {
+ 0, 3, 0, 1, 1, 0, 0, 0,
+ 0, 0, 3, 0, 0, 3
+};
+
+static const char _request_index_offsets[] = {
+ 0, 0, 5, 7, 10, 13, 16, 19,
+ 21, 25, 29, 34, 36, 38
+};
+
+static const char _request_indicies[] = {
+ 0, 2, 2, 2, 1, 3, 1, 3,
+ 4, 1, 5, 4, 1, 5, 1, 6,
+ 8, 9, 7, 11, 10, 1, 1, 1,
+ 12, 1, 13, 13, 12, 3, 14, 14,
+ 14, 1, 1, 15, 17, 16, 18, 18,
+ 0, 2, 2, 2, 1, 0
+};
+
+static const char _request_trans_targs[] = {
+ 2, 0, 10, 3, 4, 5, 6, 6,
+ 7, 11, 8, 7, 9, 13, 10, 12,
+ 12, 7, 13
+};
+
+static const char _request_trans_actions[] = {
+ 17, 0, 17, 0, 9, 0, 1, 0,
+ 3, 3, 13, 0, 0, 15, 11, 5,
+ 0, 7, 0
+};
+
+static const int request_start = 1;
+static const int request_first_final = 13;
+static const int request_error = 0;
+
+static const int request_en_main = 1;
+
+#line 56 "ngx_http_zip_parsers.rl"
+
+
+ngx_int_t
+ngx_http_zip_parse_request(ngx_http_zip_ctx_t *ctx)
+{
+ int cs;
+ u_char *p = ctx->unparsed_request->data;
+ u_char *pe = ctx->unparsed_request->data + ctx->unparsed_request->len;
+ ngx_http_zip_file_t *parsing_file = NULL;
+
+
+#line 131 "ngx_http_zip_parsers.c"
+ {
+ cs = request_start;
+ }
+
+#line 136 "ngx_http_zip_parsers.c"
+ {
+ int _klen;
+ unsigned int _trans;
+ const char *_acts;
+ unsigned int _nacts;
+ const char *_keys;
+
+ if ( p == pe )
+ goto _test_eof;
+ if ( cs == 0 )
+ goto _out;
+_resume:
+ _keys = _request_trans_keys + _request_key_offsets[cs];
+ _trans = _request_index_offsets[cs];
+
+ _klen = _request_single_lengths[cs];
+ if ( _klen > 0 ) {
+ const char *_lower = _keys;
+ const char *_mid;
+ const char *_upper = _keys + _klen - 1;
+ while (1) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + ((_upper-_lower) >> 1);
+ if ( (*p) < *_mid )
+ _upper = _mid - 1;
+ else if ( (*p) > *_mid )
+ _lower = _mid + 1;
+ else {
+ _trans += (_mid - _keys);
+ goto _match;
+ }
+ }
+ _keys += _klen;
+ _trans += _klen;
+ }
+
+ _klen = _request_range_lengths[cs];
+ if ( _klen > 0 ) {
+ const char *_lower = _keys;
+ const char *_mid;
+ const char *_upper = _keys + (_klen<<1) - 2;
+ while (1) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + (((_upper-_lower) >> 1) & ~1);
+ if ( (*p) < _mid[0] )
+ _upper = _mid - 2;
+ else if ( (*p) > _mid[1] )
+ _lower = _mid + 2;
+ else {
+ _trans += ((_mid - _keys)>>1);
+ goto _match;
+ }
+ }
+ _trans += _klen;
+ }
+
+_match:
+ _trans = _request_indicies[_trans];
+ cs = _request_trans_targs[_trans];
+
+ if ( _request_trans_actions[_trans] == 0 )
+ goto _again;
+
+ _acts = _request_actions + _request_trans_actions[_trans];
+ _nacts = (unsigned int) *_acts++;
+ while ( _nacts-- > 0 )
+ {
+ switch ( *_acts++ )
+ {
+ case 0:
+#line 68 "ngx_http_zip_parsers.rl"
+ {
+ parsing_file = ngx_array_push(&ctx->files);
+ ngx_http_zip_file_init(parsing_file);
+
+ parsing_file->index = ctx->files.nelts - 1;
+ }
+ break;
+ case 1:
+#line 75 "ngx_http_zip_parsers.rl"
+ {
+ parsing_file->uri.data = p;
+ parsing_file->uri.len = 1;
+ }
+ break;
+ case 2:
+#line 80 "ngx_http_zip_parsers.rl"
+ {
+ parsing_file->uri.len = p - parsing_file->uri.data;
+ }
+ break;
+ case 3:
+#line 83 "ngx_http_zip_parsers.rl"
+ {
+ parsing_file->args.data = p;
+ }
+ break;
+ case 4:
+#line 86 "ngx_http_zip_parsers.rl"
+ {
+ parsing_file->args.len = p - parsing_file->args.data;
+ }
+ break;
+ case 5:
+#line 89 "ngx_http_zip_parsers.rl"
+ {
+ parsing_file->size = parsing_file->size * 10 + ((*p) - '0');
+ }
+ break;
+ case 6:
+#line 92 "ngx_http_zip_parsers.rl"
+ {
+ if ((*p) == '-') {
+ ctx->missing_crc32 = 1;
+ } else {
+ parsing_file->crc32 *= 16;
+ if ((*p) >= 'a' && (*p) <= 'f') {
+ parsing_file->crc32 += (*p) - 'a' + 10;
+ }
+ else if ((*p) >= 'A' && (*p) <= 'F') {
+ parsing_file->crc32 += (*p) - 'A' + 10;
+ } else { /* 0-9 */
+ parsing_file->crc32 += (*p) - '0';
+ }
+ }
+ }
+ break;
+ case 7:
+#line 107 "ngx_http_zip_parsers.rl"
+ {
+ parsing_file->filename.data = p;
+ }
+ break;
+ case 8:
+#line 110 "ngx_http_zip_parsers.rl"
+ {
+ parsing_file->filename.len = p - parsing_file->filename.data;
+ }
+ break;
+#line 280 "ngx_http_zip_parsers.c"
+ }
+ }
+
+_again:
+ if ( cs == 0 )
+ goto _out;
+ if ( ++p != pe )
+ goto _resume;
+ _test_eof: {}
+ _out: {}
+ }
+#line 129 "ngx_http_zip_parsers.rl"
+
+
+ if (cs < request_first_final) {
+ return NGX_ERROR;
+ }
+
+ ctx->parsed = 1;
+
+ return NGX_OK;
+}
+
+
+#line 305 "ngx_http_zip_parsers.c"
+static const char _range_actions[] = {
+ 0, 1, 0, 1, 1, 1, 2, 2,
+ 0, 1, 2, 3, 1
+};
+
+static const char _range_key_offsets[] = {
+ 0, 0, 1, 2, 3, 4, 5, 6,
+ 9, 11, 14, 17
+};
+
+static const char _range_trans_keys[] = {
+ 98, 121, 116, 101, 115, 61, 45, 48,
+ 57, 48, 57, 45, 48, 57, 44, 48,
+ 57, 44, 48, 57, 0
+};
+
+static const char _range_single_lengths[] = {
+ 0, 1, 1, 1, 1, 1, 1, 1,
+ 0, 1, 1, 1
+};
+
+static const char _range_range_lengths[] = {
+ 0, 0, 0, 0, 0, 0, 0, 1,
+ 1, 1, 1, 1
+};
+
+static const char _range_index_offsets[] = {
+ 0, 0, 2, 4, 6, 8, 10, 12,
+ 15, 17, 20, 23
+};
+
+static const char _range_trans_targs[] = {
+ 2, 0, 3, 0, 4, 0, 5, 0,
+ 6, 0, 7, 0, 8, 9, 0, 10,
+ 0, 11, 9, 0, 7, 10, 0, 7,
+ 11, 0, 0
+};
+
+static const char _range_trans_actions[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 7, 0, 10,
+ 0, 0, 3, 0, 0, 3, 0, 0,
+ 5, 0, 0
+};
+
+static const int range_start = 1;
+static const int range_first_final = 10;
+static const int range_error = 0;
+
+static const int range_en_main = 1;
+
+#line 143 "ngx_http_zip_parsers.rl"
+
+
+ngx_int_t
+ngx_http_zip_parse_range(ngx_http_request_t *r, ngx_str_t *range_str, ngx_http_zip_ctx_t *ctx)
+{
+ int cs, prefix = 0, suffix = 0;
+
+ ngx_http_zip_range_t *range = NULL;
+ u_char *p = range_str->data;
+ u_char *pe = range_str->data + range_str->len;
+
+
+#line 370 "ngx_http_zip_parsers.c"
+ {
+ cs = range_start;
+ }
+
+#line 375 "ngx_http_zip_parsers.c"
+ {
+ int _klen;
+ unsigned int _trans;
+ const char *_acts;
+ unsigned int _nacts;
+ const char *_keys;
+
+ if ( p == pe )
+ goto _test_eof;
+ if ( cs == 0 )
+ goto _out;
+_resume:
+ _keys = _range_trans_keys + _range_key_offsets[cs];
+ _trans = _range_index_offsets[cs];
+
+ _klen = _range_single_lengths[cs];
+ if ( _klen > 0 ) {
+ const char *_lower = _keys;
+ const char *_mid;
+ const char *_upper = _keys + _klen - 1;
+ while (1) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + ((_upper-_lower) >> 1);
+ if ( (*p) < *_mid )
+ _upper = _mid - 1;
+ else if ( (*p) > *_mid )
+ _lower = _mid + 1;
+ else {
+ _trans += (_mid - _keys);
+ goto _match;
+ }
+ }
+ _keys += _klen;
+ _trans += _klen;
+ }
+
+ _klen = _range_range_lengths[cs];
+ if ( _klen > 0 ) {
+ const char *_lower = _keys;
+ const char *_mid;
+ const char *_upper = _keys + (_klen<<1) - 2;
+ while (1) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + (((_upper-_lower) >> 1) & ~1);
+ if ( (*p) < _mid[0] )
+ _upper = _mid - 2;
+ else if ( (*p) > _mid[1] )
+ _lower = _mid + 2;
+ else {
+ _trans += ((_mid - _keys)>>1);
+ goto _match;
+ }
+ }
+ _trans += _klen;
+ }
+
+_match:
+ cs = _range_trans_targs[_trans];
+
+ if ( _range_trans_actions[_trans] == 0 )
+ goto _again;
+
+ _acts = _range_actions + _range_trans_actions[_trans];
+ _nacts = (unsigned int) *_acts++;
+ while ( _nacts-- > 0 )
+ {
+ switch ( *_acts++ )
+ {
+ case 0:
+#line 155 "ngx_http_zip_parsers.rl"
+ {
+ if (range) {
+ if (ngx_http_zip_clean_range(range, prefix, suffix, ctx) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ }
+ if ((range = ngx_array_push(&ctx->ranges)) == NULL) {
+ return NGX_ERROR;
+ }
+ range->start = 0; range->end = 0; range->boundary_sent = 0;
+ suffix = 0;
+ prefix = 1;
+ }
+ break;
+ case 1:
+#line 169 "ngx_http_zip_parsers.rl"
+ { range->start = range->start * 10 + ((*p) - '0'); }
+ break;
+ case 2:
+#line 171 "ngx_http_zip_parsers.rl"
+ { range->end = range->end * 10 + ((*p) - '0'); prefix = 0; }
+ break;
+ case 3:
+#line 173 "ngx_http_zip_parsers.rl"
+ { suffix = 1; }
+ break;
+#line 476 "ngx_http_zip_parsers.c"
+ }
+ }
+
+_again:
+ if ( cs == 0 )
+ goto _out;
+ if ( ++p != pe )
+ goto _resume;
+ _test_eof: {}
+ _out: {}
+ }
+#line 186 "ngx_http_zip_parsers.rl"
+
+
+ if (cs < range_first_final) {
+ return NGX_ERROR;
+ }
+
+ if (range) {
+ if (ngx_http_zip_clean_range(range, prefix, suffix, ctx) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
4 ngx_http_zip_parsers.h
@@ -0,0 +1,4 @@
+/* Parser functions */
+
+ngx_int_t ngx_http_zip_parse_request(ngx_http_zip_ctx_t *ctx);
+ngx_int_t ngx_http_zip_parse_range(ngx_http_request_t *r, ngx_str_t *range, ngx_http_zip_ctx_t *ctx);
199 ngx_http_zip_parsers.rl
@@ -0,0 +1,199 @@
+/* Parser definitions for mod_zip */
+
+#include "ngx_http_zip_module.h"
+#include "ngx_http_zip_parsers.h"
+
+static void
+ngx_http_zip_file_init(ngx_http_zip_file_t *parsing_file)
+{
+ parsing_file->uri.data = NULL;
+ parsing_file->uri.len = 0;
+
+ parsing_file->args.data = NULL;
+ parsing_file->args.len = 0;
+
+ parsing_file->filename.data = NULL;
+ parsing_file->filename.len = 0;
+
+ parsing_file->header_sent = 0;
+ parsing_file->trailer_sent = 0;
+
+ parsing_file->crc32 = 0;
+ parsing_file->size = 0;
+}
+
+static ngx_int_t
+ngx_http_zip_clean_range(ngx_http_zip_range_t *range,
+ int prefix, int suffix, ngx_http_zip_ctx_t *ctx)
+{
+ if (suffix) {
+ range->end = ctx->archive_size;
+ range->start = ctx->archive_size - range->start;
+ } else if (prefix) {
+ range->end = ctx->archive_size;
+ } else {
+ range->end++;
+ /*
+ * Download Accelerator sends the last byte position
+ * that equals to the file length
+ */
+ if (range->end >= ctx->archive_size) {
+ range->end = ctx->archive_size;
+ }
+ }
+ if (range->start < 0) {
+ return NGX_ERROR;
+ }
+ if (range->start >= ctx->archive_size) {
+ return NGX_ERROR;
+ }
+ return NGX_OK;
+}
+
+%%{
+ machine request;
+ write data;
+}%%
+
+ngx_int_t
+ngx_http_zip_parse_request(ngx_http_zip_ctx_t *ctx)
+{
+ int cs;
+ u_char *p = ctx->unparsed_request->data;
+ u_char *pe = ctx->unparsed_request->data + ctx->unparsed_request->len;
+ ngx_http_zip_file_t *parsing_file = NULL;
+
+ %%{
+
+ action start_file {
+ parsing_file = ngx_array_push(&ctx->files);
+ ngx_http_zip_file_init(parsing_file);
+
+ parsing_file->index = ctx->files.nelts - 1;
+ }
+
+ action start_uri {
+ parsing_file->uri.data = fpc;
+ parsing_file->uri.len = 1;
+ }
+
+ action end_uri {
+ parsing_file->uri.len = fpc - parsing_file->uri.data;
+ }
+ action start_args {
+ parsing_file->args.data = fpc;
+ }
+ action end_args {
+ parsing_file->args.len = fpc - parsing_file->args.data;
+ }
+ action size_incr {
+ parsing_file->size = parsing_file->size * 10 + (fc - '0');
+ }
+ action crc_incr {
+ if (fc == '-') {
+ ctx->missing_crc32 = 1;
+ } else {
+ parsing_file->crc32 *= 16;
+ if (fc >= 'a' && fc <= 'f') {
+ parsing_file->crc32 += fc - 'a' + 10;
+ }
+ else if (fc >= 'A' && fc <= 'F') {
+ parsing_file->crc32 += fc - 'A' + 10;
+ } else { /* 0-9 */
+ parsing_file->crc32 += fc - '0';
+ }
+ }
+ }
+ action start_filename {
+ parsing_file->filename.data = fpc;
+ }
+ action end_filename {
+ parsing_file->filename.len = fpc - parsing_file->filename.data;
+ }
+
+ main := (
+ ( [0-9a-fA-F]+ | "-" ) >start_file $crc_incr
+ " "+
+ [0-9]+ $size_incr
+ " "+
+ [^? ]+ >start_uri %end_uri
+ ( "?" [^ ]+ >start_args %end_args )?
+ " "+
+ [^ ] >start_filename
+ [^\r\n\0]+ %end_filename
+ [\r\n]+
+ )+;
+
+ write init;
+ write exec;
+ }%%
+
+ if (cs < request_first_final) {
+ return NGX_ERROR;
+ }
+
+ ctx->parsed = 1;
+
+ return NGX_OK;
+}
+
+%%{
+ machine range;
+ write data;
+}%%
+
+ngx_int_t
+ngx_http_zip_parse_range(ngx_http_request_t *r, ngx_str_t *range_str, ngx_http_zip_ctx_t *ctx)
+{
+ int cs, prefix = 0, suffix = 0;
+
+ ngx_http_zip_range_t *range = NULL;
+ u_char *p = range_str->data;
+ u_char *pe = range_str->data + range_str->len;
+
+ %%{
+ action new_range {
+ if (range) {
+ if (ngx_http_zip_clean_range(range, prefix, suffix, ctx) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ }
+ if ((range = ngx_array_push(&ctx->ranges)) == NULL) {
+ return NGX_ERROR;
+ }
+ range->start = 0; range->end = 0; range->boundary_sent = 0;
+ suffix = 0;
+ prefix = 1;
+ }
+
+ action start_incr { range->start = range->start * 10 + (fc - '0'); }
+
+ action end_incr { range->end = range->end * 10 + (fc - '0'); prefix = 0; }
+
+ action suffix { suffix = 1; }
+
+ suffix_byte_range_spec = "-" [0-9]+ $start_incr >suffix;
+ byte_range_spec = [0-9]+ $start_incr
+ "-"
+ [0-9]* $end_incr;
+ byte_range_specs = ( byte_range_spec | suffix_byte_range_spec ) >new_range;
+ byte_range_set = byte_range_specs ( "," byte_range_specs )*;
+
+ main := "bytes=" byte_range_set;
+
+ write init;
+ write exec;
+ }%%
+
+ if (cs < range_first_final) {
+ return NGX_ERROR;
+ }
+
+ if (range) {
+ if (ngx_http_zip_clean_range(range, prefix, suffix, ctx) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
10 t/README
@@ -0,0 +1,10 @@
+To run tests, install nginx like:
+
+ ./configure --prefix=/path/to/mod_zip-1.1.5/t/nginx --add-module=/path/to/mod_zip-1.1.5
+
+Then run:
+
+ ./restart.sh
+ ./ziptest.pl
+
+Warning: don't do this in production! restart.sh kills nginx processes
4 t/clean.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+rm -r nginx/sbin nginx/*_temp nginx/logs nginx/conf/*
+cp nginx.conf nginx/conf/
84 t/nginx.conf
@@ -0,0 +1,84 @@
+
+#user nobody;
+worker_processes 1;
+
+#error_log logs/error.log;
+error_log logs/error.log debug;
+#error_log logs/error.log notice;
+#error_log logs/error.log info;
+
+#pid logs/nginx.pid;
+
+
+events {
+ worker_connections 1024;
+}
+
+
+http {
+ include mime.types;
+ default_type application/octet-stream;
+
+ #log_format main '$remote_addr - $remote_user [$time_local] $request '
+ # '"$status" $body_bytes_sent "$http_referer" '
+ # '"$http_user_agent" "$http_x_forwarded_for"';
+
+ #access_log logs/access.log main;
+
+ sendfile on;
+ #tcp_nopush on;
+
+ #keepalive_timeout 0;
+ keepalive_timeout 65;
+
+ #gzip on;
+
+ upstream ziplist {
+ server localhost:8082;
+ }
+
+ server {
+ listen 8082;
+ server_name localhost;
+
+ location / {
+ root html;
+ index index.html index.htm;
+ }
+
+ location /zip {
+ add_header X-Archive-Files zip;
+ }
+ }
+
+ server {
+ listen 8081;
+ server_name localhost;
+
+ #access_log logs/host.access.log main;
+
+ location / {
+ proxy_pass http://ziplist;
+ }
+
+ location /zip {
+ proxy_pass http://ziplist;
+ proxy_pass_request_headers off;
+ }
+
+ location /local {
+ alias html;
+ index index.html index.htm;
+ }
+
+ #error_page 404 /404.html;
+
+ # redirect server error pages to the static page /50x.html
+ #
+ error_page 500 502 503 504 /50x.html;
+ location = /50x.html {
+ root html;
+ }
+
+ }
+}
84 t/nginx/conf/nginx.conf
@@ -0,0 +1,84 @@
+
+#user nobody;
+worker_processes 1;
+
+#error_log logs/error.log;
+error_log logs/error.log debug;
+#error_log logs/error.log notice;
+#error_log logs/error.log info;
+
+#pid logs/nginx.pid;
+
+
+events {
+ worker_connections 1024;
+}
+
+
+http {
+ include mime.types;
+ default_type application/octet-stream;
+
+ #log_format main '$remote_addr - $remote_user [$time_local] $request '
+ # '"$status" $body_bytes_sent "$http_referer" '
+ # '"$http_user_agent" "$http_x_forwarded_for"';
+
+ #access_log logs/access.log main;
+
+ sendfile on;
+ #tcp_nopush on;
+
+ #keepalive_timeout 0;
+ keepalive_timeout 65;
+
+ #gzip on;
+
+ upstream ziplist {
+ server localhost:8082;
+ }
+
+ server {
+ listen 8082;
+ server_name localhost;
+
+ location / {
+ root html;
+ index index.html index.htm;
+ }
+
+ location /zip {
+ add_header X-Archive-Files zip;
+ }
+ }
+
+ server {
+ listen 8081;
+ server_name localhost;
+
+ #access_log logs/host.access.log main;