Skip to content

Commit 0d834f7

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 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 0dc3b77 commit 0d834f7

12 files changed

+1580
-20
lines changed

nginx/ngx_http_js_module.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,13 @@ static ngx_command_t ngx_http_js_commands[] = {
593593
offsetof(ngx_http_js_loc_conf_t, fetch_keepalive_timeout),
594594
NULL },
595595

596+
{ ngx_string("js_fetch_proxy"),
597+
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
598+
ngx_js_fetch_proxy,
599+
NGX_HTTP_LOC_CONF_OFFSET,
600+
0,
601+
NULL },
602+
596603
ngx_null_command
597604
};
598605

@@ -818,6 +825,16 @@ static njs_external_t ngx_http_js_ext_request[] = {
818825
}
819826
},
820827

828+
{
829+
.flags = NJS_EXTERN_PROPERTY,
830+
.name.string = njs_str("requestLine"),
831+
.enumerable = 1,
832+
.u.property = {
833+
.handler = ngx_js_ext_string,
834+
.magic32 = offsetof(ngx_http_request_t, request_line),
835+
}
836+
},
837+
821838
{
822839
.flags = NJS_EXTERN_PROPERTY,
823840
.name.string = njs_str("requestText"),
@@ -1080,6 +1097,8 @@ static const JSCFunctionListEntry ngx_http_qjs_ext_request[] = {
10801097
JS_CGETSET_DEF("remoteAddress", ngx_http_qjs_ext_remote_address, NULL),
10811098
JS_CGETSET_MAGIC_DEF("requestBuffer", ngx_http_qjs_ext_request_body, NULL,
10821099
NGX_JS_BUFFER),
1100+
JS_CGETSET_MAGIC_DEF("requestLine", ngx_http_qjs_ext_string, NULL,
1101+
offsetof(ngx_http_request_t, request_line)),
10831102
JS_CGETSET_MAGIC_DEF("requestText", ngx_http_qjs_ext_request_body, NULL,
10841103
NGX_JS_STRING),
10851104
JS_CGETSET_MAGIC_DEF("responseBuffer", ngx_http_qjs_ext_response_body, NULL,

nginx/ngx_js.c

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3305,6 +3305,162 @@ ngx_js_preload_object(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
33053305
}
33063306

33073307

3308+
static ngx_int_t
3309+
ngx_js_build_proxy_auth_header(ngx_pool_t *pool, ngx_js_loc_conf_t *jscf,
3310+
ngx_str_t *user, ngx_str_t *pass)
3311+
{
3312+
u_char *p;
3313+
size_t len;
3314+
ngx_str_t userpass, b64;
3315+
3316+
userpass.len = user->len + 1 + pass->len;
3317+
userpass.data = ngx_pnalloc(pool, userpass.len);
3318+
if (userpass.data == NULL) {
3319+
return NGX_ERROR;
3320+
}
3321+
3322+
p = ngx_cpymem(userpass.data, user->data, user->len);
3323+
*p++ = ':';
3324+
ngx_memcpy(p, pass->data, pass->len);
3325+
3326+
b64.len = ngx_base64_encoded_length(userpass.len);
3327+
b64.data = ngx_pnalloc(pool, b64.len);
3328+
if (b64.data == NULL) {
3329+
return NGX_ERROR;
3330+
}
3331+
3332+
ngx_encode_base64(&b64, &userpass);
3333+
3334+
len = sizeof("Proxy-Authorization: Basic \r\n") - 1 + b64.len;
3335+
p = ngx_pnalloc(pool, len);
3336+
if (p == NULL) {
3337+
return NGX_ERROR;
3338+
}
3339+
3340+
ngx_sprintf(p, "Proxy-Authorization: Basic %V\r\n", &b64);
3341+
jscf->fetch_proxy_auth_header.data = p;
3342+
jscf->fetch_proxy_auth_header.len = len;
3343+
3344+
return NGX_OK;
3345+
}
3346+
3347+
3348+
char *
3349+
ngx_js_fetch_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
3350+
{
3351+
u_char *p, *at, *colon, *host_start, *user_start, *pass_start;
3352+
u_char *decoded_user, *decoded_pass, *decoded_end;
3353+
size_t user_len, pass_len;
3354+
ngx_url_t *u;
3355+
ngx_str_t *value;
3356+
ngx_str_t user, pass;
3357+
ngx_js_loc_conf_t *jscf;
3358+
3359+
jscf = conf;
3360+
3361+
value = cf->args->elts;
3362+
3363+
if (ngx_strncmp(value[1].data, "http://", sizeof("http://") - 1) != 0) {
3364+
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
3365+
"js_fetch_proxy URL must use http:// scheme");
3366+
return NGX_CONF_ERROR;
3367+
}
3368+
3369+
host_start = value[1].data + (sizeof("http://") - 1);
3370+
at = ngx_strlchr(host_start, value[1].data + value[1].len, '@');
3371+
3372+
if (at != NULL) {
3373+
colon = NULL;
3374+
3375+
for (p = at - 1; p > host_start; p--) {
3376+
if (*p == ':') {
3377+
colon = p;
3378+
break;
3379+
}
3380+
}
3381+
3382+
if (colon == NULL) {
3383+
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
3384+
"js_fetch_proxy URL credentials must be in "
3385+
"user:password format");
3386+
return NGX_CONF_ERROR;
3387+
}
3388+
3389+
user_start = host_start;
3390+
user_len = colon - host_start;
3391+
pass_start = colon + 1;
3392+
pass_len = at - pass_start;
3393+
3394+
decoded_user = ngx_pnalloc(cf->pool, 128);
3395+
if (decoded_user == NULL) {
3396+
return NGX_CONF_ERROR;
3397+
}
3398+
3399+
decoded_pass = ngx_pnalloc(cf->pool, 128);
3400+
if (decoded_pass == NULL) {
3401+
return NGX_CONF_ERROR;
3402+
}
3403+
3404+
p = user_start;
3405+
decoded_end = decoded_user;
3406+
ngx_unescape_uri(&decoded_end, &p, user_len, NGX_UNESCAPE_URI);
3407+
3408+
user_len = decoded_end - decoded_user;
3409+
if (user_len == 0 || user_len > 127) {
3410+
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
3411+
"js_fetch_proxy username invalid or too long "
3412+
"(max 127 bytes after decoding)");
3413+
return NGX_CONF_ERROR;
3414+
}
3415+
3416+
p = pass_start;
3417+
decoded_end = decoded_pass;
3418+
ngx_unescape_uri(&decoded_end, &p, pass_len, NGX_UNESCAPE_URI);
3419+
3420+
pass_len = decoded_end - decoded_pass;
3421+
if (pass_len == 0 || pass_len > 127) {
3422+
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
3423+
"js_fetch_proxy password invalid or too long "
3424+
"(max 127 bytes after decoding)");
3425+
return NGX_CONF_ERROR;
3426+
}
3427+
3428+
user.data = decoded_user;
3429+
user.len = user_len;
3430+
pass.data = decoded_pass;
3431+
pass.len = pass_len;
3432+
3433+
if (ngx_js_build_proxy_auth_header(cf->pool, jscf, &user, &pass)
3434+
!= NGX_OK)
3435+
{
3436+
return NGX_CONF_ERROR;
3437+
}
3438+
3439+
host_start = at + 1;
3440+
}
3441+
3442+
u = ngx_pcalloc(cf->pool, sizeof(ngx_url_t));
3443+
if (u == NULL) {
3444+
return NGX_CONF_ERROR;
3445+
}
3446+
3447+
u->url.data = host_start;
3448+
u->url.len = value[1].data + value[1].len - host_start;
3449+
u->default_port = 3128;
3450+
u->no_resolve = 1;
3451+
3452+
if (ngx_parse_url(cf->pool, u) != NGX_OK) {
3453+
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
3454+
"invalid proxy URL: %V", &value[1]);
3455+
return NGX_CONF_ERROR;
3456+
}
3457+
3458+
jscf->fetch_proxy_url = u;
3459+
3460+
return NGX_CONF_OK;
3461+
}
3462+
3463+
33083464
static ngx_int_t
33093465
ngx_js_init_preload_vm(njs_vm_t *vm, ngx_js_loc_conf_t *conf)
33103466
{
@@ -4121,6 +4277,13 @@ ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child,
41214277
ngx_conf_merge_msec_value(conf->fetch_keepalive_timeout,
41224278
prev->fetch_keepalive_timeout, 60000);
41234279

4280+
ngx_conf_merge_str_value(conf->fetch_proxy_auth_header,
4281+
prev->fetch_proxy_auth_header, "");
4282+
4283+
if (conf->fetch_proxy_url == NULL && prev->fetch_proxy_url != NULL) {
4284+
conf->fetch_proxy_url = prev->fetch_proxy_url;
4285+
}
4286+
41244287
ngx_queue_init(&conf->fetch_keepalive_cache);
41254288
ngx_queue_init(&conf->fetch_keepalive_free);
41264289

nginx/ngx_js.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,15 @@ 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+
150+
#define ngx_js_proxy(conf) \
151+
((conf)->fetch_proxy_url != NULL \
152+
&& (conf)->fetch_proxy_url->host.len > 0)
145153

146154

147155
#if (NGX_SSL)
@@ -423,6 +431,7 @@ void ngx_js_logger(ngx_connection_t *c, ngx_uint_t level,
423431
char * ngx_js_import(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
424432
char * ngx_js_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
425433
char * ngx_js_preload_object(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
434+
char * ngx_js_fetch_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
426435
ngx_int_t ngx_js_merge_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf,
427436
ngx_js_loc_conf_t *prev,
428437
ngx_int_t (*init_vm)(ngx_conf_t *cf, ngx_js_loc_conf_t *conf));

nginx/ngx_js_fetch.c

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,7 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
508508
{
509509
njs_int_t ret;
510510
ngx_url_t u;
511+
ngx_str_t *resolve_host;
511512
ngx_pool_t *pool;
512513
njs_value_t *init, *value;
513514
ngx_js_http_t *http;
@@ -595,12 +596,30 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
595596
NJS_CHB_MP_INIT(&http->chain, njs_vm_memory_pool(vm));
596597
NJS_CHB_MP_INIT(&http->response.chain, njs_vm_memory_pool(vm));
597598

598-
ngx_js_fetch_build_request(http, &request, &u.uri, &u);
599+
ngx_js_fetch_build_request(http, &request, &u.uri, &u,
600+
ngx_js_proxy(http->conf) && !ngx_js_https(&u));
601+
602+
resolve_host = NULL;
603+
http->connect_port = http->port;
604+
605+
if (ngx_js_proxy(http->conf)) {
606+
if (http->conf->fetch_proxy_url->addrs == NULL) {
607+
resolve_host = &http->conf->fetch_proxy_url->host;
608+
http->connect_port = http->conf->fetch_proxy_url->port;
609+
}
610+
611+
} else if (u.addrs == NULL) {
612+
resolve_host = &u.host;
613+
}
614+
615+
if (resolve_host != NULL) {
616+
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, http->log, 0,
617+
"js http fetch: resolving");
599618

600-
if (u.addrs == NULL) {
601619
ctx = ngx_js_http_resolve(http, ngx_external_resolver(vm, external),
602-
&u.host,
620+
resolve_host,
603621
ngx_external_resolver_timeout(vm, external));
622+
604623
if (ctx == NULL) {
605624
njs_vm_memory_error(vm);
606625
return NJS_ERROR;
@@ -617,9 +636,16 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
617636
}
618637

619638
http->naddrs = 1;
620-
ngx_memcpy(&http->addr, &u.addrs[0], sizeof(ngx_addr_t));
621639
http->addrs = &http->addr;
622640

641+
if (ngx_js_proxy(http->conf)) {
642+
ngx_memcpy(&http->addr, &http->conf->fetch_proxy_url->addrs[0],
643+
sizeof(ngx_addr_t));
644+
645+
} else {
646+
ngx_memcpy(&http->addr, &u.addrs[0], sizeof(ngx_addr_t));
647+
}
648+
623649
ngx_js_http_connect(http);
624650

625651
njs_value_assign(retval, njs_value_arg(&fetch->promise));
@@ -1031,6 +1057,12 @@ ngx_js_fetch_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log,
10311057
goto failed;
10321058
}
10331059

1060+
/*
1061+
* set by ngx_pcalloc():
1062+
*
1063+
* fetch->http.proxy.state = HTTP_STATE_DIRECT;
1064+
*/
1065+
10341066
http = &fetch->http;
10351067

10361068
http->pool = pool;

0 commit comments

Comments
 (0)