Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass credentials in sub-requests #87

Merged
merged 9 commits into from
Nov 18, 2021
6 changes: 6 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ A special URL marker `@directory` can be used to declare a directory entry
within an archive. This is very convenient when you have to package a tree of
files, including some empty directories. As they have to be declared explicitly.

If you want mod_zip to include some HTTP headers of the original request, in the
sub-requests that fetch content of files, then pass the list of their names in
the following HTTP header:

X-Archive-Pass-Headers: <header-name>[:<header-name>]*


Re-encoding filenames
---
Expand Down
40 changes: 40 additions & 0 deletions ngx_http_zip_file.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
static ngx_str_t ngx_http_zip_header_charset_name = ngx_string("upstream_http_x_archive_charset");
#endif
static ngx_str_t ngx_http_zip_header_name_separator = ngx_string("upstream_http_x_archive_name_sep");
static ngx_str_t ngx_http_zip_header_name_pass_headers = ngx_string("upstream_http_x_archive_pass_headers");

#define NGX_MAX_UINT16_VALUE 0xffff

Expand Down Expand Up @@ -428,6 +429,45 @@ ngx_http_zip_generate_pieces(ngx_http_request_t *r, ngx_http_zip_ctx_t *ctx)

ctx->archive_size = offset;

// Collect names of original request's header fields that
// have to be present in each of the issued sub-requests.
variable_header_status = ngx_http_variable_unknown_header(vv, &ngx_http_zip_header_name_pass_headers,
&r->upstream->headers_in.headers.part, sizeof("upstream_http_")-1);

if (variable_header_status == NGX_OK && !vv->not_found) {
ngx_str_t *header;
ngx_int_t len;
u_char *next;
u_char *start = vv->data;
u_char *end = vv->data + vv->len;

// Split the list of names by ':'.
while (start < end) {
next = ngx_strnstr(start, ":", end - start);

if (next == NULL) {
next = end;
}

len = next - start;

if (len) {
if ((header = ngx_array_push(&ctx->pass_srq_headers)) == NULL) {
return NGX_ERROR;
}

if ((header->data = ngx_pnalloc(r->pool, len)) == NULL) {
return NGX_ERROR;
}

ngx_memcpy(header->data, start, len);
header->len = len;
}

start = next + 1;
}
}

return NGX_OK;
}

Expand Down
51 changes: 45 additions & 6 deletions ngx_http_zip_headers.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@
#include "ngx_http_zip_headers.h"
#include "ngx_http_zip_file.h"

static ngx_uint_t
ngx_http_zip_find_key_in_set(ngx_str_t *key, ngx_array_t *set)
{
ngx_uint_t i;
ngx_str_t *items = set->elts;

for (i = 0; i < set->nelts; ++i) {
if (items[i].len == key->len
&& !ngx_rstrncasecmp(items[i].data, key->data, key->len)) {
return 1;
}
}

return 0;
}

ngx_int_t
ngx_http_zip_add_cache_control(ngx_http_request_t *r)
{
Expand Down Expand Up @@ -206,17 +222,40 @@ ngx_http_zip_strip_range_header(ngx_http_request_t *r)
}

