blob: 8897b3aef9b2ca83e19e97b71b281e8a42c8683c [file] [log] [blame]
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Dmitry Volyntsev
* Copyright (C) NGINX, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_stream.h>
#include "ngx_js.h"
typedef struct {
njs_vm_t *vm;
ngx_array_t *imports;
ngx_array_t *paths;
} ngx_stream_js_main_conf_t;
typedef struct {
ngx_str_t name;
ngx_str_t path;
u_char *file;
ngx_uint_t line;
} ngx_stream_js_import_t;
typedef struct {
ngx_str_t access;
ngx_str_t preread;
ngx_str_t filter;
size_t buffer_size;
size_t max_response_body_size;
ngx_msec_t timeout;
#if (NGX_STREAM_SSL)
ngx_ssl_t *ssl;
ngx_str_t ssl_ciphers;
ngx_flag_t ssl_verify;
ngx_uint_t ssl_protocols;
ngx_int_t ssl_verify_depth;
ngx_str_t ssl_trusted_certificate;
#endif
} ngx_stream_js_srv_conf_t;
typedef struct {
njs_vm_event_t ev;
ngx_uint_t data_type;
} ngx_stream_js_ev_t;
typedef struct {
njs_vm_t *vm;
njs_opaque_value_t retval;
njs_opaque_value_t args[3];
ngx_buf_t *buf;
ngx_chain_t **last_out;
ngx_chain_t *free;
ngx_chain_t *upstream_busy;
ngx_chain_t *downstream_busy;
ngx_int_t status;
#define NGX_JS_EVENT_UPLOAD 0
#define NGX_JS_EVENT_DOWNLOAD 1
#define NGX_JS_EVENT_MAX 2
ngx_stream_js_ev_t events[2];
unsigned from_upstream:1;
unsigned filter:1;
unsigned in_progress:1;
} ngx_stream_js_ctx_t;
typedef struct {
ngx_stream_session_t *session;
njs_vm_event_t vm_event;
void *unused;
ngx_int_t ident;
} ngx_stream_js_event_t;
static ngx_int_t ngx_stream_js_access_handler(ngx_stream_session_t *s);
static ngx_int_t ngx_stream_js_preread_handler(ngx_stream_session_t *s);
static ngx_int_t ngx_stream_js_phase_handler(ngx_stream_session_t *s,
ngx_str_t *name);
static ngx_int_t ngx_stream_js_body_filter(ngx_stream_session_t *s,
ngx_chain_t *in, ngx_uint_t from_upstream);
static ngx_int_t ngx_stream_js_variable_set(ngx_stream_session_t *s,
ngx_stream_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_stream_js_variable_var(ngx_stream_session_t *s,
ngx_stream_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_stream_js_init_vm(ngx_stream_session_t *s);
static void ngx_stream_js_drop_events(ngx_stream_js_ctx_t *ctx);
static void ngx_stream_js_cleanup(void *data);
static void ngx_stream_js_cleanup_vm(void *data);
static njs_int_t ngx_stream_js_run_event(ngx_stream_session_t *s,
ngx_stream_js_ctx_t *ctx, ngx_stream_js_ev_t *event);
static njs_vm_event_t *ngx_stream_js_event(ngx_stream_session_t *s,
njs_str_t *event);
static njs_int_t ngx_stream_js_ext_get_remote_address(njs_vm_t *vm,
njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval,
njs_value_t *retval);
static njs_int_t ngx_stream_js_ext_done(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t unused);
static njs_int_t ngx_stream_js_ext_on(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t unused);
static njs_int_t ngx_stream_js_ext_off(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t unused);
static njs_int_t ngx_stream_js_ext_send(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t unused);
static njs_int_t ngx_stream_js_ext_set_return_value(njs_vm_t *vm,
njs_value_t *args, njs_uint_t nargs, njs_index_t unused);
static njs_int_t ngx_stream_js_ext_variables(njs_vm_t *vm,
njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval,
njs_value_t *retval);
static njs_host_event_t ngx_stream_js_set_timer(njs_external_ptr_t external,
uint64_t delay, njs_vm_event_t vm_event);
static void ngx_stream_js_clear_timer(njs_external_ptr_t external,
njs_host_event_t event);
static void ngx_stream_js_timer_handler(ngx_event_t *ev);
static ngx_pool_t *ngx_stream_js_pool(njs_vm_t *vm, ngx_stream_session_t *s);
static ngx_resolver_t *ngx_stream_js_resolver(njs_vm_t *vm,
ngx_stream_session_t *s);
static ngx_msec_t ngx_stream_js_resolver_timeout(njs_vm_t *vm,
ngx_stream_session_t *s);
static ngx_msec_t ngx_stream_js_fetch_timeout(njs_vm_t *vm,
ngx_stream_session_t *s);
static size_t ngx_stream_js_buffer_size(njs_vm_t *vm, ngx_stream_session_t *s);
static size_t ngx_stream_js_max_response_buffer_size(njs_vm_t *vm,
ngx_stream_session_t *s);
static void ngx_stream_js_handle_event(ngx_stream_session_t *s,
njs_vm_event_t vm_event, njs_value_t *args, njs_uint_t nargs);
static char *ngx_stream_js_import(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_stream_js_set(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_stream_js_var(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static void *ngx_stream_js_create_main_conf(ngx_conf_t *cf);
static char *ngx_stream_js_init_main_conf(ngx_conf_t *cf, void *conf);
static void *ngx_stream_js_create_srv_conf(ngx_conf_t *cf);
static char *ngx_stream_js_merge_srv_conf(ngx_conf_t *cf, void *parent,
void *child);
static ngx_int_t ngx_stream_js_init(ngx_conf_t *cf);
#if (NGX_STREAM_SSL)
static char * ngx_stream_js_set_ssl(ngx_conf_t *cf,
ngx_stream_js_srv_conf_t *jscf);
#endif
static ngx_ssl_t *ngx_stream_js_ssl(njs_vm_t *vm, ngx_stream_session_t *s);
static ngx_flag_t ngx_stream_js_ssl_verify(njs_vm_t *vm,
ngx_stream_session_t *s);
#if (NGX_STREAM_SSL)
static ngx_conf_bitmask_t ngx_stream_js_ssl_protocols[] = {
{ ngx_string("TLSv1"), NGX_SSL_TLSv1 },
{ ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 },
{ ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 },
{ ngx_string("TLSv1.3"), NGX_SSL_TLSv1_3 },
{ ngx_null_string, 0 }
};
#endif
static ngx_command_t ngx_stream_js_commands[] = {
{ ngx_string("js_import"),
NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE13,
ngx_stream_js_import,
NGX_STREAM_MAIN_CONF_OFFSET,
0,
NULL },
{ ngx_string("js_path"),
NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_array_slot,
NGX_STREAM_MAIN_CONF_OFFSET,
offsetof(ngx_stream_js_main_conf_t, paths),
NULL },
{ ngx_string("js_set"),
NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE2,
ngx_stream_js_set,
0,
0,
NULL },
{ ngx_string("js_var"),
NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE12,
ngx_stream_js_var,
0,
0,
NULL },
{ ngx_string("js_access"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_stream_js_srv_conf_t, access),
NULL },
{ ngx_string("js_preread"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_stream_js_srv_conf_t, preread),
NULL },
{ ngx_string("js_filter"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_stream_js_srv_conf_t, filter),
NULL },
{ ngx_string("js_fetch_buffer_size"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_size_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_stream_js_srv_conf_t, buffer_size),
NULL },
{ ngx_string("js_fetch_max_response_buffer_size"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_size_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_stream_js_srv_conf_t, max_response_body_size),
NULL },
{ ngx_string("js_fetch_timeout"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_stream_js_srv_conf_t, timeout),
NULL },
#if (NGX_STREAM_SSL)
{ ngx_string("js_fetch_ciphers"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_stream_js_srv_conf_t, ssl_ciphers),
NULL },
{ ngx_string("js_fetch_protocols"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE,
ngx_conf_set_bitmask_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_stream_js_srv_conf_t, ssl_protocols),
&ngx_stream_js_ssl_protocols },
{ ngx_string("js_fetch_verify"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_stream_js_srv_conf_t, ssl_verify),
NULL },
{ ngx_string("js_fetch_verify_depth"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_stream_js_srv_conf_t, ssl_verify_depth),
NULL },
{ ngx_string("js_fetch_trusted_certificate"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_stream_js_srv_conf_t, ssl_trusted_certificate),
NULL },
#endif
ngx_null_command
};
static ngx_stream_module_t ngx_stream_js_module_ctx = {
NULL, /* preconfiguration */
ngx_stream_js_init, /* postconfiguration */
ngx_stream_js_create_main_conf, /* create main configuration */
ngx_stream_js_init_main_conf, /* init main configuration */
ngx_stream_js_create_srv_conf, /* create server configuration */
ngx_stream_js_merge_srv_conf, /* merge server configuration */
};
ngx_module_t ngx_stream_js_module = {
NGX_MODULE_V1,
&ngx_stream_js_module_ctx, /* module context */
ngx_stream_js_commands, /* module directives */
NGX_STREAM_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 njs_external_t ngx_stream_js_ext_session[] = {
{
.flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
.name.symbol = NJS_SYMBOL_TO_STRING_TAG,
.u.property = {
.value = "Stream Session",
}
},
{
.flags = NJS_EXTERN_PROPERTY,
.name.string = njs_str("status"),
.enumerable = 1,
.u.property = {
.handler = ngx_js_ext_uint,
.magic32 = offsetof(ngx_stream_session_t, status),
}
},
{
.flags = NJS_EXTERN_PROPERTY,
.name.string = njs_str("remoteAddress"),
.enumerable = 1,
.u.property = {
.handler = ngx_stream_js_ext_get_remote_address,
}
},
{
.flags = NJS_EXTERN_OBJECT,
.name.string = njs_str("variables"),
.u.object = {
.writable = 1,
.prop_handler = ngx_stream_js_ext_variables,
.magic32 = NGX_JS_STRING,
}
},
{
.flags = NJS_EXTERN_OBJECT,
.name.string = njs_str("rawVariables"),
.u.object = {
.writable = 1,
.prop_handler = ngx_stream_js_ext_variables,
.magic32 = NGX_JS_BUFFER,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("allow"),
.writable = 1,
.configurable = 1,
.enumerable = 1,
.u.method = {
.native = ngx_stream_js_ext_done,
.magic8 = NGX_OK,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("deny"),
.writable = 1,
.configurable = 1,
.enumerable = 1,
.u.method = {
.native = ngx_stream_js_ext_done,
.magic8 = -NGX_DONE,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("decline"),
.writable = 1,
.configurable = 1,
.enumerable = 1,
.u.method = {
.native = ngx_stream_js_ext_done,
.magic8 = -NGX_DECLINED,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("done"),
.writable = 1,
.configurable = 1,
.enumerable = 1,
.u.method = {
.native = ngx_stream_js_ext_done,
.magic8 = NGX_OK,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("log"),
.writable = 1,
.configurable = 1,
.enumerable = 1,
.u.method = {
.native = ngx_js_ext_log,
.magic8 = NGX_LOG_INFO,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("warn"),
.writable = 1,
.configurable = 1,
.enumerable = 1,
.u.method = {
.native = ngx_js_ext_log,
.magic8 = NGX_LOG_WARN,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("error"),
.writable = 1,
.configurable = 1,
.enumerable = 1,
.u.method = {
.native = ngx_js_ext_log,
.magic8 = NGX_LOG_ERR,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("on"),
.writable = 1,
.configurable = 1,
.enumerable = 1,
.u.method = {
.native = ngx_stream_js_ext_on,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("off"),
.writable = 1,
.configurable = 1,
.enumerable = 1,
.u.method = {
.native = ngx_stream_js_ext_off,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("send"),
.writable = 1,
.configurable = 1,
.enumerable = 1,
.u.method = {
.native = ngx_stream_js_ext_send,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("setReturnValue"),
.writable = 1,
.configurable = 1,
.enumerable = 1,
.u.method = {
.native = ngx_stream_js_ext_set_return_value,
}
},
};
static njs_vm_ops_t ngx_stream_js_ops = {
ngx_stream_js_set_timer,
ngx_stream_js_clear_timer,
NULL,
};
static uintptr_t ngx_stream_js_uptr[] = {
offsetof(ngx_stream_session_t, connection),
(uintptr_t) ngx_stream_js_pool,
(uintptr_t) ngx_stream_js_resolver,
(uintptr_t) ngx_stream_js_resolver_timeout,
(uintptr_t) ngx_stream_js_handle_event,
(uintptr_t) ngx_stream_js_ssl,
(uintptr_t) ngx_stream_js_ssl_verify,
(uintptr_t) ngx_stream_js_fetch_timeout,
(uintptr_t) ngx_stream_js_buffer_size,
(uintptr_t) ngx_stream_js_max_response_buffer_size,
};
static njs_vm_meta_t ngx_stream_js_metas = {
.size = njs_nitems(ngx_stream_js_uptr),
.values = ngx_stream_js_uptr
};
static ngx_stream_filter_pt ngx_stream_next_filter;
static njs_int_t ngx_stream_js_session_proto_id;
static ngx_int_t
ngx_stream_js_access_handler(ngx_stream_session_t *s)
{
ngx_stream_js_srv_conf_t *jscf;
ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
"js access handler");
jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module);
return ngx_stream_js_phase_handler(s, &jscf->access);
}
static ngx_int_t
ngx_stream_js_preread_handler(ngx_stream_session_t *s)
{
ngx_stream_js_srv_conf_t *jscf;
ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
"js preread handler");
jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module);
return ngx_stream_js_phase_handler(s, &jscf->preread);
}
static ngx_int_t
ngx_stream_js_phase_handler(ngx_stream_session_t *s, ngx_str_t *name)
{
ngx_str_t exception;
njs_int_t ret;
ngx_int_t rc;
ngx_connection_t *c;
ngx_stream_js_ctx_t *ctx;
if (name->len == 0) {
return NGX_DECLINED;
}
rc = ngx_stream_js_init_vm(s);
if (rc != NGX_OK) {
return rc;
}
c = s->connection;
ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0,
"stream js phase call \"%V\"", name);
ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
if (!ctx->in_progress) {
/*
* status is expected to be overriden by allow(), deny(), decline() or
* done() methods.
*/
ctx->status = NGX_ERROR;
rc = ngx_js_call(ctx->vm, name, c->log, &ctx->args[0], 1);
if (rc == NGX_ERROR) {
return rc;
}
}
ret = ngx_stream_js_run_event(s, ctx, &ctx->events[NGX_JS_EVENT_UPLOAD]);
if (ret != NJS_OK) {
ngx_js_retval(ctx->vm, NULL, &exception);
ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %V",
&exception);
return NGX_ERROR;
}
if (njs_vm_pending(ctx->vm)) {
ctx->in_progress = 1;
rc = ctx->events[NGX_JS_EVENT_UPLOAD].ev ? NGX_AGAIN : NGX_DONE;
} else {
ctx->in_progress = 0;
rc = ctx->status;
}
ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream js phase rc: %i",
rc);
return rc;
}
#define ngx_stream_event(from_upstream) \
(from_upstream ? &ctx->events[NGX_JS_EVENT_DOWNLOAD] \
: &ctx->events[NGX_JS_EVENT_UPLOAD])
static ngx_int_t
ngx_stream_js_body_filter(ngx_stream_session_t *s, ngx_chain_t *in,
ngx_uint_t from_upstream)
{
ngx_str_t exception;
njs_int_t ret;
ngx_int_t rc;
ngx_chain_t *out, *cl, **busy;
ngx_connection_t *c, *dst;
ngx_stream_js_ev_t *event;
ngx_stream_js_ctx_t *ctx;
ngx_stream_js_srv_conf_t *jscf;
jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module);
if (jscf->filter.len == 0) {
return ngx_stream_next_filter(s, in, from_upstream);
}
c = s->connection;
ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream js filter u:%ui",
from_upstream);
rc = ngx_stream_js_init_vm(s);
if (rc == NGX_ERROR) {
return NGX_ERROR;
}
if (rc == NGX_DECLINED) {
return ngx_stream_next_filter(s, in, from_upstream);
}
ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
if (!ctx->filter) {
rc = ngx_js_call(ctx->vm, &jscf->filter, c->log, &ctx->args[0], 1);
if (rc == NGX_ERROR) {
return rc;
}
}
ctx->filter = 1;
ctx->from_upstream = from_upstream;
ctx->last_out = &out;
while (in) {
ctx->buf = in->buf;
event = ngx_stream_event(from_upstream);
if (event->ev != NULL) {
ret = ngx_stream_js_run_event(s, ctx, event);
if (ret != NJS_OK) {
ngx_js_retval(ctx->vm, NULL, &exception);
ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %V",
&exception);
return NGX_ERROR;
}
ctx->buf->pos = ctx->buf->last;
} else {
cl = ngx_alloc_chain_link(c->pool);
if (cl == NULL) {
return NGX_ERROR;
}
cl->buf = ctx->buf;
*ctx->last_out = cl;
ctx->last_out = &cl->next;
}
in = in->next;
}
*ctx->last_out = NULL;
if (from_upstream) {
dst = c;
busy = &ctx->downstream_busy;
} else {
dst = s->upstream ? s->upstream->peer.connection : NULL;
busy = &ctx->upstream_busy;
}
if (out != NULL || dst == NULL || dst->buffered) {
rc = ngx_stream_next_filter(s, out, from_upstream);
ngx_chain_update_chains(c->pool, &ctx->free, busy, &out,
(ngx_buf_tag_t) &ngx_stream_js_module);
} else {
rc = NGX_OK;
}
return rc;
}
static ngx_int_t
ngx_stream_js_variable_set(ngx_stream_session_t *s,
ngx_stream_variable_value_t *v, uintptr_t data)
{
ngx_str_t *fname = (ngx_str_t *) data;
ngx_int_t rc;
njs_int_t pending;
ngx_str_t value;
ngx_stream_js_ctx_t *ctx;
rc = ngx_stream_js_init_vm(s);
if (rc == NGX_ERROR) {
return NGX_ERROR;
}
if (rc == NGX_DECLINED) {
v->not_found = 1;
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
"stream js variable call \"%V\"", fname);
ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
pending = njs_vm_pending(ctx->vm);
rc = ngx_js_call(ctx->vm, fname, s->connection->log, &ctx->args[0], 1);
if (rc == NGX_ERROR) {
v->not_found = 1;
return NGX_OK;
}
if (!pending && rc == NGX_AGAIN) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"async operation inside \"%V\" variable handler", fname);
return NGX_ERROR;
}
if (ngx_js_retval(ctx->vm, &ctx->retval, &value) != NGX_OK) {
return NGX_ERROR;
}
v->len = value.len;
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
v->data = value.data;
return NGX_OK;
}
static ngx_int_t
ngx_stream_js_variable_var(ngx_stream_session_t *s,
ngx_stream_variable_value_t *v, uintptr_t data)
{
ngx_stream_complex_value_t *cv = (ngx_stream_complex_value_t *) data;
ngx_str_t value;
if (cv != NULL) {
if (ngx_stream_complex_value(s, cv, &value) != NGX_OK) {
return NGX_ERROR;
}
} else {
ngx_str_null(&value);
}
v->len = value.len;
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
v->data = value.data;
return NGX_OK;
}
static ngx_int_t
ngx_stream_js_init_vm(ngx_stream_session_t *s)
{
njs_int_t rc;
ngx_str_t exception;
ngx_pool_cleanup_t *cln;
ngx_stream_js_ctx_t *ctx;
ngx_stream_js_main_conf_t *jmcf;
jmcf = ngx_stream_get_module_main_conf(s, ngx_stream_js_module);
if (jmcf->vm == NULL) {
return NGX_DECLINED;
}
ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
if (ctx == NULL) {
ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_stream_js_ctx_t));
if (ctx == NULL) {
return NGX_ERROR;
}
njs_value_invalid_set(njs_value_arg(&ctx->retval));
ngx_stream_set_ctx(s, ctx, ngx_stream_js_module);
}
if (ctx->vm) {
return NGX_OK;
}
ctx->vm = njs_vm_clone(jmcf->vm, s);
if (ctx->vm == NULL) {
return NGX_ERROR;
}
cln = ngx_pool_cleanup_add(s->connection->pool, 0);
if (cln == NULL) {
return NGX_ERROR;
}
cln->handler = ngx_stream_js_cleanup;
cln->data = s;
if (njs_vm_start(ctx->vm) == NJS_ERROR) {
ngx_js_retval(ctx->vm, NULL, &exception);
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"js exception: %V", &exception);
return NGX_ERROR;
}
rc = njs_vm_external_create(ctx->vm, njs_value_arg(&ctx->args[0]),
ngx_stream_js_session_proto_id, s, 0);
if (rc != NJS_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
static void
ngx_stream_js_drop_events(ngx_stream_js_ctx_t *ctx)
{
ngx_uint_t i;
for (i = 0; i < NGX_JS_EVENT_MAX; i++) {
if (ctx->events[i].ev != NULL) {
njs_vm_del_event(ctx->vm, ctx->events[i].ev);
ctx->events[i].ev = NULL;
}
}
}
static void
ngx_stream_js_cleanup(void *data)
{
ngx_stream_js_ctx_t *ctx;
ngx_stream_session_t *s = data;
ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
ngx_stream_js_drop_events(ctx);
if (njs_vm_pending(ctx->vm)) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "pending events");
}
njs_vm_destroy(ctx->vm);
}
static void
ngx_stream_js_cleanup_vm(void *data)
{
njs_vm_t *vm = data;
njs_vm_destroy(vm);
}
static njs_int_t
ngx_stream_js_run_event(ngx_stream_session_t *s, ngx_stream_js_ctx_t *ctx,
ngx_stream_js_ev_t *event)
{
size_t len;
u_char *p;
njs_int_t ret;
ngx_buf_t *b;
ngx_connection_t *c;
njs_opaque_value_t last_key, last;
static const njs_str_t last_str = njs_str("last");
if (event->ev == NULL) {
return NJS_OK;
}
c = s->connection;
b = ctx->filter ? ctx->buf : c->buffer;
len = b ? b->last - b->pos : 0;
p = ngx_pnalloc(c->pool, len);
if (p == NULL) {
njs_vm_memory_error(ctx->vm);
return NJS_ERROR;
}
if (len) {
ngx_memcpy(p, b->pos, len);
}
ret = ngx_js_prop(ctx->vm, event->data_type, njs_value_arg(&ctx->args[1]),
p, len);
if (ret != NJS_OK) {
return ret;
}
njs_vm_value_string_set(ctx->vm, njs_value_arg(&last_key), last_str.start,
last_str.length);
njs_value_boolean_set(njs_value_arg(&last), b && b->last_buf);
ret = njs_vm_object_alloc(ctx->vm, njs_value_arg(&ctx->args[2]),
njs_value_arg(&last_key),
njs_value_arg(&last), NULL);
if (ret != NJS_OK) {
return ret;
}
njs_vm_post_event(ctx->vm, event->ev, njs_value_arg(&ctx->args[1]), 2);
ret = njs_vm_run(ctx->vm);
if (ret == NJS_ERROR) {
return ret;
}
return NJS_OK;
}
static njs_vm_event_t *
ngx_stream_js_event(ngx_stream_session_t *s, njs_str_t *event)
{
ngx_uint_t i, n, type;
ngx_stream_js_ctx_t *ctx;
static const struct {
ngx_str_t name;
ngx_uint_t data_type;
ngx_uint_t id;
} events[] = {
{
ngx_string("upload"),
NGX_JS_STRING,
NGX_JS_EVENT_UPLOAD,
},
{
ngx_string("download"),
NGX_JS_STRING,
NGX_JS_EVENT_DOWNLOAD,
},
{
ngx_string("upstream"),
NGX_JS_BUFFER,
NGX_JS_EVENT_UPLOAD,
},
{
ngx_string("downstream"),
NGX_JS_BUFFER,
NGX_JS_EVENT_DOWNLOAD,
},
};
ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
i = 0;
n = sizeof(events) / sizeof(events[0]);
while (i < n) {
if (event->length == events[i].name.len
&& ngx_memcmp(event->start, events[i].name.data, event->length)
== 0)
{
break;
}
i++;
}
if (i == n) {
njs_vm_error(ctx->vm, "unknown event \"%V\"", event);
return NULL;
}
ctx->events[events[i].id].data_type = events[i].data_type;
for (n = 0; n < NGX_JS_EVENT_MAX; n++) {
type = ctx->events[n].data_type;
if (type != NGX_JS_UNSET && type != events[i].data_type) {
njs_vm_error(ctx->vm, "mixing string and buffer events"
" is not allowed");
return NULL;
}
}
return &ctx->events[events[i].id].ev;
}
static njs_int_t
ngx_stream_js_ext_get_remote_address(njs_vm_t *vm,
njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval,
njs_value_t *retval)
{
ngx_connection_t *c;
ngx_stream_session_t *s;
s = njs_vm_external(vm, ngx_stream_js_session_proto_id, value);
if (s == NULL) {
njs_value_undefined_set(retval);
return NJS_DECLINED;
}
c = s->connection;
return njs_vm_value_string_set(vm, retval, c->addr_text.data,
c->addr_text.len);
}
static njs_int_t
ngx_stream_js_ext_done(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t magic)
{
ngx_int_t status;
njs_value_t *code;
ngx_stream_js_ctx_t *ctx;
ngx_stream_session_t *s;
s = njs_vm_external(vm, ngx_stream_js_session_proto_id,
njs_argument(args, 0));
if (s == NULL) {
njs_vm_error(vm, "\"this\" is not an external");
return NJS_ERROR;
}
status = (ngx_int_t) magic;
status = -status;
if (status == NGX_DONE) {
status = NGX_STREAM_FORBIDDEN;
}
code = njs_arg(args, nargs, 1);
if (!njs_value_is_undefined(code)) {
if (ngx_js_integer(vm, code, &status) != NGX_OK) {
return NJS_ERROR;
}
if (status < NGX_ABORT || status > NGX_STREAM_SERVICE_UNAVAILABLE) {
njs_vm_error(vm, "code is out of range");
return NJS_ERROR;
}
}
ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
"stream js set status: %i", status);
ctx->status = status;
ngx_stream_js_drop_events(ctx);
njs_value_undefined_set(njs_vm_retval(vm));
return NJS_OK;
}
static njs_int_t
ngx_stream_js_ext_on(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
njs_str_t name;
njs_value_t *callback;
njs_vm_event_t *event;
ngx_stream_session_t *s;
s = njs_vm_external(vm, ngx_stream_js_session_proto_id,
njs_argument(args, 0));
if (s == NULL) {
njs_vm_error(vm, "\"this\" is not an external");
return NJS_ERROR;
}
if (ngx_js_string(vm, njs_arg(args, nargs, 1), &name) == NJS_ERROR) {
njs_vm_error(vm, "failed to convert event arg");
return NJS_ERROR;
}
callback = njs_arg(args, nargs, 2);
if (!njs_value_is_function(callback)) {
njs_vm_error(vm, "callback is not a function");
return NJS_ERROR;
}
event = ngx_stream_js_event(s, &name);
if (event == NULL) {
return NJS_ERROR;
}
if (*event != NULL) {
njs_vm_error(vm, "event handler \"%V\" is already set", &name);
return NJS_ERROR;
}
*event = njs_vm_add_event(vm, njs_value_function(callback), 0, NULL, NULL);
if (*event == NULL) {
njs_vm_error(vm, "internal error");
return NJS_ERROR;
}
njs_value_undefined_set(njs_vm_retval(vm));
return NJS_OK;
}
static njs_int_t
ngx_stream_js_ext_off(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
njs_str_t name;
njs_vm_event_t *event;
ngx_stream_session_t *s;
s = njs_vm_external(vm, ngx_stream_js_session_proto_id,
njs_argument(args, 0));
if (s == NULL) {
njs_vm_error(vm, "\"this\" is not an external");
return NJS_ERROR;
}
if (ngx_js_string(vm, njs_arg(args, nargs, 1), &name) == NJS_ERROR) {
njs_vm_error(vm, "failed to convert event arg");
return NJS_ERROR;
}
event = ngx_stream_js_event(s, &name);
if (event == NULL) {
return NJS_ERROR;
}
njs_vm_del_event(vm, *event);
*event = NULL;
njs_value_undefined_set(njs_vm_retval(vm));
return NJS_OK;
}
static njs_int_t
ngx_stream_js_ext_send(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
unsigned last_buf, flush;
njs_str_t buffer;
ngx_buf_t *b;
njs_value_t *flags, *value;
ngx_chain_t *cl;
ngx_connection_t *c;
njs_opaque_value_t lvalue;
ngx_stream_js_ctx_t *ctx;
ngx_stream_session_t *s;
static const njs_str_t last_key = njs_str("last");
static const njs_str_t flush_key = njs_str("flush");
s = njs_vm_external(vm, ngx_stream_js_session_proto_id,
njs_argument(args, 0));
if (s == NULL) {
njs_vm_error(vm, "\"this\" is not an external");
return NJS_ERROR;
}
c = s->connection;
ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
if (!ctx->filter) {
njs_vm_error(vm, "cannot send buffer in this handler");
return NJS_ERROR;
}
if (ngx_js_string(vm, njs_arg(args, nargs, 1), &buffer) != NGX_OK) {
njs_vm_error(vm, "failed to get buffer arg");
return NJS_ERROR;
}
flush = ctx->buf->flush;
last_buf = ctx->buf->last_buf;
flags = njs_arg(args, nargs, 2);
if (njs_value_is_object(flags)) {
value = njs_vm_object_prop(vm, flags, &flush_key, &lvalue);
if (value != NULL) {
flush = njs_value_bool(value);
}
value = njs_vm_object_prop(vm, flags, &last_key, &lvalue);
if (value != NULL) {
last_buf = njs_value_bool(value);
}
}
cl = ngx_chain_get_free_buf(c->pool, &ctx->free);
if (cl == NULL) {
njs_vm_error(vm, "memory error");
return NJS_ERROR;
}
b = cl->buf;
b->flush = flush;
b->last_buf = last_buf;
b->memory = (buffer.length ? 1 : 0);
b->sync = (buffer.length ? 0 : 1);
b->tag = (ngx_buf_tag_t) &ngx_stream_js_module;
b->start = buffer.start;
b->end = buffer.start + buffer.length;
b->pos = b->start;
b->last = b->end;
*ctx->last_out = cl;
ctx->last_out = &cl->next;
njs_value_undefined_set(njs_vm_retval(vm));
return NJS_OK;
}
static njs_int_t
ngx_stream_js_ext_set_return_value(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t unused)
{
ngx_stream_js_ctx_t *ctx;
ngx_stream_session_t *s;
s = njs_vm_external(vm, ngx_stream_js_session_proto_id,
njs_argument(args, 0));
if (s == NULL) {
njs_vm_error(vm, "\"this\" is not an external");
return NJS_ERROR;
}
ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
njs_value_assign(&ctx->retval, njs_arg(args, nargs, 1));
njs_value_undefined_set(njs_vm_retval(vm));
return NJS_OK;
}
static njs_int_t
ngx_stream_js_ext_variables(njs_vm_t *vm, njs_object_prop_t *prop,
njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
{
njs_int_t rc;
njs_str_t val;
ngx_str_t name;
ngx_uint_t key;
ngx_stream_variable_t *v;
ngx_stream_session_t *s;
ngx_stream_core_main_conf_t *cmcf;
ngx_stream_variable_value_t *vv;
s = njs_vm_external(vm, ngx_stream_js_session_proto_id, value);
if (s == NULL) {
njs_value_undefined_set(retval);
return NJS_DECLINED;
}
rc = njs_vm_prop_name(vm, prop, &val);
if (rc != NJS_OK) {
njs_value_undefined_set(retval);
return NJS_DECLINED;
}
name.data = val.start;
name.len = val.length;
if (setval == NULL) {
key = ngx_hash_strlow(name.data, name.data, name.len);
vv = ngx_stream_get_variable(s, &name, key);
if (vv == NULL || vv->not_found) {
njs_value_undefined_set(retval);
return NJS_DECLINED;
}
return ngx_js_prop(vm, njs_vm_prop_magic32(prop), retval, vv->data,
vv->len);
}
cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
key = ngx_hash_strlow(name.data, name.data, name.len);
v = ngx_hash_find(&cmcf->variables_hash, key, name.data, name.len);
if (v == NULL) {
njs_vm_error(vm, "variable not found");
return NJS_ERROR;
}
if (ngx_js_string(vm, setval, &val) != NGX_OK) {
return NJS_ERROR;
}
if (v->set_handler != NULL) {
vv = ngx_pcalloc(s->connection->pool,
sizeof(ngx_stream_variable_value_t));
if (vv == NULL) {
return NJS_ERROR;
}
vv->valid = 1;
vv->not_found = 0;
vv->data = val.start;
vv->len = val.length;
v->set_handler(s, vv, v->data);
return NJS_OK;
}
if (!(v->flags & NGX_STREAM_VAR_INDEXED)) {
njs_vm_error(vm, "variable is not writable");
return NJS_ERROR;
}
vv = &s->variables[v->index];
vv->valid = 1;
vv->not_found = 0;
vv->data = ngx_pnalloc(s->connection->pool, val.length);
if (vv->data == NULL) {
return NJS_ERROR;
}
vv->len = val.length;
ngx_memcpy(vv->data, val.start, vv->len);
return NJS_OK;
}
static njs_host_event_t
ngx_stream_js_set_timer(njs_external_ptr_t external, uint64_t delay,
njs_vm_event_t vm_event)
{
ngx_event_t *ev;
ngx_stream_session_t *s;
ngx_stream_js_event_t *js_event;
s = (ngx_stream_session_t *) external;
ev = ngx_pcalloc(s->connection->pool, sizeof(ngx_event_t));
if (ev == NULL) {
return NULL;
}
js_event = ngx_palloc(s->connection->pool, sizeof(ngx_stream_js_event_t));
if (js_event == NULL) {
return NULL;
}
js_event->session = s;
js_event->vm_event = vm_event;
js_event->ident = s->connection->fd;
ev->data = js_event;
ev->log = s->connection->log;
ev->handler = ngx_stream_js_timer_handler;
ngx_add_timer(ev, delay);
return ev;
}
static void
ngx_stream_js_clear_timer(njs_external_ptr_t external, njs_host_event_t event)
{
ngx_event_t *ev = event;
if (ev->timer_set) {
ngx_del_timer(ev);
}
}
static void
ngx_stream_js_timer_handler(ngx_event_t *ev)
{
ngx_stream_session_t *s;
ngx_stream_js_event_t *js_event;
js_event = (ngx_stream_js_event_t *) ev->data;
s = js_event->session;
ngx_stream_js_handle_event(s, js_event->vm_event, NULL, 0);
}
static ngx_pool_t *
ngx_stream_js_pool(njs_vm_t *vm, ngx_stream_session_t *s)
{
return s->connection->pool;
}
static ngx_resolver_t *
ngx_stream_js_resolver(njs_vm_t *vm, ngx_stream_session_t *s)
{
ngx_stream_core_srv_conf_t *cscf;
cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module);
return cscf->resolver;
}
static ngx_msec_t
ngx_stream_js_resolver_timeout(njs_vm_t *vm, ngx_stream_session_t *s)
{
ngx_stream_core_srv_conf_t *cscf;
cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module);
return cscf->resolver_timeout;
}
static ngx_msec_t
ngx_stream_js_fetch_timeout(njs_vm_t *vm, ngx_stream_session_t *s)
{
ngx_stream_core_srv_conf_t *cscf;
cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module);
return cscf->resolver_timeout;
}
static size_t
ngx_stream_js_buffer_size(njs_vm_t *vm, ngx_stream_session_t *s)
{
ngx_stream_js_srv_conf_t *jscf;
jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module);
return jscf->buffer_size;
}
static size_t
ngx_stream_js_max_response_buffer_size(njs_vm_t *vm, ngx_stream_session_t *s)
{
ngx_stream_js_srv_conf_t *jscf;
jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module);
return jscf->max_response_body_size;
}
static void
ngx_stream_js_handle_event(ngx_stream_session_t *s, njs_vm_event_t vm_event,
njs_value_t *args, njs_uint_t nargs)
{
njs_int_t rc;
ngx_str_t exception;
ngx_stream_js_ctx_t *ctx;
ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
njs_vm_post_event(ctx->vm, vm_event, args, nargs);
rc = njs_vm_run(ctx->vm);
if (rc == NJS_ERROR) {
ngx_js_retval(ctx->vm, NULL, &exception);
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"js exception: %V", &exception);
ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
}
if (rc == NJS_OK) {
ngx_post_event(s->connection->read, &ngx_posted_events);
}
}
static char *
ngx_stream_js_init_main_conf(ngx_conf_t *cf, void *conf)
{
ngx_stream_js_main_conf_t *jmcf = conf;
size_t size;
u_char *start, *end, *p;
ngx_str_t *m, file;
njs_int_t rc;
njs_str_t text, path;
ngx_uint_t i;
njs_value_t *value;
njs_vm_opt_t options;
ngx_pool_cleanup_t *cln;
njs_opaque_value_t lvalue, exception;
ngx_stream_js_import_t *import;
static const njs_str_t line_number_key = njs_str("lineNumber");
static const njs_str_t file_name_key = njs_str("fileName");
if (jmcf->imports == NGX_CONF_UNSET_PTR) {
return NGX_CONF_OK;
}
size = 0;
import = jmcf->imports->elts;
for (i = 0; i < jmcf->imports->nelts; i++) {
/* import <name> from '<path>'; globalThis.<name> = <name>; */
size += sizeof("import from '';") - 1 + import[i].name.len * 3
+ import[i].path.len
+ sizeof(" globalThis. = ;\n") - 1;
}
start = ngx_pnalloc(cf->pool, size);
if (start == NULL) {
return NGX_CONF_ERROR;
}
p = start;
import = jmcf->imports->elts;
for (i = 0; i < jmcf->imports->nelts; i++) {
/* import <name> from '<path>'; globalThis.<name> = <name>; */
p = ngx_cpymem(p, "import ", sizeof("import ") - 1);
p = ngx_cpymem(p, import[i].name.data, import[i].name.len);
p = ngx_cpymem(p, " from '", sizeof(" from '") - 1);
p = ngx_cpymem(p, import[i].path.data, import[i].path.len);
p = ngx_cpymem(p, "'; globalThis.", sizeof("'; globalThis.") - 1);
p = ngx_cpymem(p, import[i].name.data, import[i].name.len);
p = ngx_cpymem(p, " = ", sizeof(" = ") - 1);
p = ngx_cpymem(p, import[i].name.data, import[i].name.len);
p = ngx_cpymem(p, ";\n", sizeof(";\n") - 1);
}
njs_vm_opt_init(&options);
options.backtrace = 1;
options.unhandled_rejection = NJS_VM_OPT_UNHANDLED_REJECTION_THROW;
options.ops = &ngx_stream_js_ops;
options.metas = &ngx_stream_js_metas;
options.addons = njs_js_addon_modules;
options.argv = ngx_argv;
options.argc = ngx_argc;
file = ngx_cycle->conf_prefix;
options.file.start = file.data;
options.file.length = file.len;
jmcf->vm = njs_vm_create(&options);
if (jmcf->vm == NULL) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "failed to create js VM");
return NGX_CONF_ERROR;
}
cln = ngx_pool_cleanup_add(cf->pool, 0);
if (cln == NULL) {
return NGX_CONF_ERROR;
}
cln->handler = ngx_stream_js_cleanup_vm;
cln->data = jmcf->vm;
path.start = ngx_cycle->conf_prefix.data;
path.length = ngx_cycle->conf_prefix.len;
rc = njs_vm_add_path(jmcf->vm, &path);
if (rc != NJS_OK) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "failed to add \"js_path\"");
return NGX_CONF_ERROR;
}
if (jmcf->paths != NGX_CONF_UNSET_PTR) {
m = jmcf->paths->elts;
for (i = 0; i < jmcf->paths->nelts; i++) {
if (ngx_conf_full_name(cf->cycle, &m[i], 1) != NGX_OK) {
return NGX_CONF_ERROR;
}
path.start = m[i].data;
path.length = m[i].len;
rc = njs_vm_add_path(jmcf->vm, &path);
if (rc != NJS_OK) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"failed to add \"js_path\"");
return NGX_CONF_ERROR;
}
}
}
ngx_stream_js_session_proto_id = njs_vm_external_prototype(jmcf->vm,
ngx_stream_js_ext_session,
njs_nitems(ngx_stream_js_ext_session));
if (ngx_stream_js_session_proto_id < 0) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"failed to add js request proto");
return NGX_CONF_ERROR;
}
rc = ngx_js_core_init(jmcf->vm, cf->log);
if (njs_slow_path(rc != NJS_OK)) {
return NGX_CONF_ERROR;
}
end = start + size;
rc = njs_vm_compile(jmcf->vm, &start, end);
if (rc != NJS_OK) {
njs_value_assign(&exception, njs_vm_retval(jmcf->vm));
njs_vm_retval_string(jmcf->vm, &text);
value = njs_vm_object_prop(jmcf->vm, njs_value_arg(&exception),
&file_name_key, &lvalue);
if (value == NULL) {
value = njs_vm_object_prop(jmcf->vm, njs_value_arg(&exception),
&line_number_key, &lvalue);
if (value != NULL) {
i = njs_value_number(value) - 1;
if (i < jmcf->imports->nelts) {
import = jmcf->imports->elts;
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"%*s, included in %s:%ui", text.length,
text.start, import[i].file, import[i].line);
return NGX_CONF_ERROR;
}
}
}
ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "%*s", text.length,
text.start);
return NGX_CONF_ERROR;
}
if (start != end) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"extra characters in js script: \"%*s\"",
end - start, start);
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
static char *
ngx_stream_js_import(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_stream_js_main_conf_t *jmcf = conf;
u_char *p, *end, c;
ngx_int_t from;
ngx_str_t *value, name, path;
ngx_stream_js_import_t *import;
value = cf->args->elts;
from = (cf->args->nelts == 4);
if (from) {
if (ngx_strcmp(value[2].data, "from") != 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid parameter \"%V\"", &value[2]);
return NGX_CONF_ERROR;
}
}
name = value[1];
path = (from ? value[3] : value[1]);
if (!from) {
end = name.data + name.len;
for (p = end - 1; p >= name.data; p--) {
if (*p == '/') {
break;
}
}
name.data = p + 1;
name.len = end - p - 1;
if (name.len < 3
|| ngx_memcmp(&name.data[name.len - 3], ".js", 3) != 0)
{
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"cannot extract export name from file path "
"\"%V\", use extended \"from\" syntax", &path);
return NGX_CONF_ERROR;
}
name.len -= 3;
}
if (name.len == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "empty \"name\" parameter");
return NGX_CONF_ERROR;
}
p = name.data;
end = name.data + name.len;
while (p < end) {
c = ngx_tolower(*p);
if (*p != '_' && (c < 'a' || c > 'z')) {
if (p == name.data) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "cannot start "
"with \"%c\" in export name \"%V\"", *p,
&name);
return NGX_CONF_ERROR;
}
if (*p < '0' || *p > '9') {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid character "
"\"%c\" in export name \"%V\"", *p, &name);
return NGX_CONF_ERROR;
}
}
p++;
}
if (ngx_strchr(path.data, '\'') != NULL) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid character \"'\" "
"in file path \"%V\"", &path);
return NGX_CONF_ERROR;
}
if (jmcf->imports == NGX_CONF_UNSET_PTR) {
jmcf->imports = ngx_array_create(cf->pool, 4,
sizeof(ngx_stream_js_import_t));
if (jmcf->imports == NULL) {
return NGX_CONF_ERROR;
}
}
import = ngx_array_push(jmcf->imports);
if (import == NULL) {
return NGX_CONF_ERROR;
}
import->name = name;
import->path = path;
import->file = cf->conf_file->file.name.data;
import->line = cf->conf_file->line;
return NGX_CONF_OK;
}
static char *
ngx_stream_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_str_t *value, *fname;
ngx_stream_variable_t *v;
value = cf->args->elts;
if (value[1].data[0] != '$') {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid variable name \"%V\"", &value[1]);
return NGX_CONF_ERROR;
}
value[1].len--;
value[1].data++;
v = ngx_stream_add_variable(cf, &value[1], NGX_STREAM_VAR_CHANGEABLE);
if (v == NULL) {
return NGX_CONF_ERROR;
}
fname = ngx_palloc(cf->pool, sizeof(ngx_str_t));
if (fname == NULL) {
return NGX_CONF_ERROR;
}
*fname = value[2];
v->get_handler = ngx_stream_js_variable_set;
v->data = (uintptr_t) fname;
return NGX_CONF_OK;
}
static char *
ngx_stream_js_var(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_str_t *value;
ngx_int_t index;
ngx_stream_variable_t *v;
ngx_stream_complex_value_t *cv;
ngx_stream_compile_complex_value_t ccv;
value = cf->args->elts;
if (value[1].data[0] != '$') {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid variable name \"%V\"", &value[1]);
return NGX_CONF_ERROR;
}
value[1].len--;
value[1].data++;
v = ngx_stream_add_variable(cf, &value[1], NGX_STREAM_VAR_CHANGEABLE);
if (v == NULL) {
return NGX_CONF_ERROR;
}
index = ngx_stream_get_variable_index(cf, &value[1]);
if (index == NGX_ERROR) {
return NGX_CONF_ERROR;
}
cv = NULL;
if (cf->args->nelts == 3) {
cv = ngx_palloc(cf->pool, sizeof(ngx_stream_complex_value_t));
if (cv == NULL) {
return NGX_CONF_ERROR;
}
ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t));
ccv.cf = cf;
ccv.value = &value[2];
ccv.complex_value = cv;
if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
v->get_handler = ngx_stream_js_variable_var;
v->data = (uintptr_t) cv;
return NGX_CONF_OK;
}
static void *
ngx_stream_js_create_main_conf(ngx_conf_t *cf)
{
ngx_stream_js_main_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_js_main_conf_t));
if (conf == NULL) {
return NULL;
}
/*
* set by ngx_pcalloc():
*
* conf->vm = NULL;
*/
conf->paths = NGX_CONF_UNSET_PTR;
conf->imports = NGX_CONF_UNSET_PTR;
return conf;
}
static void *
ngx_stream_js_create_srv_conf(ngx_conf_t *cf)
{
ngx_stream_js_srv_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_js_srv_conf_t));
if (conf == NULL) {
return NULL;
}
/*
* set by ngx_pcalloc():
*
* conf->access = { 0, NULL };
* conf->preread = { 0, NULL };
* conf->filter = { 0, NULL };
* conf->ssl_ciphers = { 0, NULL };
* conf->ssl_protocols = 0;
* conf->ssl_trusted_certificate = { 0, NULL };
*/
conf->buffer_size = NGX_CONF_UNSET_SIZE;
conf->max_response_body_size = NGX_CONF_UNSET_SIZE;
conf->timeout = NGX_CONF_UNSET_MSEC;
#if (NGX_STREAM_SSL)
conf->ssl_verify = NGX_CONF_UNSET;
conf->ssl_verify_depth = NGX_CONF_UNSET;
#endif
return conf;
}
static char *
ngx_stream_js_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_stream_js_srv_conf_t *prev = parent;
ngx_stream_js_srv_conf_t *conf = child;
ngx_conf_merge_str_value(conf->access, prev->access, "");
ngx_conf_merge_str_value(conf->preread, prev->preread, "");
ngx_conf_merge_str_value(conf->filter, prev->filter, "");
ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000);
ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 16384);
ngx_conf_merge_size_value(conf->max_response_body_size,
prev->max_response_body_size, 1048576);
#if (NGX_STREAM_SSL)
ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers, "DEFAULT");
ngx_conf_merge_bitmask_value(conf->ssl_protocols, prev->ssl_protocols,
(NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1
|NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2));
ngx_conf_merge_value(conf->ssl_verify, prev->ssl_verify, 1);
ngx_conf_merge_value(conf->ssl_verify_depth, prev->ssl_verify_depth, 100);
ngx_conf_merge_str_value(conf->ssl_trusted_certificate,
prev->ssl_trusted_certificate, "");
return ngx_stream_js_set_ssl(cf, conf);
#else
return NGX_CONF_OK;
#endif
}
static ngx_int_t
ngx_stream_js_init(ngx_conf_t *cf)
{
ngx_stream_handler_pt *h;
ngx_stream_core_main_conf_t *cmcf;
ngx_stream_next_filter = ngx_stream_top_filter;
ngx_stream_top_filter = ngx_stream_js_body_filter;
cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
h = ngx_array_push(&cmcf->phases[NGX_STREAM_ACCESS_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_stream_js_access_handler;
h = ngx_array_push(&cmcf->phases[NGX_STREAM_PREREAD_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_stream_js_preread_handler;
return NGX_OK;
}
#if (NGX_STREAM_SSL)
static char *
ngx_stream_js_set_ssl(ngx_conf_t *cf, ngx_stream_js_srv_conf_t *jscf)
{
ngx_ssl_t *ssl;
ngx_pool_cleanup_t *cln;
ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t));
if (ssl == NULL) {
return NGX_CONF_ERROR;
}
jscf->ssl = ssl;
ssl->log = cf->log;
if (ngx_ssl_create(ssl, jscf->ssl_protocols, NULL) != NGX_OK) {
return NGX_CONF_ERROR;
}
cln = ngx_pool_cleanup_add(cf->pool, 0);
if (cln == NULL) {
ngx_ssl_cleanup_ctx(ssl);
return NGX_CONF_ERROR;
}
cln->handler = ngx_ssl_cleanup_ctx;
cln->data = ssl;
if (ngx_ssl_ciphers(NULL, ssl, &jscf->ssl_ciphers, 0) != NGX_OK) {
return NGX_CONF_ERROR;
}
if (ngx_ssl_trusted_certificate(cf, ssl, &jscf->ssl_trusted_certificate,
jscf->ssl_verify_depth)
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
#endif
static ngx_ssl_t *
ngx_stream_js_ssl(njs_vm_t *vm, ngx_stream_session_t *s)
{
#if (NGX_STREAM_SSL)
ngx_stream_js_srv_conf_t *jscf;
jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module);
return jscf->ssl;
#else
return NULL;
#endif
}
static ngx_flag_t
ngx_stream_js_ssl_verify(njs_vm_t *vm, ngx_stream_session_t *s)
{
#if (NGX_STREAM_SSL)
ngx_stream_js_srv_conf_t *jscf;
jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module);
return jscf->ssl_verify;
#else
return 0;
#endif
}