Skip to content

Commit

Permalink
Pass credentials in sub-requests (#87)
Browse files Browse the repository at this point in the history
* Preserve some important headers in subrequests

* Made the list of HTTP header fileds, that have to be included in sub-requests, configurable

* Covered a sub-request header field passing feature with tests
  • Loading branch information
devgs committed Nov 18, 2021
1 parent 51cf45d commit 555d3b3
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 17 deletions.
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

0 comments on commit 555d3b3

Please sign in to comment.