ngx_int_t
ngx_http_zip_init_subrequest_headers(ngx_http_request_t *r, ngx_http_request_t *sr,
ngx_http_zip_range_t *piece_range, ngx_http_zip_range_t *req_range)
ngx_http_zip_init_subrequest_headers(ngx_http_request_t *r, ngx_http_zip_ctx_t *ctx,
ngx_http_request_t *sr, ngx_http_zip_range_t *piece_range,
ngx_http_zip_range_t *req_range)
{
ngx_memzero(&sr->headers_in, sizeof(sr->headers_in));
sr->headers_in.content_length_n = -1;
sr->headers_in.keep_alive_n = -1;
ngx_list_t new_headers;

if (ngx_list_init(&sr->headers_in.headers, r->pool, 1, sizeof(ngx_table_elt_t)) != NGX_OK) {
if (ngx_list_init(&new_headers, r->pool, 1, sizeof(ngx_table_elt_t)) != NGX_OK) {
return NGX_ERROR;
}

if (ctx->pass_srq_headers.nelts) {
// Pass original header fileds, that appear on the list.
ngx_list_part_t *next_part;
ngx_table_elt_t *h;
ngx_uint_t i;

next_part = &sr->headers_in.headers.part;

for (; next_part; next_part = next_part->next) {
h = next_part->elts;

for (i = 0; i < next_part->nelts; ++i) {
if (ngx_http_zip_find_key_in_set(&h[i].key, &ctx->pass_srq_headers)) {
ngx_memcpy(ngx_list_push(&new_headers), &h[i], sizeof(ngx_table_elt_t));
}
}
}
}

ngx_memzero(&sr->headers_in, sizeof(sr->headers_in));
ngx_memcpy(&sr->headers_in.headers, &new_headers, sizeof(new_headers));
sr->headers_in.content_length_n = -1;
sr->headers_in.keep_alive_n = -1;

if (req_range && (piece_range->start < req_range->start || piece_range->end > req_range->end)) {
ngx_table_elt_t *range_header = ngx_list_push(&sr->headers_in.headers);
off_t start = req_range->start - piece_range->start;
Expand Down
5 changes: 3 additions & 2 deletions ngx_http_zip_headers.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ ngx_int_t ngx_http_zip_add_partial_content_range(ngx_http_request_t *r,
ngx_int_t ngx_http_zip_init_multipart_range(ngx_http_request_t *r,
ngx_http_zip_ctx_t *ctx);

ngx_int_t ngx_http_zip_init_subrequest_headers(ngx_http_request_t *r, ngx_http_request_t *sr,
ngx_http_zip_range_t *piece_range, ngx_http_zip_range_t *req_range);
ngx_int_t ngx_http_zip_init_subrequest_headers(ngx_http_request_t *r, ngx_http_zip_ctx_t *ctx,
ngx_http_request_t *sr, ngx_http_zip_range_t *piece_range,
ngx_http_zip_range_t *req_range);
7 changes: 4 additions & 3 deletions ngx_http_zip_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ ngx_http_zip_main_request_header_filter(ngx_http_request_t *r)

if ((ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_zip_ctx_t))) == NULL
|| ngx_array_init(&ctx->files, r->pool, 1, sizeof(ngx_http_zip_file_t)) == NGX_ERROR
|| ngx_array_init(&ctx->ranges, r->pool, 1, sizeof(ngx_http_zip_range_t)) == NGX_ERROR)
|| ngx_array_init(&ctx->ranges, r->pool, 1, sizeof(ngx_http_zip_range_t)) == NGX_ERROR
|| ngx_array_init(&ctx->pass_srq_headers, r->pool, 1, sizeof(ngx_str_t)) == NGX_ERROR)
return NGX_ERROR;

ngx_http_set_ctx(r, ctx, ngx_http_zip_module);
Expand Down Expand Up @@ -552,7 +553,7 @@ ngx_http_zip_send_file_piece(ngx_http_request_t *r, ngx_http_zip_ctx_t *ctx,
sr->subrequest_ranges = 1;
sr->single_range = 1;

rc = ngx_http_zip_init_subrequest_headers(r, sr, &piece->range, req_range);
rc = ngx_http_zip_init_subrequest_headers(r, ctx, sr, &piece->range, req_range);
if (sr->headers_in.range) {
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"mod_zip: subrequest for \"%V?%V\" Range: %V",
Expand All @@ -562,7 +563,7 @@ ngx_http_zip_send_file_piece(ngx_http_request_t *r, ngx_http_zip_ctx_t *ctx,
return NGX_ERROR;
}

if ((sr_ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_zip_ctx_t))) == NULL) {
if ((sr_ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_zip_sr_ctx_t))) == NULL) {
return NGX_ERROR;
}

Expand Down
1 change: 1 addition & 0 deletions ngx_http_zip_module.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ typedef struct {
off_t archive_size;
off_t cd_size; // zip central directory size
ngx_http_request_t *wait;
ngx_array_t pass_srq_headers;

unsigned parsed:1;
unsigned trailer_sent:1;
Expand Down
27 changes: 24 additions & 3 deletions t/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,27 @@ http {
}

location /zip {
add_header X-Archive-Files zip;
add_header Last-Modified "Wed, 15 Nov 1995 04:58:08 GMT";
add_header ETag "3.14159";
add_header X-Archive-Files zip;
add_header X-Archive-Pass-Headers $arg_pass_headers;
add_header Last-Modified "Wed, 15 Nov 1995 04:58:08 GMT";
add_header ETag "3.14159";
}

location /with_auth/cookie {
if ($http_cookie = "") {
return 403;
}

alias html;
}

location /with_auth/x_auth_token {
if ($http_x_auth_token = "") {
return 403;
}

alias html;
}
}

