1
2 /*
3 * Copyright (C) Roman Arutyunyan
4 * Copyright (C) Dmitry Volyntsev
5 * Copyright (C) NGINX, Inc.
6 */
7
8
9 #include <ngx_config.h>
10 #include <ngx_core.h>
11 #include <ngx_stream.h>
12 #include "ngx_js.h"
13
14
15 typedef struct {
16 njs_vm_t *vm;
17 ngx_array_t *imports;
18 ngx_array_t *paths;
19 } ngx_stream_js_main_conf_t;
20
21
22 typedef struct {
23 ngx_str_t name;
24 ngx_str_t path;
25 u_char *file;
26 ngx_uint_t line;
27 } ngx_stream_js_import_t;
28
29
30 typedef struct {
31 ngx_str_t access;
32 ngx_str_t preread;
33 ngx_str_t filter;
34 #if (NGX_STREAM_SSL)
35 ngx_ssl_t *ssl;
36 ngx_str_t ssl_ciphers;
37 ngx_uint_t ssl_protocols;
38 ngx_int_t ssl_verify_depth;
39 ngx_str_t ssl_trusted_certificate;
40 #endif
41 } ngx_stream_js_srv_conf_t;
42
43
44 typedef struct {
45 njs_vm_event_t ev;
46 ngx_uint_t data_type;
47 } ngx_stream_js_ev_t;
48
49
50 typedef struct {
51 njs_vm_t *vm;
52 njs_opaque_value_t retval;
53 njs_opaque_value_t args[3];
54 ngx_buf_t *buf;
55 ngx_chain_t **last_out;
56 ngx_chain_t *free;
57 ngx_chain_t *upstream_busy;
58 ngx_chain_t *downstream_busy;
59 ngx_int_t status;
60 #define NGX_JS_EVENT_UPLOAD 0
61 #define NGX_JS_EVENT_DOWNLOAD 1
62 #define NGX_JS_EVENT_MAX 2
63 ngx_stream_js_ev_t events[2];
64 unsigned from_upstream:1;
65 unsigned filter:1;
66 unsigned in_progress:1;
67 } ngx_stream_js_ctx_t;
68
69
70 typedef struct {
71 ngx_stream_session_t *session;
72 njs_vm_event_t vm_event;
73 void *unused;
74 ngx_int_t ident;
75 } ngx_stream_js_event_t;
76
77
78 static ngx_int_t ngx_stream_js_access_handler(ngx_stream_session_t *s);
79 static ngx_int_t ngx_stream_js_preread_handler(ngx_stream_session_t *s);
80 static ngx_int_t ngx_stream_js_phase_handler(ngx_stream_session_t *s,
81 ngx_str_t *name);
82 static ngx_int_t ngx_stream_js_body_filter(ngx_stream_session_t *s,
83 ngx_chain_t *in, ngx_uint_t from_upstream);
84 static ngx_int_t ngx_stream_js_variable_set(ngx_stream_session_t *s,
85 ngx_stream_variable_value_t *v, uintptr_t data);
86 static ngx_int_t ngx_stream_js_variable_var(ngx_stream_session_t *s,
87 ngx_stream_variable_value_t *v, uintptr_t data);
88 static ngx_int_t ngx_stream_js_init_vm(ngx_stream_session_t *s);
89 static void ngx_stream_js_drop_events(ngx_stream_js_ctx_t *ctx);
90 static void ngx_stream_js_cleanup(void *data);
91 static void ngx_stream_js_cleanup_vm(void *data);
92 static njs_int_t ngx_stream_js_run_event(ngx_stream_session_t *s,
93 ngx_stream_js_ctx_t *ctx, ngx_stream_js_ev_t *event);
94 static njs_vm_event_t *ngx_stream_js_event(ngx_stream_session_t *s,
95 njs_str_t *event);
96
97 static njs_int_t ngx_stream_js_ext_get_remote_address(njs_vm_t *vm,
98 njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval,
99 njs_value_t *retval);
100
101 static njs_int_t ngx_stream_js_ext_done(njs_vm_t *vm, njs_value_t *args,
102 njs_uint_t nargs, njs_index_t unused);
103
104 static njs_int_t ngx_stream_js_ext_on(njs_vm_t *vm, njs_value_t *args,
105 njs_uint_t nargs, njs_index_t unused);
106 static njs_int_t ngx_stream_js_ext_off(njs_vm_t *vm, njs_value_t *args,
107 njs_uint_t nargs, njs_index_t unused);
108 static njs_int_t ngx_stream_js_ext_send(njs_vm_t *vm, njs_value_t *args,
109 njs_uint_t nargs, njs_index_t unused);
110 static njs_int_t ngx_stream_js_ext_set_return_value(njs_vm_t *vm,
111 njs_value_t *args, njs_uint_t nargs, njs_index_t unused);
112
113 static njs_int_t ngx_stream_js_ext_variables(njs_vm_t *vm,
114 njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval,
115 njs_value_t *retval);
116
117 static njs_host_event_t ngx_stream_js_set_timer(njs_external_ptr_t external,
118 uint64_t delay, njs_vm_event_t vm_event);
119 static void ngx_stream_js_clear_timer(njs_external_ptr_t external,
120 njs_host_event_t event);
121 static void ngx_stream_js_timer_handler(ngx_event_t *ev);
122 static ngx_pool_t *ngx_stream_js_pool(njs_vm_t *vm, ngx_stream_session_t *s);
123 static ngx_resolver_t *ngx_stream_js_resolver(njs_vm_t *vm,
124 ngx_stream_session_t *s);
125 static ngx_msec_t ngx_stream_js_resolver_timeout(njs_vm_t *vm,
126 ngx_stream_session_t *s);
127 static void ngx_stream_js_handle_event(ngx_stream_session_t *s,
128 njs_vm_event_t vm_event, njs_value_t *args, njs_uint_t nargs);
129
130 static char *ngx_stream_js_import(ngx_conf_t *cf, ngx_command_t *cmd,
131 void *conf);
132 static char *ngx_stream_js_set(ngx_conf_t *cf, ngx_command_t *cmd,
133 void *conf);
134 static char *ngx_stream_js_var(ngx_conf_t *cf, ngx_command_t *cmd,
135 void *conf);
136 static void *ngx_stream_js_create_main_conf(ngx_conf_t *cf);
137 static char *ngx_stream_js_init_main_conf(ngx_conf_t *cf, void *conf);
138 static void *ngx_stream_js_create_srv_conf(ngx_conf_t *cf);
139 static char *ngx_stream_js_merge_srv_conf(ngx_conf_t *cf, void *parent,
140 void *child);
141 static ngx_int_t ngx_stream_js_init(ngx_conf_t *cf);
142
143 #if (NGX_STREAM_SSL)
144 static char * ngx_stream_js_set_ssl(ngx_conf_t *cf,
145 ngx_stream_js_srv_conf_t *jscf);
146 #endif
147 static ngx_ssl_t *ngx_stream_js_ssl(njs_vm_t *vm, ngx_stream_session_t *s);
148
149 #if (NGX_STREAM_SSL)
150
151 static ngx_conf_bitmask_t ngx_stream_js_ssl_protocols[] = {
152 { ngx_string("TLSv1"), NGX_SSL_TLSv1 },
153 { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 },
154 { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 },
155 { ngx_string("TLSv1.3"), NGX_SSL_TLSv1_3 },
156 { ngx_null_string, 0 }
157 };
158
159 #endif
160
161 static ngx_command_t ngx_stream_js_commands[] = {
162
163 { ngx_string("js_import"),
164 NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE13,
165 ngx_stream_js_import,
166 NGX_STREAM_MAIN_CONF_OFFSET,
167 0,
168 NULL },
169
170 { ngx_string("js_path"),
171 NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1,
172 ngx_conf_set_str_array_slot,
173 NGX_STREAM_MAIN_CONF_OFFSET,
174 offsetof(ngx_stream_js_main_conf_t, paths),
175 NULL },
176
177 { ngx_string("js_set"),
178 NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE2,
179 ngx_stream_js_set,
180 0,
181 0,
182 NULL },
183
184 { ngx_string("js_var"),
185 NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE12,
186 ngx_stream_js_var,
187 0,
188 0,
189 NULL },
190
191 { ngx_string("js_access"),
192 NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
193 ngx_conf_set_str_slot,
194 NGX_STREAM_SRV_CONF_OFFSET,
195 offsetof(ngx_stream_js_srv_conf_t, access),
196 NULL },
197
198 { ngx_string("js_preread"),
199 NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
200 ngx_conf_set_str_slot,
201 NGX_STREAM_SRV_CONF_OFFSET,
202 offsetof(ngx_stream_js_srv_conf_t, preread),
203 NULL },
204
205 { ngx_string("js_filter"),
206 NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
207 ngx_conf_set_str_slot,
208 NGX_STREAM_SRV_CONF_OFFSET,
209 offsetof(ngx_stream_js_srv_conf_t, filter),
210 NULL },
211
212 #if (NGX_STREAM_SSL)
213
214 { ngx_string("js_fetch_ciphers"),
215 NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
216 ngx_conf_set_str_slot,
217 NGX_STREAM_SRV_CONF_OFFSET,
218 offsetof(ngx_stream_js_srv_conf_t, ssl_ciphers),
219 NULL },
220
221 { ngx_string("js_fetch_protocols"),
222 NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE,
223 ngx_conf_set_bitmask_slot,
224 NGX_STREAM_SRV_CONF_OFFSET,
225 offsetof(ngx_stream_js_srv_conf_t, ssl_protocols),
226 &ngx_stream_js_ssl_protocols },
227
228 { ngx_string("js_fetch_verify_depth"),
229 NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
230 ngx_conf_set_num_slot,
231 NGX_STREAM_SRV_CONF_OFFSET,
232 offsetof(ngx_stream_js_srv_conf_t, ssl_verify_depth),
233 NULL },
234
235 { ngx_string("js_fetch_trusted_certificate"),
236 NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
237 ngx_conf_set_str_slot,
238 NGX_STREAM_SRV_CONF_OFFSET,
239 offsetof(ngx_stream_js_srv_conf_t, ssl_trusted_certificate),
240 NULL },
241
242 #endif
243
244 ngx_null_command
245 };
246
247
248 static ngx_stream_module_t ngx_stream_js_module_ctx = {
249 NULL, /* preconfiguration */
250 ngx_stream_js_init, /* postconfiguration */
251
252 ngx_stream_js_create_main_conf, /* create main configuration */
253 ngx_stream_js_init_main_conf, /* init main configuration */
254
255 ngx_stream_js_create_srv_conf, /* create server configuration */
256 ngx_stream_js_merge_srv_conf, /* merge server configuration */
257 };
258
259
260 ngx_module_t ngx_stream_js_module = {
261 NGX_MODULE_V1,
262 &ngx_stream_js_module_ctx, /* module context */
263 ngx_stream_js_commands, /* module directives */
264 NGX_STREAM_MODULE, /* module type */
265 NULL, /* init master */
266 NULL, /* init module */
267 NULL, /* init process */
268 NULL, /* init thread */
269 NULL, /* exit thread */
270 NULL, /* exit process */
271 NULL, /* exit master */
272 NGX_MODULE_V1_PADDING
273 };
274
275
276 static njs_external_t ngx_stream_js_ext_session[] = {
277
278 {
279 .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
280 .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
281 .u.property = {
282 .value = "Stream Session",
283 }
284 },
285
286 {
287 .flags = NJS_EXTERN_PROPERTY,
288 .name.string = njs_str("status"),
289 .enumerable = 1,
290 .u.property = {
291 .handler = ngx_js_ext_uint,
292 .magic32 = offsetof(ngx_stream_session_t, status),
293 }
294 },
295
296 {
297 .flags = NJS_EXTERN_PROPERTY,
298 .name.string = njs_str("remoteAddress"),
299 .enumerable = 1,
300 .u.property = {
301 .handler = ngx_stream_js_ext_get_remote_address,
302 }
303 },
304
305 {
306 .flags = NJS_EXTERN_OBJECT,
307 .name.string = njs_str("variables"),
308 .u.object = {
309 .writable = 1,
310 .prop_handler = ngx_stream_js_ext_variables,
311 .magic32 = NGX_JS_STRING,
312 }
313 },
314
315 {
316 .flags = NJS_EXTERN_OBJECT,
317 .name.string = njs_str("rawVariables"),
318 .u.object = {
319 .writable = 1,
320 .prop_handler = ngx_stream_js_ext_variables,
321 .magic32 = NGX_JS_BUFFER,
322 }
323 },
324
325 {
326 .flags = NJS_EXTERN_METHOD,
327 .name.string = njs_str("allow"),
328 .writable = 1,
329 .configurable = 1,
330 .enumerable = 1,
331 .u.method = {
332 .native = ngx_stream_js_ext_done,
333 .magic8 = NGX_OK,
334 }
335 },
336
337 {
338 .flags = NJS_EXTERN_METHOD,
339 .name.string = njs_str("deny"),
340 .writable = 1,
341 .configurable = 1,
342 .enumerable = 1,
343 .u.method = {
344 .native = ngx_stream_js_ext_done,
345 .magic8 = -NGX_DONE,
346 }
347 },
348
349 {
350 .flags = NJS_EXTERN_METHOD,
351 .name.string = njs_str("decline"),
352 .writable = 1,
353 .configurable = 1,
354 .enumerable = 1,
355 .u.method = {
356 .native = ngx_stream_js_ext_done,
357 .magic8 = -NGX_DECLINED,
358 }
359 },
360
361 {
362 .flags = NJS_EXTERN_METHOD,
363 .name.string = njs_str("done"),
364 .writable = 1,
365 .configurable = 1,
366 .enumerable = 1,
367 .u.method = {
368 .native = ngx_stream_js_ext_done,
369 .magic8 = NGX_OK,
370
371 }
372 },
373
374 {
375 .flags = NJS_EXTERN_METHOD,
376 .name.string = njs_str("log"),
377 .writable = 1,
378 .configurable = 1,
379 .enumerable = 1,
380 .u.method = {
381 .native = ngx_js_ext_log,
382 .magic8 = NGX_LOG_INFO,
383 }
384 },
385
386 {
387 .flags = NJS_EXTERN_METHOD,
388 .name.string = njs_str("warn"),
389 .writable = 1,
390 .configurable = 1,
391 .enumerable = 1,
392 .u.method = {
393 .native = ngx_js_ext_log,
394 .magic8 = NGX_LOG_WARN,
395 }
396 },
397
398 {
399 .flags = NJS_EXTERN_METHOD,
400 .name.string = njs_str("error"),
401 .writable = 1,
402 .configurable = 1,
403 .enumerable = 1,
404 .u.method = {
405 .native = ngx_js_ext_log,
406 .magic8 = NGX_LOG_ERR,
407 }
408 },
409
410 {
411 .flags = NJS_EXTERN_METHOD,
412 .name.string = njs_str("on"),
413 .writable = 1,
414 .configurable = 1,
415 .enumerable = 1,
416 .u.method = {
417 .native = ngx_stream_js_ext_on,
418 }
419 },
420
421 {
422 .flags = NJS_EXTERN_METHOD,
423 .name.string = njs_str("off"),
424 .writable = 1,
425 .configurable = 1,
426 .enumerable = 1,
427 .u.method = {
428 .native = ngx_stream_js_ext_off,
429 }
430 },
431
432 {
433 .flags = NJS_EXTERN_METHOD,
434 .name.string = njs_str("send"),
435 .writable = 1,
436 .configurable = 1,
437 .enumerable = 1,
438 .u.method = {
439 .native = ngx_stream_js_ext_send,
440 }
441 },
442
443 {
444 .flags = NJS_EXTERN_METHOD,
445 .name.string = njs_str("setReturnValue"),
446 .writable = 1,
447 .configurable = 1,
448 .enumerable = 1,
449 .u.method = {
450 .native = ngx_stream_js_ext_set_return_value,
451 }
452 },
453
454 };
455
456
457 static njs_vm_ops_t ngx_stream_js_ops = {
458 ngx_stream_js_set_timer,
459 ngx_stream_js_clear_timer
460 };
461
462
463 static uintptr_t ngx_stream_js_uptr[] = {
464 offsetof(ngx_stream_session_t, connection),
465 (uintptr_t) ngx_stream_js_pool,
466 (uintptr_t) ngx_stream_js_resolver,
467 (uintptr_t) ngx_stream_js_resolver_timeout,
468 (uintptr_t) ngx_stream_js_handle_event,
469 (uintptr_t) ngx_stream_js_ssl,
470 };
471
472
473 static njs_vm_meta_t ngx_stream_js_metas = {
474 .size = 6,
475 .values = ngx_stream_js_uptr
476 };
477
478
479 static ngx_stream_filter_pt ngx_stream_next_filter;
480
481
482 static njs_int_t ngx_stream_js_session_proto_id;
483
484
485 static ngx_int_t
ngx_stream_js_access_handler(ngx_stream_session_t * s)486 ngx_stream_js_access_handler(ngx_stream_session_t *s)
487 {
488 ngx_stream_js_srv_conf_t *jscf;
489
490 ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
491 "js access handler");
492
493 jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module);
494
495 return ngx_stream_js_phase_handler(s, &jscf->access);
496 }
497
498
499 static ngx_int_t
ngx_stream_js_preread_handler(ngx_stream_session_t * s)500 ngx_stream_js_preread_handler(ngx_stream_session_t *s)
501 {
502 ngx_stream_js_srv_conf_t *jscf;
503
504 ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
505 "js preread handler");
506
507 jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module);
508
509 return ngx_stream_js_phase_handler(s, &jscf->preread);
510 }
511
512
513 static ngx_int_t
ngx_stream_js_phase_handler(ngx_stream_session_t * s,ngx_str_t * name)514 ngx_stream_js_phase_handler(ngx_stream_session_t *s, ngx_str_t *name)
515 {
516 ngx_str_t exception;
517 njs_int_t ret;
518 ngx_int_t rc;
519 ngx_connection_t *c;
520 ngx_stream_js_ctx_t *ctx;
521
522 if (name->len == 0) {
523 return NGX_DECLINED;
524 }
525
526 rc = ngx_stream_js_init_vm(s);
527 if (rc != NGX_OK) {
528 return rc;
529 }
530
531 c = s->connection;
532
533 ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0,
534 "stream js phase call \"%V\"", name);
535
536 ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
537
538 if (!ctx->in_progress) {
539 /*
540 * status is expected to be overriden by allow(), deny(), decline() or
541 * done() methods.
542 */
543
544 ctx->status = NGX_ERROR;
545
546 rc = ngx_js_call(ctx->vm, name, c->log, &ctx->args[0], 1);
547
548 if (rc == NGX_ERROR) {
549 return rc;
550 }
551 }
552
553 ret = ngx_stream_js_run_event(s, ctx, &ctx->events[NGX_JS_EVENT_UPLOAD]);
554 if (ret != NJS_OK) {
555 ngx_js_retval(ctx->vm, NULL, &exception);
556
557 ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %V",
558 &exception);
559
560 return NGX_ERROR;
561 }
562
563 if (njs_vm_pending(ctx->vm)) {
564 ctx->in_progress = 1;
565 rc = ctx->events[NGX_JS_EVENT_UPLOAD].ev ? NGX_AGAIN : NGX_DONE;
566
567 } else {
568 ctx->in_progress = 0;
569 rc = ctx->status;
570 }
571
572 ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream js phase rc: %i",
573 rc);
574
575 return rc;
576 }
577
578
579 #define ngx_stream_event(from_upstream) \
580 (from_upstream ? &ctx->events[NGX_JS_EVENT_DOWNLOAD] \
581 : &ctx->events[NGX_JS_EVENT_UPLOAD])
582
583
584 static ngx_int_t
ngx_stream_js_body_filter(ngx_stream_session_t * s,ngx_chain_t * in,ngx_uint_t from_upstream)585 ngx_stream_js_body_filter(ngx_stream_session_t *s, ngx_chain_t *in,
586 ngx_uint_t from_upstream)
587 {
588 ngx_str_t exception;
589 njs_int_t ret;
590 ngx_int_t rc;
591 ngx_chain_t *out, *cl, **busy;
592 ngx_connection_t *c, *dst;
593 ngx_stream_js_ev_t *event;
594 ngx_stream_js_ctx_t *ctx;
595 ngx_stream_js_srv_conf_t *jscf;
596
597 jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module);
598 if (jscf->filter.len == 0) {
599 return ngx_stream_next_filter(s, in, from_upstream);
600 }
601
602 c = s->connection;
603
604 ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream js filter u:%ui",
605 from_upstream);
606
607 rc = ngx_stream_js_init_vm(s);
608
609 if (rc == NGX_ERROR) {
610 return NGX_ERROR;
611 }
612
613 if (rc == NGX_DECLINED) {
614 return ngx_stream_next_filter(s, in, from_upstream);
615 }
616
617 ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
618
619 if (!ctx->filter) {
620 rc = ngx_js_call(ctx->vm, &jscf->filter, c->log, &ctx->args[0], 1);
621
622 if (rc == NGX_ERROR) {
623 return rc;
624 }
625 }
626
627 ctx->filter = 1;
628 ctx->from_upstream = from_upstream;
629
630 ctx->last_out = &out;
631
632 while (in) {
633 ctx->buf = in->buf;
634
635 event = ngx_stream_event(from_upstream);
636
637 if (event->ev != NULL) {
638 ret = ngx_stream_js_run_event(s, ctx, event);
639 if (ret != NJS_OK) {
640 ngx_js_retval(ctx->vm, NULL, &exception);
641
642 ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %V",
643 &exception);
644
645 return NGX_ERROR;
646 }
647
648 ctx->buf->pos = ctx->buf->last;
649
650 } else {
651 cl = ngx_alloc_chain_link(c->pool);
652 if (cl == NULL) {
653 return NGX_ERROR;
654 }
655
656 cl->buf = ctx->buf;
657
658 *ctx->last_out = cl;
659 ctx->last_out = &cl->next;
660 }
661
662 in = in->next;
663 }
664
665 *ctx->last_out = NULL;
666
667 if (from_upstream) {
668 dst = c;
669 busy = &ctx->downstream_busy;
670
671 } else {
672 dst = s->upstream ? s->upstream->peer.connection : NULL;
673 busy = &ctx->upstream_busy;
674 }
675
676 if (out != NULL || dst == NULL || dst->buffered) {
677 rc = ngx_stream_next_filter(s, out, from_upstream);
678
679 ngx_chain_update_chains(c->pool, &ctx->free, busy, &out,
680 (ngx_buf_tag_t) &ngx_stream_js_module);
681
682 } else {
683 rc = NGX_OK;
684 }
685
686 return rc;
687 }
688
689
690 static ngx_int_t
ngx_stream_js_variable_set(ngx_stream_session_t * s,ngx_stream_variable_value_t * v,uintptr_t data)691 ngx_stream_js_variable_set(ngx_stream_session_t *s,
692 ngx_stream_variable_value_t *v, uintptr_t data)
693 {
694 ngx_str_t *fname = (ngx_str_t *) data;
695
696 ngx_int_t rc;
697 njs_int_t pending;
698 ngx_str_t value;
699 ngx_stream_js_ctx_t *ctx;
700
701 rc = ngx_stream_js_init_vm(s);
702
703 if (rc == NGX_ERROR) {
704 return NGX_ERROR;
705 }
706
707 if (rc == NGX_DECLINED) {
708 v->not_found = 1;
709 return NGX_OK;
710 }
711
712 ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
713 "stream js variable call \"%V\"", fname);
714
715 ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
716
717 pending = njs_vm_pending(ctx->vm);
718
719 rc = ngx_js_call(ctx->vm, fname, s->connection->log, &ctx->args[0], 1);
720
721 if (rc == NGX_ERROR) {
722 v->not_found = 1;
723 return NGX_OK;
724 }
725
726 if (!pending && rc == NGX_AGAIN) {
727 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
728 "async operation inside \"%V\" variable handler", fname);
729 return NGX_ERROR;
730 }
731
732 if (ngx_js_retval(ctx->vm, &ctx->retval, &value) != NGX_OK) {
733 return NGX_ERROR;
734 }
735
736 v->len = value.len;
737 v->valid = 1;
738 v->no_cacheable = 0;
739 v->not_found = 0;
740 v->data = value.data;
741
742 return NGX_OK;
743 }
744
745
746 static ngx_int_t
ngx_stream_js_variable_var(ngx_stream_session_t * s,ngx_stream_variable_value_t * v,uintptr_t data)747 ngx_stream_js_variable_var(ngx_stream_session_t *s,
748 ngx_stream_variable_value_t *v, uintptr_t data)
749 {
750 ngx_stream_complex_value_t *cv = (ngx_stream_complex_value_t *) data;
751
752 ngx_str_t value;
753
754 if (cv != NULL) {
755 if (ngx_stream_complex_value(s, cv, &value) != NGX_OK) {
756 return NGX_ERROR;
757 }
758
759 } else {
760 ngx_str_null(&value);
761 }
762
763 v->len = value.len;
764 v->valid = 1;
765 v->no_cacheable = 0;
766 v->not_found = 0;
767 v->data = value.data;
768
769 return NGX_OK;
770 }
771
772
773 static ngx_int_t
ngx_stream_js_init_vm(ngx_stream_session_t * s)774 ngx_stream_js_init_vm(ngx_stream_session_t *s)
775 {
776 njs_int_t rc;
777 ngx_str_t exception;
778 ngx_pool_cleanup_t *cln;
779 ngx_stream_js_ctx_t *ctx;
780 ngx_stream_js_main_conf_t *jmcf;
781
782 jmcf = ngx_stream_get_module_main_conf(s, ngx_stream_js_module);
783 if (jmcf->vm == NULL) {
784 return NGX_DECLINED;
785 }
786
787 ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
788
789 if (ctx == NULL) {
790 ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_stream_js_ctx_t));
791 if (ctx == NULL) {
792 return NGX_ERROR;
793 }
794
795 njs_value_invalid_set(njs_value_arg(&ctx->retval));
796
797 ngx_stream_set_ctx(s, ctx, ngx_stream_js_module);
798 }
799
800 if (ctx->vm) {
801 return NGX_OK;
802 }
803
804 ctx->vm = njs_vm_clone(jmcf->vm, s);
805 if (ctx->vm == NULL) {
806 return NGX_ERROR;
807 }
808
809 cln = ngx_pool_cleanup_add(s->connection->pool, 0);
810 if (cln == NULL) {
811 return NGX_ERROR;
812 }
813
814 cln->handler = ngx_stream_js_cleanup;
815 cln->data = s;
816
817 if (njs_vm_start(ctx->vm) == NJS_ERROR) {
818 ngx_js_retval(ctx->vm, NULL, &exception);
819
820 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
821 "js exception: %V", &exception);
822
823 return NGX_ERROR;
824 }
825
826 rc = njs_vm_external_create(ctx->vm, njs_value_arg(&ctx->args[0]),
827 ngx_stream_js_session_proto_id, s, 0);
828 if (rc != NJS_OK) {
829 return NGX_ERROR;
830 }
831
832 return NGX_OK;
833 }
834
835
836 static void
ngx_stream_js_drop_events(ngx_stream_js_ctx_t * ctx)837 ngx_stream_js_drop_events(ngx_stream_js_ctx_t *ctx)
838 {
839 ngx_uint_t i;
840
841 for (i = 0; i < NGX_JS_EVENT_MAX; i++) {
842 if (ctx->events[i].ev != NULL) {
843 njs_vm_del_event(ctx->vm, ctx->events[i].ev);
844 ctx->events[i].ev = NULL;
845 }
846 }
847 }
848
849
850 static void
ngx_stream_js_cleanup(void * data)851 ngx_stream_js_cleanup(void *data)
852 {
853 ngx_stream_js_ctx_t *ctx;
854
855 ngx_stream_session_t *s = data;
856
857 ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
858
859 ngx_stream_js_drop_events(ctx);
860
861 if (njs_vm_pending(ctx->vm)) {
862 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "pending events");
863 }
864
865 njs_vm_destroy(ctx->vm);
866 }
867
868
869 static void
ngx_stream_js_cleanup_vm(void * data)870 ngx_stream_js_cleanup_vm(void *data)
871 {
872 njs_vm_t *vm = data;
873
874 njs_vm_destroy(vm);
875 }
876
877
878 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)879 ngx_stream_js_run_event(ngx_stream_session_t *s, ngx_stream_js_ctx_t *ctx,
880 ngx_stream_js_ev_t *event)
881 {
882 size_t len;
883 u_char *p;
884 njs_int_t ret;
885 ngx_buf_t *b;
886 ngx_connection_t *c;
887 njs_opaque_value_t last_key, last;
888
889 static const njs_str_t last_str = njs_str("last");
890
891 if (event->ev == NULL) {
892 return NJS_OK;
893 }
894
895 c = s->connection;
896 b = ctx->filter ? ctx->buf : c->buffer;
897
898 len = b ? b->last - b->pos : 0;
899
900 p = ngx_pnalloc(c->pool, len);
901 if (p == NULL) {
902 njs_vm_memory_error(ctx->vm);
903 return NJS_ERROR;
904 }
905
906 if (len) {
907 ngx_memcpy(p, b->pos, len);
908 }
909
910 ret = ngx_js_prop(ctx->vm, event->data_type, njs_value_arg(&ctx->args[1]),
911 p, len);
912 if (ret != NJS_OK) {
913 return ret;
914 }
915
916 njs_vm_value_string_set(ctx->vm, njs_value_arg(&last_key), last_str.start,
917 last_str.length);
918
919 njs_value_boolean_set(njs_value_arg(&last), b && b->last_buf);
920
921 ret = njs_vm_object_alloc(ctx->vm, njs_value_arg(&ctx->args[2]),
922 njs_value_arg(&last_key),
923 njs_value_arg(&last), NULL);
924 if (ret != NJS_OK) {
925 return ret;
926 }
927
928 njs_vm_post_event(ctx->vm, event->ev, njs_value_arg(&ctx->args[1]), 2);
929
930 ret = njs_vm_run(ctx->vm);
931 if (ret == NJS_ERROR) {
932 return ret;
933 }
934
935 return NJS_OK;
936 }
937
938
939 static njs_vm_event_t *
ngx_stream_js_event(ngx_stream_session_t * s,njs_str_t * event)940 ngx_stream_js_event(ngx_stream_session_t *s, njs_str_t *event)
941 {
942 ngx_uint_t i, n, type;
943 ngx_stream_js_ctx_t *ctx;
944
945 static const struct {
946 ngx_str_t name;
947 ngx_uint_t data_type;
948 ngx_uint_t id;
949 } events[] = {
950 {
951 ngx_string("upload"),
952 NGX_JS_STRING,
953 NGX_JS_EVENT_UPLOAD,
954 },
955
956 {
957 ngx_string("download"),
958 NGX_JS_STRING,
959 NGX_JS_EVENT_DOWNLOAD,
960 },
961
962 {
963 ngx_string("upstream"),
964 NGX_JS_BUFFER,
965 NGX_JS_EVENT_UPLOAD,
966 },
967
968 {
969 ngx_string("downstream"),
970 NGX_JS_BUFFER,
971 NGX_JS_EVENT_DOWNLOAD,
972 },
973 };
974
975 ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
976
977 i = 0;
978 n = sizeof(events) / sizeof(events[0]);
979
980 while (i < n) {
981 if (event->length == events[i].name.len
982 && ngx_memcmp(event->start, events[i].name.data, event->length)
983 == 0)
984 {
985 break;
986 }
987
988 i++;
989 }
990
991 if (i == n) {
992 njs_vm_error(ctx->vm, "unknown event \"%V\"", event);
993 return NULL;
994 }
995
996 ctx->events[events[i].id].data_type = events[i].data_type;
997
998 for (n = 0; n < NGX_JS_EVENT_MAX; n++) {
999 type = ctx->events[n].data_type;
1000 if (type != NGX_JS_UNSET && type != events[i].data_type) {
1001 njs_vm_error(ctx->vm, "mixing string and buffer events"
1002 " is not allowed");
1003 return NULL;
1004 }
1005 }
1006
1007 return &ctx->events[events[i].id].ev;
1008 }
1009
1010
1011 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)1012 ngx_stream_js_ext_get_remote_address(njs_vm_t *vm,
1013 njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval,
1014 njs_value_t *retval)
1015 {
1016 ngx_connection_t *c;
1017 ngx_stream_session_t *s;
1018
1019 s = njs_vm_external(vm, ngx_stream_js_session_proto_id, value);
1020 if (s == NULL) {
1021 njs_value_undefined_set(retval);
1022 return NJS_DECLINED;
1023 }
1024
1025 c = s->connection;
1026
1027 return njs_vm_value_string_set(vm, retval, c->addr_text.data,
1028 c->addr_text.len);
1029 }
1030
1031
1032 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)1033 ngx_stream_js_ext_done(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
1034 njs_index_t magic)
1035 {
1036 ngx_int_t status;
1037 njs_value_t *code;
1038 ngx_stream_js_ctx_t *ctx;
1039 ngx_stream_session_t *s;
1040
1041 s = njs_vm_external(vm, ngx_stream_js_session_proto_id,
1042 njs_argument(args, 0));
1043 if (s == NULL) {
1044 njs_vm_error(vm, "\"this\" is not an external");
1045 return NJS_ERROR;
1046 }
1047
1048 status = (ngx_int_t) magic;
1049 status = -status;
1050
1051 if (status == NGX_DONE) {
1052 status = NGX_STREAM_FORBIDDEN;
1053 }
1054
1055 code = njs_arg(args, nargs, 1);
1056
1057 if (!njs_value_is_undefined(code)) {
1058 if (ngx_js_integer(vm, code, &status) != NGX_OK) {
1059 return NJS_ERROR;
1060 }
1061
1062 if (status < NGX_ABORT || status > NGX_STREAM_SERVICE_UNAVAILABLE) {
1063 njs_vm_error(vm, "code is out of range");
1064 return NJS_ERROR;
1065 }
1066 }
1067
1068
1069 ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
1070
1071 ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
1072 "stream js set status: %i", status);
1073
1074 ctx->status = status;
1075
1076 ngx_stream_js_drop_events(ctx);
1077
1078 njs_value_undefined_set(njs_vm_retval(vm));
1079
1080 return NJS_OK;
1081 }
1082
1083
1084 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)1085 ngx_stream_js_ext_on(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
1086 njs_index_t unused)
1087 {
1088 njs_str_t name;
1089 njs_value_t *callback;
1090 njs_vm_event_t *event;
1091 ngx_stream_session_t *s;
1092
1093 s = njs_vm_external(vm, ngx_stream_js_session_proto_id,
1094 njs_argument(args, 0));
1095 if (s == NULL) {
1096 njs_vm_error(vm, "\"this\" is not an external");
1097 return NJS_ERROR;
1098 }
1099
1100 if (ngx_js_string(vm, njs_arg(args, nargs, 1), &name) == NJS_ERROR) {
1101 njs_vm_error(vm, "failed to convert event arg");
1102 return NJS_ERROR;
1103 }
1104
1105 callback = njs_arg(args, nargs, 2);
1106 if (!njs_value_is_function(callback)) {
1107 njs_vm_error(vm, "callback is not a function");
1108 return NJS_ERROR;
1109 }
1110
1111 event = ngx_stream_js_event(s, &name);
1112 if (event == NULL) {
1113 return NJS_ERROR;
1114 }
1115
1116 if (*event != NULL) {
1117 njs_vm_error(vm, "event handler \"%V\" is already set", &name);
1118 return NJS_ERROR;
1119 }
1120
1121 *event = njs_vm_add_event(vm, njs_value_function(callback), 0, NULL, NULL);
1122 if (*event == NULL) {
1123 njs_vm_error(vm, "internal error");
1124 return NJS_ERROR;
1125 }
1126
1127 njs_value_undefined_set(njs_vm_retval(vm));
1128
1129 return NJS_OK;
1130 }
1131
1132
1133 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)1134 ngx_stream_js_ext_off(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
1135 njs_index_t unused)
1136 {
1137 njs_str_t name;
1138 njs_vm_event_t *event;
1139 ngx_stream_session_t *s;
1140
1141 s = njs_vm_external(vm, ngx_stream_js_session_proto_id,
1142 njs_argument(args, 0));
1143 if (s == NULL) {
1144 njs_vm_error(vm, "\"this\" is not an external");
1145 return NJS_ERROR;
1146 }
1147
1148 if (ngx_js_string(vm, njs_arg(args, nargs, 1), &name) == NJS_ERROR) {
1149 njs_vm_error(vm, "failed to convert event arg");
1150 return NJS_ERROR;
1151 }
1152
1153 event = ngx_stream_js_event(s, &name);
1154 if (event == NULL) {
1155 return NJS_ERROR;
1156 }
1157
1158 njs_vm_del_event(vm, *event);
1159
1160 *event = NULL;
1161
1162 njs_value_undefined_set(njs_vm_retval(vm));
1163
1164 return NJS_OK;
1165 }
1166
1167
1168 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)1169 ngx_stream_js_ext_send(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
1170 njs_index_t unused)
1171 {
1172 unsigned last_buf, flush;
1173 njs_str_t buffer;
1174 ngx_buf_t *b;
1175 njs_value_t *flags, *value;
1176 ngx_chain_t *cl;
1177 ngx_connection_t *c;
1178 njs_opaque_value_t lvalue;
1179 ngx_stream_js_ctx_t *ctx;
1180 ngx_stream_session_t *s;
1181
1182 static const njs_str_t last_key = njs_str("last");
1183 static const njs_str_t flush_key = njs_str("flush");
1184
1185 s = njs_vm_external(vm, ngx_stream_js_session_proto_id,
1186 njs_argument(args, 0));
1187 if (s == NULL) {
1188 njs_vm_error(vm, "\"this\" is not an external");
1189 return NJS_ERROR;
1190 }
1191
1192 c = s->connection;
1193
1194 ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
1195
1196 if (!ctx->filter) {
1197 njs_vm_error(vm, "cannot send buffer in this handler");
1198 return NJS_ERROR;
1199 }
1200
1201 if (ngx_js_string(vm, njs_arg(args, nargs, 1), &buffer) != NGX_OK) {
1202 njs_vm_error(vm, "failed to get buffer arg");
1203 return NJS_ERROR;
1204 }
1205
1206 flush = ctx->buf->flush;
1207 last_buf = ctx->buf->last_buf;
1208
1209 flags = njs_arg(args, nargs, 2);
1210
1211 if (njs_value_is_object(flags)) {
1212 value = njs_vm_object_prop(vm, flags, &flush_key, &lvalue);
1213 if (value != NULL) {
1214 flush = njs_value_bool(value);
1215 }
1216
1217 value = njs_vm_object_prop(vm, flags, &last_key, &lvalue);
1218 if (value != NULL) {
1219 last_buf = njs_value_bool(value);
1220 }
1221 }
1222
1223 cl = ngx_chain_get_free_buf(c->pool, &ctx->free);
1224 if (cl == NULL) {
1225 njs_vm_error(vm, "memory error");
1226 return NJS_ERROR;
1227 }
1228
1229 b = cl->buf;
1230
1231 b->flush = flush;
1232 b->last_buf = last_buf;
1233
1234 b->memory = (buffer.length ? 1 : 0);
1235 b->sync = (buffer.length ? 0 : 1);
1236 b->tag = (ngx_buf_tag_t) &ngx_stream_js_module;
1237
1238 b->start = buffer.start;
1239 b->end = buffer.start + buffer.length;
1240 b->pos = b->start;
1241 b->last = b->end;
1242
1243 *ctx->last_out = cl;
1244 ctx->last_out = &cl->next;
1245
1246 njs_value_undefined_set(njs_vm_retval(vm));
1247
1248 return NJS_OK;
1249 }
1250
1251
1252 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)1253 ngx_stream_js_ext_set_return_value(njs_vm_t *vm, njs_value_t *args,
1254 njs_uint_t nargs, njs_index_t unused)
1255 {
1256 ngx_stream_js_ctx_t *ctx;
1257 ngx_stream_session_t *s;
1258
1259 s = njs_vm_external(vm, ngx_stream_js_session_proto_id,
1260 njs_argument(args, 0));
1261 if (s == NULL) {
1262 njs_vm_error(vm, "\"this\" is not an external");
1263 return NJS_ERROR;
1264 }
1265
1266 ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
1267
1268 njs_value_assign(&ctx->retval, njs_arg(args, nargs, 1));
1269 njs_value_undefined_set(njs_vm_retval(vm));
1270
1271 return NJS_OK;
1272 }
1273
1274
1275 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)1276 ngx_stream_js_ext_variables(njs_vm_t *vm, njs_object_prop_t *prop,
1277 njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
1278 {
1279 njs_int_t rc;
1280 njs_str_t val;
1281 ngx_str_t name;
1282 ngx_uint_t key;
1283 ngx_stream_variable_t *v;
1284 ngx_stream_session_t *s;
1285 ngx_stream_core_main_conf_t *cmcf;
1286 ngx_stream_variable_value_t *vv;
1287
1288 s = njs_vm_external(vm, ngx_stream_js_session_proto_id, value);
1289 if (s == NULL) {
1290 njs_value_undefined_set(retval);
1291 return NJS_DECLINED;
1292 }
1293
1294 rc = njs_vm_prop_name(vm, prop, &val);
1295 if (rc != NJS_OK) {
1296 njs_value_undefined_set(retval);
1297 return NJS_DECLINED;
1298 }
1299
1300 name.data = val.start;
1301 name.len = val.length;
1302
1303 if (setval == NULL) {
1304 key = ngx_hash_strlow(name.data, name.data, name.len);
1305
1306 vv = ngx_stream_get_variable(s, &name, key);
1307 if (vv == NULL || vv->not_found) {
1308 njs_value_undefined_set(retval);
1309 return NJS_DECLINED;
1310 }
1311
1312 return ngx_js_prop(vm, njs_vm_prop_magic32(prop), retval, vv->data,
1313 vv->len);
1314 }
1315
1316 cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
1317
1318 key = ngx_hash_strlow(name.data, name.data, name.len);
1319
1320 v = ngx_hash_find(&cmcf->variables_hash, key, name.data, name.len);
1321
1322 if (v == NULL) {
1323 njs_vm_error(vm, "variable not found");
1324 return NJS_ERROR;
1325 }
1326
1327 if (ngx_js_string(vm, setval, &val) != NGX_OK) {
1328 return NJS_ERROR;
1329 }
1330
1331 if (v->set_handler != NULL) {
1332 vv = ngx_pcalloc(s->connection->pool,
1333 sizeof(ngx_stream_variable_value_t));
1334 if (vv == NULL) {
1335 return NJS_ERROR;
1336 }
1337
1338 vv->valid = 1;
1339 vv->not_found = 0;
1340 vv->data = val.start;
1341 vv->len = val.length;
1342
1343 v->set_handler(s, vv, v->data);
1344
1345 return NJS_OK;
1346 }
1347
1348 if (!(v->flags & NGX_STREAM_VAR_INDEXED)) {
1349 njs_vm_error(vm, "variable is not writable");
1350 return NJS_ERROR;
1351 }
1352
1353 vv = &s->variables[v->index];
1354
1355 vv->valid = 1;
1356 vv->not_found = 0;
1357
1358 vv->data = ngx_pnalloc(s->connection->pool, val.length);
1359 if (vv->data == NULL) {
1360 return NJS_ERROR;
1361 }
1362
1363 vv->len = val.length;
1364 ngx_memcpy(vv->data, val.start, vv->len);
1365
1366 return NJS_OK;
1367 }
1368
1369
1370 static njs_host_event_t
ngx_stream_js_set_timer(njs_external_ptr_t external,uint64_t delay,njs_vm_event_t vm_event)1371 ngx_stream_js_set_timer(njs_external_ptr_t external, uint64_t delay,
1372 njs_vm_event_t vm_event)
1373 {
1374 ngx_event_t *ev;
1375 ngx_stream_session_t *s;
1376 ngx_stream_js_event_t *js_event;
1377
1378 s = (ngx_stream_session_t *) external;
1379
1380 ev = ngx_pcalloc(s->connection->pool, sizeof(ngx_event_t));
1381 if (ev == NULL) {
1382 return NULL;
1383 }
1384
1385 js_event = ngx_palloc(s->connection->pool, sizeof(ngx_stream_js_event_t));
1386 if (js_event == NULL) {
1387 return NULL;
1388 }
1389
1390 js_event->session = s;
1391 js_event->vm_event = vm_event;
1392 js_event->ident = s->connection->fd;
1393
1394 ev->data = js_event;
1395 ev->log = s->connection->log;
1396 ev->handler = ngx_stream_js_timer_handler;
1397
1398 ngx_add_timer(ev, delay);
1399
1400 return ev;
1401 }
1402
1403
1404 static void
ngx_stream_js_clear_timer(njs_external_ptr_t external,njs_host_event_t event)1405 ngx_stream_js_clear_timer(njs_external_ptr_t external, njs_host_event_t event)
1406 {
1407 ngx_event_t *ev = event;
1408
1409 if (ev->timer_set) {
1410 ngx_del_timer(ev);
1411 }
1412 }
1413
1414
1415 static void
ngx_stream_js_timer_handler(ngx_event_t * ev)1416 ngx_stream_js_timer_handler(ngx_event_t *ev)
1417 {
1418 ngx_stream_session_t *s;
1419 ngx_stream_js_event_t *js_event;
1420
1421 js_event = (ngx_stream_js_event_t *) ev->data;
1422
1423 s = js_event->session;
1424
1425 ngx_stream_js_handle_event(s, js_event->vm_event, NULL, 0);
1426 }
1427
1428
1429 static ngx_pool_t *
ngx_stream_js_pool(njs_vm_t * vm,ngx_stream_session_t * s)1430 ngx_stream_js_pool(njs_vm_t *vm, ngx_stream_session_t *s)
1431 {
1432 return s->connection->pool;
1433 }
1434
1435
1436 static ngx_resolver_t *
ngx_stream_js_resolver(njs_vm_t * vm,ngx_stream_session_t * s)1437 ngx_stream_js_resolver(njs_vm_t *vm, ngx_stream_session_t *s)
1438 {
1439 ngx_stream_core_srv_conf_t *cscf;
1440
1441 cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module);
1442
1443 return cscf->resolver;
1444 }
1445
1446
1447 static ngx_msec_t
ngx_stream_js_resolver_timeout(njs_vm_t * vm,ngx_stream_session_t * s)1448 ngx_stream_js_resolver_timeout(njs_vm_t *vm, ngx_stream_session_t *s)
1449 {
1450 ngx_stream_core_srv_conf_t *cscf;
1451
1452 cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module);
1453
1454 return cscf->resolver_timeout;
1455 }
1456
1457
1458 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)1459 ngx_stream_js_handle_event(ngx_stream_session_t *s, njs_vm_event_t vm_event,
1460 njs_value_t *args, njs_uint_t nargs)
1461 {
1462 njs_int_t rc;
1463 ngx_str_t exception;
1464 ngx_stream_js_ctx_t *ctx;
1465
1466 ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
1467
1468 njs_vm_post_event(ctx->vm, vm_event, args, nargs);
1469
1470 rc = njs_vm_run(ctx->vm);
1471
1472 if (rc == NJS_ERROR) {
1473 ngx_js_retval(ctx->vm, NULL, &exception);
1474
1475 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
1476 "js exception: %V", &exception);
1477
1478 ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
1479 }
1480
1481 if (rc == NJS_OK) {
1482 ngx_post_event(s->connection->read, &ngx_posted_events);
1483 }
1484 }
1485
1486
1487 static char *
ngx_stream_js_init_main_conf(ngx_conf_t * cf,void * conf)1488 ngx_stream_js_init_main_conf(ngx_conf_t *cf, void *conf)
1489 {
1490 ngx_stream_js_main_conf_t *jmcf = conf;
1491
1492 size_t size;
1493 u_char *start, *end, *p;
1494 ngx_str_t *m, file;
1495 njs_int_t rc;
1496 njs_str_t text, path;
1497 ngx_uint_t i;
1498 njs_value_t *value;
1499 njs_vm_opt_t options;
1500 ngx_pool_cleanup_t *cln;
1501 njs_opaque_value_t lvalue, exception;
1502 ngx_stream_js_import_t *import;
1503
1504 static const njs_str_t line_number_key = njs_str("lineNumber");
1505 static const njs_str_t file_name_key = njs_str("fileName");
1506
1507 if (jmcf->imports == NGX_CONF_UNSET_PTR) {
1508 return NGX_CONF_OK;
1509 }
1510
1511 size = 0;
1512
1513 import = jmcf->imports->elts;
1514 for (i = 0; i < jmcf->imports->nelts; i++) {
1515 size += sizeof("import from '';\n") - 1 + import[i].name.len
1516 + import[i].path.len;
1517 }
1518
1519 start = ngx_pnalloc(cf->pool, size);
1520 if (start == NULL) {
1521 return NGX_CONF_ERROR;
1522 }
1523
1524 p = start;
1525 import = jmcf->imports->elts;
1526 for (i = 0; i < jmcf->imports->nelts; i++) {
1527 p = ngx_cpymem(p, "import ", sizeof("import ") - 1);
1528 p = ngx_cpymem(p, import[i].name.data, import[i].name.len);
1529 p = ngx_cpymem(p, " from '", sizeof(" from '") - 1);
1530 p = ngx_cpymem(p, import[i].path.data, import[i].path.len);
1531 p = ngx_cpymem(p, "';\n", sizeof("';\n") - 1);
1532 }
1533
1534 njs_vm_opt_init(&options);
1535
1536 options.backtrace = 1;
1537 options.unhandled_rejection = NJS_VM_OPT_UNHANDLED_REJECTION_THROW;
1538 options.ops = &ngx_stream_js_ops;
1539 options.metas = &ngx_stream_js_metas;
1540 options.addons = njs_js_addon_modules;
1541 options.argv = ngx_argv;
1542 options.argc = ngx_argc;
1543
1544 file = ngx_cycle->conf_prefix;
1545
1546 options.file.start = file.data;
1547 options.file.length = file.len;
1548
1549 jmcf->vm = njs_vm_create(&options);
1550 if (jmcf->vm == NULL) {
1551 ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "failed to create js VM");
1552 return NGX_CONF_ERROR;
1553 }
1554
1555 cln = ngx_pool_cleanup_add(cf->pool, 0);
1556 if (cln == NULL) {
1557 return NGX_CONF_ERROR;
1558 }
1559
1560 cln->handler = ngx_stream_js_cleanup_vm;
1561 cln->data = jmcf->vm;
1562
1563 path.start = ngx_cycle->conf_prefix.data;
1564 path.length = ngx_cycle->conf_prefix.len;
1565
1566 rc = njs_vm_add_path(jmcf->vm, &path);
1567 if (rc != NJS_OK) {
1568 ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "failed to add \"js_path\"");
1569 return NGX_CONF_ERROR;
1570 }
1571
1572 if (jmcf->paths != NGX_CONF_UNSET_PTR) {
1573 m = jmcf->paths->elts;
1574
1575 for (i = 0; i < jmcf->paths->nelts; i++) {
1576 if (ngx_conf_full_name(cf->cycle, &m[i], 1) != NGX_OK) {
1577 return NGX_CONF_ERROR;
1578 }
1579
1580 path.start = m[i].data;
1581 path.length = m[i].len;
1582
1583 rc = njs_vm_add_path(jmcf->vm, &path);
1584 if (rc != NJS_OK) {
1585 ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
1586 "failed to add \"js_path\"");
1587 return NGX_CONF_ERROR;
1588 }
1589 }
1590 }
1591
1592 ngx_stream_js_session_proto_id = njs_vm_external_prototype(jmcf->vm,
1593 ngx_stream_js_ext_session,
1594 njs_nitems(ngx_stream_js_ext_session));
1595 if (ngx_stream_js_session_proto_id < 0) {
1596 ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
1597 "failed to add js request proto");
1598 return NGX_CONF_ERROR;
1599 }
1600
1601 rc = ngx_js_core_init(jmcf->vm, cf->log);
1602 if (njs_slow_path(rc != NJS_OK)) {
1603 return NGX_CONF_ERROR;
1604 }
1605
1606 end = start + size;
1607
1608 rc = njs_vm_compile(jmcf->vm, &start, end);
1609
1610 if (rc != NJS_OK) {
1611 njs_value_assign(&exception, njs_vm_retval(jmcf->vm));
1612 njs_vm_retval_string(jmcf->vm, &text);
1613
1614 value = njs_vm_object_prop(jmcf->vm, njs_value_arg(&exception),
1615 &file_name_key, &lvalue);
1616 if (value == NULL) {
1617 value = njs_vm_object_prop(jmcf->vm, njs_value_arg(&exception),
1618 &line_number_key, &lvalue);
1619
1620 if (value != NULL) {
1621 i = njs_value_number(value) - 1;
1622
1623 if (i < jmcf->imports->nelts) {
1624 import = jmcf->imports->elts;
1625 ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
1626 "%*s, included in %s:%ui", text.length,
1627 text.start, import[i].file, import[i].line);
1628 return NGX_CONF_ERROR;
1629 }
1630 }
1631 }
1632
1633 ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "%*s", text.length,
1634 text.start);
1635 return NGX_CONF_ERROR;
1636 }
1637
1638 if (start != end) {
1639 ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
1640 "extra characters in js script: \"%*s\"",
1641 end - start, start);
1642 return NGX_CONF_ERROR;
1643 }
1644
1645 return NGX_CONF_OK;
1646 }
1647
1648
1649 static char *
ngx_stream_js_import(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)1650 ngx_stream_js_import(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
1651 {
1652 ngx_stream_js_main_conf_t *jmcf = conf;
1653
1654 u_char *p, *end, c;
1655 ngx_int_t from;
1656 ngx_str_t *value, name, path;
1657 ngx_stream_js_import_t *import;
1658
1659 value = cf->args->elts;
1660 from = (cf->args->nelts == 4);
1661
1662 if (from) {
1663 if (ngx_strcmp(value[2].data, "from") != 0) {
1664 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1665 "invalid parameter \"%V\"", &value[2]);
1666 return NGX_CONF_ERROR;
1667 }
1668 }
1669
1670 name = value[1];
1671 path = (from ? value[3] : value[1]);
1672
1673 if (!from) {
1674 end = name.data + name.len;
1675
1676 for (p = end - 1; p >= name.data; p--) {
1677 if (*p == '/') {
1678 break;
1679 }
1680 }
1681
1682 name.data = p + 1;
1683 name.len = end - p - 1;
1684
1685 if (name.len < 3
1686 || ngx_memcmp(&name.data[name.len - 3], ".js", 3) != 0)
1687 {
1688 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1689 "cannot extract export name from file path "
1690 "\"%V\", use extended \"from\" syntax", &path);
1691 return NGX_CONF_ERROR;
1692 }
1693
1694 name.len -= 3;
1695 }
1696
1697 if (name.len == 0) {
1698 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "empty \"name\" parameter");
1699 return NGX_CONF_ERROR;
1700 }
1701
1702 p = name.data;
1703 end = name.data + name.len;
1704
1705 while (p < end) {
1706 c = ngx_tolower(*p);
1707
1708 if (*p != '_' && (c < 'a' || c > 'z')) {
1709 if (p == name.data) {
1710 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "cannot start "
1711 "with \"%c\" in export name \"%V\"", *p,
1712 &name);
1713 return NGX_CONF_ERROR;
1714 }
1715
1716 if (*p < '0' || *p > '9') {
1717 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid character "
1718 "\"%c\" in export name \"%V\"", *p, &name);
1719 return NGX_CONF_ERROR;
1720 }
1721 }
1722
1723 p++;
1724 }
1725
1726 if (ngx_strchr(path.data, '\'') != NULL) {
1727 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid character \"'\" "
1728 "in file path \"%V\"", &path);
1729 return NGX_CONF_ERROR;
1730 }
1731
1732 if (jmcf->imports == NGX_CONF_UNSET_PTR) {
1733 jmcf->imports = ngx_array_create(cf->pool, 4,
1734 sizeof(ngx_stream_js_import_t));
1735 if (jmcf->imports == NULL) {
1736 return NGX_CONF_ERROR;
1737 }
1738 }
1739
1740 import = ngx_array_push(jmcf->imports);
1741 if (import == NULL) {
1742 return NGX_CONF_ERROR;
1743 }
1744
1745 import->name = name;
1746 import->path = path;
1747 import->file = cf->conf_file->file.name.data;
1748 import->line = cf->conf_file->line;
1749
1750 return NGX_CONF_OK;
1751 }
1752
1753
1754 static char *
ngx_stream_js_set(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)1755 ngx_stream_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
1756 {
1757 ngx_str_t *value, *fname;
1758 ngx_stream_variable_t *v;
1759
1760 value = cf->args->elts;
1761
1762 if (value[1].data[0] != '$') {
1763 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1764 "invalid variable name \"%V\"", &value[1]);
1765 return NGX_CONF_ERROR;
1766 }
1767
1768 value[1].len--;
1769 value[1].data++;
1770
1771 v = ngx_stream_add_variable(cf, &value[1], NGX_STREAM_VAR_CHANGEABLE);
1772 if (v == NULL) {
1773 return NGX_CONF_ERROR;
1774 }
1775
1776 fname = ngx_palloc(cf->pool, sizeof(ngx_str_t));
1777 if (fname == NULL) {
1778 return NGX_CONF_ERROR;
1779 }
1780
1781 *fname = value[2];
1782
1783 v->get_handler = ngx_stream_js_variable_set;
1784 v->data = (uintptr_t) fname;
1785
1786 return NGX_CONF_OK;
1787 }
1788
1789
1790 static char *
ngx_stream_js_var(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)1791 ngx_stream_js_var(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
1792 {
1793 ngx_str_t *value;
1794 ngx_int_t index;
1795 ngx_stream_variable_t *v;
1796 ngx_stream_complex_value_t *cv;
1797 ngx_stream_compile_complex_value_t ccv;
1798
1799 value = cf->args->elts;
1800
1801 if (value[1].data[0] != '$') {
1802 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1803 "invalid variable name \"%V\"", &value[1]);
1804 return NGX_CONF_ERROR;
1805 }
1806
1807 value[1].len--;
1808 value[1].data++;
1809
1810 v = ngx_stream_add_variable(cf, &value[1], NGX_STREAM_VAR_CHANGEABLE);
1811 if (v == NULL) {
1812 return NGX_CONF_ERROR;
1813 }
1814
1815 index = ngx_stream_get_variable_index(cf, &value[1]);
1816 if (index == NGX_ERROR) {
1817 return NGX_CONF_ERROR;
1818 }
1819
1820 cv = NULL;
1821
1822 if (cf->args->nelts == 3) {
1823 cv = ngx_palloc(cf->pool, sizeof(ngx_stream_complex_value_t));
1824 if (cv == NULL) {
1825 return NGX_CONF_ERROR;
1826 }
1827
1828 ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t));
1829
1830 ccv.cf = cf;
1831 ccv.value = &value[2];
1832 ccv.complex_value = cv;
1833
1834 if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
1835 return NGX_CONF_ERROR;
1836 }
1837 }
1838
1839 v->get_handler = ngx_stream_js_variable_var;
1840 v->data = (uintptr_t) cv;
1841
1842 return NGX_CONF_OK;
1843 }
1844
1845
1846 static void *
ngx_stream_js_create_main_conf(ngx_conf_t * cf)1847 ngx_stream_js_create_main_conf(ngx_conf_t *cf)
1848 {
1849 ngx_stream_js_main_conf_t *conf;
1850
1851 conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_js_main_conf_t));
1852 if (conf == NULL) {
1853 return NULL;
1854 }
1855
1856 /*
1857 * set by ngx_pcalloc():
1858 *
1859 * conf->vm = NULL;
1860 */
1861
1862 conf->paths = NGX_CONF_UNSET_PTR;
1863 conf->imports = NGX_CONF_UNSET_PTR;
1864
1865 return conf;
1866 }
1867
1868
1869 static void *
ngx_stream_js_create_srv_conf(ngx_conf_t * cf)1870 ngx_stream_js_create_srv_conf(ngx_conf_t *cf)
1871 {
1872 ngx_stream_js_srv_conf_t *conf;
1873
1874 conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_js_srv_conf_t));
1875 if (conf == NULL) {
1876 return NULL;
1877 }
1878
1879 /*
1880 * set by ngx_pcalloc():
1881 *
1882 * conf->access = { 0, NULL };
1883 * conf->preread = { 0, NULL };
1884 * conf->filter = { 0, NULL };
1885 * conf->ssl_ciphers = { 0, NULL };
1886 * conf->ssl_protocols = 0;
1887 * conf->ssl_trusted_certificate = { 0, NULL };
1888 */
1889
1890 #if (NGX_STREAM_SSL)
1891 conf->ssl_verify_depth = NGX_CONF_UNSET;
1892 #endif
1893 return conf;
1894 }
1895
1896
1897 static char *
ngx_stream_js_merge_srv_conf(ngx_conf_t * cf,void * parent,void * child)1898 ngx_stream_js_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
1899 {
1900 ngx_stream_js_srv_conf_t *prev = parent;
1901 ngx_stream_js_srv_conf_t *conf = child;
1902
1903 ngx_conf_merge_str_value(conf->access, prev->access, "");
1904 ngx_conf_merge_str_value(conf->preread, prev->preread, "");
1905 ngx_conf_merge_str_value(conf->filter, prev->filter, "");
1906
1907 #if (NGX_STREAM_SSL)
1908 ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers, "DEFAULT");
1909
1910 ngx_conf_merge_bitmask_value(conf->ssl_protocols, prev->ssl_protocols,
1911 (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1
1912 |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2));
1913
1914 ngx_conf_merge_value(conf->ssl_verify_depth, prev->ssl_verify_depth, 100);
1915
1916 ngx_conf_merge_str_value(conf->ssl_trusted_certificate,
1917 prev->ssl_trusted_certificate, "");
1918
1919 return ngx_stream_js_set_ssl(cf, conf);
1920 #else
1921 return NGX_CONF_OK;
1922 #endif
1923 }
1924
1925
1926 static ngx_int_t
ngx_stream_js_init(ngx_conf_t * cf)1927 ngx_stream_js_init(ngx_conf_t *cf)
1928 {
1929 ngx_stream_handler_pt *h;
1930 ngx_stream_core_main_conf_t *cmcf;
1931
1932 ngx_stream_next_filter = ngx_stream_top_filter;
1933 ngx_stream_top_filter = ngx_stream_js_body_filter;
1934
1935 cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
1936
1937 h = ngx_array_push(&cmcf->phases[NGX_STREAM_ACCESS_PHASE].handlers);
1938 if (h == NULL) {
1939 return NGX_ERROR;
1940 }
1941
1942 *h = ngx_stream_js_access_handler;
1943
1944 h = ngx_array_push(&cmcf->phases[NGX_STREAM_PREREAD_PHASE].handlers);
1945 if (h == NULL) {
1946 return NGX_ERROR;
1947 }
1948
1949 *h = ngx_stream_js_preread_handler;
1950
1951 return NGX_OK;
1952 }
1953
1954
1955 #if (NGX_STREAM_SSL)
1956
1957 static char *
ngx_stream_js_set_ssl(ngx_conf_t * cf,ngx_stream_js_srv_conf_t * jscf)1958 ngx_stream_js_set_ssl(ngx_conf_t *cf, ngx_stream_js_srv_conf_t *jscf)
1959 {
1960 ngx_ssl_t *ssl;
1961 ngx_pool_cleanup_t *cln;
1962
1963 ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t));
1964 if (ssl == NULL) {
1965 return NGX_CONF_ERROR;
1966 }
1967
1968 jscf->ssl = ssl;
1969 ssl->log = cf->log;
1970
1971 if (ngx_ssl_create(ssl, jscf->ssl_protocols, NULL) != NGX_OK) {
1972 return NGX_CONF_ERROR;
1973 }
1974
1975 cln = ngx_pool_cleanup_add(cf->pool, 0);
1976 if (cln == NULL) {
1977 ngx_ssl_cleanup_ctx(ssl);
1978 return NGX_CONF_ERROR;
1979 }
1980
1981 cln->handler = ngx_ssl_cleanup_ctx;
1982 cln->data = ssl;
1983
1984 if (ngx_ssl_ciphers(NULL, ssl, &jscf->ssl_ciphers, 0) != NGX_OK) {
1985 return NGX_CONF_ERROR;
1986 }
1987
1988 if (ngx_ssl_trusted_certificate(cf, ssl, &jscf->ssl_trusted_certificate,
1989 jscf->ssl_verify_depth)
1990 != NGX_OK)
1991 {
1992 return NGX_CONF_ERROR;
1993 }
1994
1995 return NGX_CONF_OK;
1996 }
1997
1998 #endif
1999
2000
2001 static ngx_ssl_t *
ngx_stream_js_ssl(njs_vm_t * vm,ngx_stream_session_t * s)2002 ngx_stream_js_ssl(njs_vm_t *vm, ngx_stream_session_t *s)
2003 {
2004 #if (NGX_STREAM_SSL)
2005 ngx_stream_js_srv_conf_t *jscf;
2006
2007 jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module);
2008
2009 return jscf->ssl;
2010 #else
2011 return NULL;
2012 #endif
2013 }
2014