Skip to content

Commit 0bccb71

Browse files
committed
Fetch: added forward proxy support with HTTPS tunneling.
Supports Basic authentication via Proxy-Authorization header. - js_fetch_proxy - configures forward proxy URL. It takes proxy URL as a parameter. The URL may optionally contain user and password. Parameter value can contain variables. If value is empty, forward proxy is disabled. example.conf: ... http { js_import main.js; server { listen 8080; resolver 127.0.0.1:5353; location /api { js_fetch_proxy http://user:[email protected]:3128; js_content main.fetch_handler; } } } This implements #956 feature request on Github.
1 parent d71ba25 commit 0bccb71

14 files changed

+2118
-24
lines changed

nginx/ngx_http_js_module.c

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
typedef struct {
1616
NGX_JS_COMMON_LOC_CONF;
1717

18+
ngx_http_complex_value_t fetch_proxy_cv;
19+
1820
ngx_str_t content;
1921
ngx_str_t header_filter;
2022
ngx_str_t body_filter;
@@ -380,6 +382,8 @@ static char *ngx_http_js_content(ngx_conf_t *cf, ngx_command_t *cmd,
380382
void *conf);
381383
static char *ngx_http_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd,
382384
void *conf);
385+
static char *ngx_http_js_fetch_proxy(ngx_conf_t *cf, ngx_command_t *cmd,
386+
void *conf);
383387
static char *ngx_http_js_body_filter_set(ngx_conf_t *cf, ngx_command_t *cmd,
384388
void *conf);
385389
static ngx_int_t ngx_http_js_init_conf_vm(ngx_conf_t *cf,
@@ -593,6 +597,13 @@ static ngx_command_t ngx_http_js_commands[] = {
593597
offsetof(ngx_http_js_loc_conf_t, fetch_keepalive_timeout),
594598
NULL },
595599

600+
{ ngx_string("js_fetch_proxy"),
601+
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
602+
ngx_http_js_fetch_proxy,
603+
NGX_HTTP_LOC_CONF_OFFSET,
604+
0,
605+
NULL },
606+
596607
ngx_null_command
597608
};
598609

@@ -818,6 +829,16 @@ static njs_external_t ngx_http_js_ext_request[] = {
818829
}
819830
},
820831

832+
{
833+
.flags = NJS_EXTERN_PROPERTY,
834+
.name.string = njs_str("requestLine"),
835+
.enumerable = 1,
836+
.u.property = {
837+
.handler = ngx_js_ext_string,
838+
.magic32 = offsetof(ngx_http_request_t, request_line),
839+
}
840+
},
841+
821842
{
822843
.flags = NJS_EXTERN_PROPERTY,
823844
.name.string = njs_str("requestText"),
@@ -1080,6 +1101,8 @@ static const JSCFunctionListEntry ngx_http_qjs_ext_request[] = {
10801101
JS_CGETSET_DEF("remoteAddress", ngx_http_qjs_ext_remote_address, NULL),
10811102
JS_CGETSET_MAGIC_DEF("requestBuffer", ngx_http_qjs_ext_request_body, NULL,
10821103
NGX_JS_BUFFER),
1104+
JS_CGETSET_MAGIC_DEF("requestLine", ngx_http_qjs_ext_string, NULL,
1105+
offsetof(ngx_http_request_t, request_line)),
10831106
JS_CGETSET_MAGIC_DEF("requestText", ngx_http_qjs_ext_request_body, NULL,
10841107
NGX_JS_STRING),
10851108
JS_CGETSET_MAGIC_DEF("responseBuffer", ngx_http_qjs_ext_response_body, NULL,
@@ -8016,6 +8039,60 @@ ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
80168039
}
80178040

80188041

8042+
static ngx_int_t
8043+
ngx_http_js_eval_proxy_url(ngx_pool_t *pool, void *request,
8044+
void *module_conf, ngx_url_t **url_out, ngx_str_t *auth_out)
8045+
{
8046+
ngx_str_t value;
8047+
ngx_http_request_t *r;
8048+
ngx_http_js_loc_conf_t *jlcf;
8049+
8050+
r = request;
8051+
jlcf = module_conf;
8052+
8053+
if (ngx_http_complex_value(r, &jlcf->fetch_proxy_cv, &value) != NGX_OK) {
8054+
return NGX_ERROR;
8055+
}
8056+
8057+
return ngx_js_parse_proxy_url(pool, r->connection->log, &value,
8058+
url_out, auth_out);
8059+
}
8060+
8061+
8062+
static char *
8063+
ngx_http_js_fetch_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
8064+
{
8065+
ngx_str_t *value;
8066+
ngx_uint_t n;
8067+
ngx_http_js_loc_conf_t *jlcf;
8068+
ngx_http_compile_complex_value_t ccv;
8069+
8070+
value = cf->args->elts;
8071+
8072+
n = ngx_http_script_variables_count(&value[1]);
8073+
8074+
if (n) {
8075+
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
8076+
8077+
jlcf = conf;
8078+
8079+
ccv.cf = cf;
8080+
ccv.value = &value[1];
8081+
ccv.complex_value = &jlcf->fetch_proxy_cv;
8082+
8083+
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
8084+
return NGX_CONF_ERROR;
8085+
}
8086+
8087+
jlcf->eval_proxy_url = ngx_http_js_eval_proxy_url;
8088+
8089+
return NGX_CONF_OK;
8090+
}
8091+
8092+
return ngx_js_fetch_proxy(cf, cmd, conf);
8093+
}
8094+
8095+
80198096
static char *
80208097
ngx_http_js_var(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
80218098
{

nginx/ngx_js.c

Lines changed: 198 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3267,6 +3267,195 @@ ngx_js_preload_object(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
32673267
}
32683268

32693269

3270+
static ngx_int_t
3271+
ngx_js_build_proxy_auth_header(ngx_pool_t *pool, ngx_str_t *auth_header,
3272+
ngx_str_t *user, ngx_str_t *pass)
3273+
{
3274+
u_char *p;
3275+
size_t len;
3276+
ngx_str_t userpass, b64;
3277+
3278+
userpass.len = user->len + 1 + pass->len;
3279+
userpass.data = ngx_pnalloc(pool, userpass.len);
3280+
if (userpass.data == NULL) {
3281+
return NGX_ERROR;
3282+
}
3283+
3284+
p = ngx_cpymem(userpass.data, user->data, user->len);
3285+
*p++ = ':';
3286+
ngx_memcpy(p, pass->data, pass->len);
3287+
3288+
b64.len = ngx_base64_encoded_length(userpass.len);
3289+
b64.data = ngx_pnalloc(pool, b64.len);
3290+
if (b64.data == NULL) {
3291+
return NGX_ERROR;
3292+
}
3293+
3294+
ngx_encode_base64(&b64, &userpass);
3295+
3296+
len = sizeof("Proxy-Authorization: Basic \r\n") - 1 + b64.len;
3297+
p = ngx_pnalloc(pool, len);
3298+
if (p == NULL) {
3299+
return NGX_ERROR;
3300+
}
3301+
3302+
ngx_sprintf(p, "Proxy-Authorization: Basic %V\r\n", &b64);
3303+
auth_header->data = p;
3304+
auth_header->len = len;
3305+
3306+
return NGX_OK;
3307+
}
3308+
3309+
3310+
ngx_int_t
3311+
ngx_js_parse_proxy_url(ngx_pool_t *pool, ngx_log_t *log, ngx_str_t *url,
3312+
ngx_url_t **url_out, ngx_str_t *auth_header_out)
3313+
{
3314+
u_char *p, *at, *colon, *host_start, *user_start, *pass_start;
3315+
u_char *decoded_user, *decoded_pass, *decoded_end;
3316+
size_t user_len, pass_len;
3317+
ngx_url_t *u;
3318+
ngx_str_t user, pass;
3319+
3320+
if (url->len == 0) {
3321+
*url_out = NULL;
3322+
ngx_str_null(auth_header_out);
3323+
return NGX_OK;
3324+
}
3325+
3326+
if (ngx_strncmp(url->data, "http://", sizeof("http://") - 1) != 0) {
3327+
ngx_log_error(NGX_LOG_ERR, log, 0,
3328+
"js_fetch_proxy URL must use http:// scheme");
3329+
return NGX_ERROR;
3330+
}
3331+
3332+
host_start = url->data + (sizeof("http://") - 1);
3333+
at = ngx_strlchr(host_start, url->data + url->len, '@');
3334+
3335+
ngx_str_null(auth_header_out);
3336+
3337+
if (at != NULL) {
3338+
colon = NULL;
3339+
3340+
for (p = at - 1; p > host_start; p--) {
3341+
if (*p == ':') {
3342+
colon = p;
3343+
break;
3344+
}
3345+
}
3346+
3347+
if (colon == NULL) {
3348+
ngx_log_error(NGX_LOG_ERR, log, 0,
3349+
"js_fetch_proxy URL credentials must be in "
3350+
"user:password format");
3351+
return NGX_ERROR;
3352+
}
3353+
3354+
user_start = host_start;
3355+
user_len = colon - host_start;
3356+
pass_start = colon + 1;
3357+
pass_len = at - pass_start;
3358+
3359+
decoded_user = ngx_pnalloc(pool, 128);
3360+
if (decoded_user == NULL) {
3361+
return NGX_ERROR;
3362+
}
3363+
3364+
decoded_pass = ngx_pnalloc(pool, 128);
3365+
if (decoded_pass == NULL) {
3366+
return NGX_ERROR;
3367+
}
3368+
3369+
p = user_start;
3370+
decoded_end = decoded_user;
3371+
ngx_unescape_uri(&decoded_end, &p, user_len, NGX_UNESCAPE_URI);
3372+
3373+
user_len = decoded_end - decoded_user;
3374+
if (user_len == 0 || user_len > 127) {
3375+
ngx_log_error(NGX_LOG_ERR, log, 0,
3376+
"js_fetch_proxy username invalid or too long "
3377+
"(max 127 bytes after decoding)");
3378+
return NGX_ERROR;
3379+
}
3380+
3381+
p = pass_start;
3382+
decoded_end = decoded_pass;
3383+
ngx_unescape_uri(&decoded_end, &p, pass_len, NGX_UNESCAPE_URI);
3384+
3385+
pass_len = decoded_end - decoded_pass;
3386+
if (pass_len == 0 || pass_len > 127) {
3387+
ngx_log_error(NGX_LOG_ERR, log, 0,
3388+
"js_fetch_proxy password invalid or too long "
3389+
"(max 127 bytes after decoding)");
3390+
return NGX_ERROR;
3391+
}
3392+
3393+
user.data = decoded_user;
3394+
user.len = user_len;
3395+
pass.data = decoded_pass;
3396+
pass.len = pass_len;
3397+
3398+
if (ngx_js_build_proxy_auth_header(pool, auth_header_out,
3399+
&user, &pass)
3400+
!= NGX_OK)
3401+
{
3402+
return NGX_ERROR;
3403+
}
3404+
3405+
host_start = at + 1;
3406+
}
3407+
3408+
u = ngx_pcalloc(pool, sizeof(ngx_url_t));
3409+
if (u == NULL) {
3410+
return NGX_ERROR;
3411+
}
3412+
3413+
u->url.data = host_start;
3414+
u->url.len = url->data + url->len - host_start;
3415+
u->default_port = 3128;
3416+
u->no_resolve = 1;
3417+
3418+
if (ngx_parse_url(pool, u) != NGX_OK) {
3419+
ngx_log_error(NGX_LOG_ERR, log, 0, "invalid proxy URL: %V", url);
3420+
return NGX_ERROR;
3421+
}
3422+
3423+
*url_out = u;
3424+
3425+
return NGX_OK;
3426+
}
3427+
3428+
3429+
char *
3430+
ngx_js_fetch_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
3431+
{
3432+
ngx_str_t *value;
3433+
ngx_js_loc_conf_t *jscf;
3434+
3435+
jscf = conf;
3436+
3437+
value = cf->args->elts;
3438+
3439+
if (ngx_js_parse_proxy_url(cf->pool, cf->log, &value[1],
3440+
&jscf->fetch_proxy_url,
3441+
&jscf->fetch_proxy_auth_header)
3442+
!= NGX_OK)
3443+
{
3444+
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
3445+
"invalid proxy URL: %V", &value[1]);
3446+
return NGX_CONF_ERROR;
3447+
}
3448+
3449+
if (jscf->fetch_proxy_url == NULL) {
3450+
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
3451+
"proxy host is empty in URL: %V", &value[1]);
3452+
return NGX_CONF_ERROR;
3453+
}
3454+
3455+
return NGX_CONF_OK;
3456+
}
3457+
3458+
32703459
static ngx_int_t
32713460
ngx_js_init_preload_vm(njs_vm_t *vm, ngx_js_loc_conf_t *conf)
32723461
{
@@ -3946,6 +4135,7 @@ ngx_js_create_conf(ngx_conf_t *cf, size_t size)
39464135
* set by ngx_pcalloc():
39474136
*
39484137
* conf->reuse_queue = NULL;
4138+
* conf->fetch_proxy_auth_header = { 0, NULL };
39494139
*/
39504140

39514141
conf->paths = NGX_CONF_UNSET_PTR;
@@ -3963,6 +4153,8 @@ ngx_js_create_conf(ngx_conf_t *cf, size_t size)
39634153
conf->fetch_keepalive_requests = NGX_CONF_UNSET_UINT;
39644154
conf->fetch_keepalive_time = NGX_CONF_UNSET_MSEC;
39654155
conf->fetch_keepalive_timeout = NGX_CONF_UNSET_MSEC;
4156+
conf->fetch_proxy_url = NGX_CONF_UNSET_PTR;
4157+
conf->eval_proxy_url = NGX_CONF_UNSET_PTR;
39664158

39674159
return conf;
39684160
}
@@ -4082,10 +4274,15 @@ ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child,
40824274
prev->fetch_keepalive_time, 3600000);
40834275
ngx_conf_merge_msec_value(conf->fetch_keepalive_timeout,
40844276
prev->fetch_keepalive_timeout, 60000);
4085-
40864277
ngx_queue_init(&conf->fetch_keepalive_cache);
40874278
ngx_queue_init(&conf->fetch_keepalive_free);
40884279

4280+
ngx_conf_merge_ptr_value(conf->fetch_proxy_url, prev->fetch_proxy_url,
4281+
NULL);
4282+
ngx_conf_merge_ptr_value(conf->eval_proxy_url, prev->eval_proxy_url, NULL);
4283+
ngx_conf_merge_str_value(conf->fetch_proxy_auth_header,
4284+
prev->fetch_proxy_auth_header, "");
4285+
40894286
if (ngx_js_merge_vm(cf, (ngx_js_loc_conf_t *) conf,
40904287
(ngx_js_loc_conf_t *) prev,
40914288
init_vm)

nginx/ngx_js.h

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,24 @@ typedef struct {
141141
ngx_msec_t fetch_keepalive_time; \
142142
ngx_msec_t fetch_keepalive_timeout; \
143143
ngx_queue_t fetch_keepalive_cache; \
144-
ngx_queue_t fetch_keepalive_free
144+
ngx_queue_t fetch_keepalive_free; \
145+
\
146+
ngx_url_t *fetch_proxy_url; \
147+
ngx_str_t fetch_proxy_auth_header; \
148+
\
149+
ngx_int_t (*eval_proxy_url)(ngx_pool_t *pool, \
150+
void *request, \
151+
void *module_conf, \
152+
ngx_url_t **url_out, \
153+
ngx_str_t *auth_out)
154+
155+
#define ngx_js_conf_dynamic_proxy(conf) \
156+
((conf)->eval_proxy_url != NULL)
157+
158+
#define ngx_js_conf_proxy(conf) \
159+
(((conf)->fetch_proxy_url != NULL \
160+
&& (conf)->fetch_proxy_url->host.len > 0) \
161+
|| ngx_js_conf_dynamic_proxy(conf))
145162

146163

147164
#if (NGX_SSL)
@@ -424,6 +441,9 @@ void ngx_js_logger(ngx_connection_t *c, ngx_uint_t level,
424441
char * ngx_js_import(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
425442
char * ngx_js_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
426443
char * ngx_js_preload_object(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
444+
char * ngx_js_fetch_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
445+
ngx_int_t ngx_js_parse_proxy_url(ngx_pool_t *pool, ngx_log_t *log,
446+
ngx_str_t *url_str, ngx_url_t **url_out, ngx_str_t *auth_header_out);
427447
ngx_int_t ngx_js_merge_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf,
428448
ngx_js_loc_conf_t *prev,
429449
ngx_int_t (*init_vm)(ngx_conf_t *cf, ngx_js_loc_conf_t *conf));

0 commit comments

Comments
 (0)