server {
Expand All @@ -72,6 +89,10 @@ http {
alias html;
}

location /with_auth/ {
proxy_pass http://ziplist;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
Expand Down
2 changes: 2 additions & 0 deletions t/nginx/html/zip-authorized-files-cookie.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
1a6349c5 24 /with_auth/cookie/file1.txt file1.txt
5d70c4d3 25 /with_auth/cookie/file2.txt file2.txt
2 changes: 2 additions & 0 deletions t/nginx/html/zip-authorized-files-mixed.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
1a6349c5 24 /with_auth/cookie/file1.txt file1.txt
5d70c4d3 25 /with_auth/x_auth_token/file2.txt file2.txt
2 changes: 2 additions & 0 deletions t/nginx/html/zip-authorized-files-x.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
1a6349c5 24 /with_auth/x_auth_token/file1.txt file1.txt
5d70c4d3 25 /with_auth/x_auth_token/file2.txt file2.txt
36 changes: 33 additions & 3 deletions t/ziptest.pl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# TODO tests for Zip64

use Test::More tests => 103;
use Test::More tests => 115;
use LWP::UserAgent;
use Archive::Zip;

Expand Down Expand Up @@ -154,19 +154,49 @@ ($$)
########## Package empty directories

$response = $ua->get("$http_root/zip-empty-dirs.txt");
$zip = test_zip_archive($response->content, "with empty directories");
is($response->code, 200, "Returns OK with mixed empty directories and files");
$zip = test_zip_archive($response->content, "with empty directories");
is($zip->memberNamed("file1.txt")->isBinaryFile(), 1, "file1.txt exists in archive");
is($zip->memberNamed("empty_dir1/")->isDirectory(), 1, "empty_dir1 exists in archive");
is($zip->memberNamed("file2.txt")->isBinaryFile(), 1, "file2.txt exists in archive");
is($zip->memberNamed("empty_dir2/")->isDirectory(), 1, "empty_dir2 exists in archive");

$response = $ua->get("$http_root/zip-only-empty-dirs.txt");
$zip = write_temp_zip($response->content);
is($response->code, 200, "Returns OK with only empty directories");
$zip = write_temp_zip($response->content);
is($zip->memberNamed("empty_dir1/")->isDirectory(), 1, "empty_dir1 exists in archive");
is($zip->memberNamed("empty_dir2/")->isDirectory(), 1, "empty_dir2 exists in archive");

########## Pass headers in sub-requests

$response = $ua->get("$http_root/zip-authorized-files-cookie.txt", "Cookie" => "session=verified");
is($response->code, 500, "Server error on attempt to use authorizable file without Cookie");

$response = $ua->get("$http_root/zip-authorized-files-cookie.txt?pass_headers=Cookie", "Cookie" => "session=verified");
is($response->code, 200, "Returns OK when Cookie header field is passed");
$zip = write_temp_zip($response->content);
is($zip->memberNamed("file1.txt")->isBinaryFile(), 1, "file1.txt exists in archive");
is($zip->memberNamed("file2.txt")->isBinaryFile(), 1, "file2.txt exists in archive");

$response = $ua->get("$http_root/zip-authorized-files-x.txt", "X-Auth-Token" => "verified");
is($response->code, 500, "Server error on attempt to use authorizable file without X-Auth-Token");

$response = $ua->get("$http_root/zip-authorized-files-x.txt?pass_headers=X-Auth-Token", "X-Auth-Token" => "verified");
is($response->code, 200, "Returns OK when X-Auth-Token header field is passed");
$zip = write_temp_zip($response->content);
is($zip->memberNamed("file1.txt")->isBinaryFile(), 1, "file1.txt exists in archive");
is($zip->memberNamed("file2.txt")->isBinaryFile(), 1, "file2.txt exists in archive");

$response = $ua->get("$http_root/zip-authorized-files-mixed.txt", "Cookie" => "session=verified", "X-Auth-Token" => "verified");
is($response->code, 500, "Server error on attempt to use authorizable files without respective header fields");

$response = $ua->get("$http_root/zip-authorized-files-mixed.txt?pass_headers=Cookie:X-Auth-Token",
"Cookie" => "session=verified", "X-Auth-Token" => "verified");
is($response->code, 200, "Returns OK when Cookie and X-Auth-Token header field is passed");
$zip = write_temp_zip($response->content);
is($zip->memberNamed("file1.txt")->isBinaryFile(), 1, "file1.txt exists in archive");
is($zip->memberNamed("file2.txt")->isBinaryFile(), 1, "file2.txt exists in archive");


open LARGEFILE, ">", "nginx/html/largefile.txt";
for (0..99999) {
Expand Down