| |
| /* |
| * Copyright (C) Roman Arutyunyan |
| * Copyright (C) Dmitry Volyntsev |
| * Copyright (C) NGINX, Inc. |
| */ |
| |
| |
| #include <ngx_config.h> |
| #include <ngx_core.h> |
| #include <ngx_http.h> |
| #include "ngx_js.h" |
| |
| |
| #define NJS_HEADER_SEMICOLON 0x1 |
| #define NJS_HEADER_SINGLE 0x2 |
| #define NJS_HEADER_ARRAY 0x4 |
| |
| |
| typedef struct { |
| njs_vm_t *vm; |
| ngx_array_t *imports; |
| ngx_array_t *paths; |
| } ngx_http_js_main_conf_t; |
| |
| |
| typedef struct { |
| ngx_str_t content; |
| ngx_str_t header_filter; |
| ngx_str_t body_filter; |
| ngx_uint_t buffer_type; |
| |
| size_t buffer_size; |
| size_t max_response_body_size; |
| ngx_msec_t timeout; |
| |
| #if (NGX_HTTP_SSL) |
| ngx_ssl_t *ssl; |
| ngx_str_t ssl_ciphers; |
| ngx_uint_t ssl_protocols; |
| ngx_flag_t ssl_verify; |
| ngx_int_t ssl_verify_depth; |
| ngx_str_t ssl_trusted_certificate; |
| #endif |
| } ngx_http_js_loc_conf_t; |
| |
| |
| typedef struct { |
| ngx_str_t name; |
| ngx_str_t path; |
| u_char *file; |
| ngx_uint_t line; |
| } ngx_http_js_import_t; |
| |
| |
| typedef struct { |
| njs_vm_t *vm; |
| ngx_log_t *log; |
| ngx_uint_t done; |
| ngx_int_t status; |
| njs_opaque_value_t retval; |
| njs_opaque_value_t request; |
| njs_opaque_value_t request_body; |
| njs_opaque_value_t response_body; |
| ngx_str_t redirect_uri; |
| ngx_array_t promise_callbacks; |
| |
| ngx_int_t filter; |
| ngx_buf_t *buf; |
| ngx_chain_t **last_out; |
| ngx_chain_t *free; |
| ngx_chain_t *busy; |
| } ngx_http_js_ctx_t; |
| |
| |
| typedef struct { |
| ngx_http_request_t *request; |
| njs_opaque_value_t callbacks[2]; |
| } ngx_http_js_cb_t; |
| |
| |
| typedef struct { |
| ngx_http_request_t *request; |
| njs_vm_event_t vm_event; |
| void *unused; |
| ngx_int_t ident; |
| } ngx_http_js_event_t; |
| |
| |
| typedef struct { |
| njs_str_t name; |
| #if defined(nginx_version) && (nginx_version >= 1023000) |
| unsigned flags; |
| njs_int_t (*handler)(njs_vm_t *vm, ngx_http_request_t *r, |
| unsigned flags, njs_str_t *name, |
| njs_value_t *setval, njs_value_t *retval); |
| #else |
| njs_int_t (*handler)(njs_vm_t *vm, ngx_http_request_t *r, |
| ngx_list_t *headers, njs_str_t *name, |
| njs_value_t *setval, njs_value_t *retval); |
| |
| #endif |
| } ngx_http_js_header_t; |
| |
| |
| static ngx_int_t ngx_http_js_content_handler(ngx_http_request_t *r); |
| static void ngx_http_js_content_event_handler(ngx_http_request_t *r); |
| static void ngx_http_js_content_write_event_handler(ngx_http_request_t *r); |
| static void ngx_http_js_content_finalize(ngx_http_request_t *r, |
| ngx_http_js_ctx_t *ctx); |
| static ngx_int_t ngx_http_js_header_filter(ngx_http_request_t *r); |
| static ngx_int_t ngx_http_js_variable_set(ngx_http_request_t *r, |
| ngx_http_variable_value_t *v, uintptr_t data); |
| static ngx_int_t ngx_http_js_variable_var(ngx_http_request_t *r, |
| ngx_http_variable_value_t *v, uintptr_t data); |
| static ngx_int_t ngx_http_js_init_vm(ngx_http_request_t *r); |
| static void ngx_http_js_cleanup_ctx(void *data); |
| static void ngx_http_js_cleanup_vm(void *data); |
| |
| static njs_int_t ngx_http_js_ext_keys_header(njs_vm_t *vm, njs_value_t *value, |
| njs_value_t *keys, ngx_list_t *headers); |
| #if defined(nginx_version) && (nginx_version < 1023000) |
| static ngx_table_elt_t *ngx_http_js_get_header(ngx_list_part_t *part, |
| u_char *data, size_t len); |
| #endif |
| static njs_int_t ngx_http_js_ext_raw_header(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_http_js_ext_header_out(njs_vm_t *vm, |
| njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, |
| njs_value_t *retval); |
| #if defined(nginx_version) && (nginx_version < 1023000) |
| static njs_int_t ngx_http_js_header_single(njs_vm_t *vm, |
| ngx_http_request_t *r, ngx_list_t *headers, njs_str_t *name, |
| njs_value_t *setval, njs_value_t *retval); |
| static njs_int_t ngx_http_js_header_out_special(njs_vm_t *vm, |
| ngx_http_request_t *r, njs_str_t *v, njs_value_t *setval, |
| njs_value_t *retval, ngx_table_elt_t **hh); |
| static njs_int_t ngx_http_js_header_array(njs_vm_t *vm, |
| ngx_http_request_t *r, ngx_list_t *headers, njs_str_t *name, |
| njs_value_t *setval, njs_value_t *retval); |
| static njs_int_t ngx_http_js_header_generic(njs_vm_t *vm, |
| ngx_http_request_t *r, ngx_list_t *headers, njs_str_t *name, |
| njs_value_t *setval, njs_value_t *retval); |
| static njs_int_t ngx_http_js_content_length(njs_vm_t *vm, ngx_http_request_t *r, |
| ngx_list_t *headers, njs_str_t *name, njs_value_t *setval, |
| njs_value_t *retval); |
| static njs_int_t ngx_http_js_content_encoding(njs_vm_t *vm, |
| ngx_http_request_t *r, ngx_list_t *headers, njs_str_t *name, |
| njs_value_t *setval, njs_value_t *retval); |
| static njs_int_t ngx_http_js_content_type(njs_vm_t *vm, ngx_http_request_t *r, |
| ngx_list_t *headers, njs_str_t *name, njs_value_t *setval, |
| njs_value_t *retval); |
| #endif |
| static njs_int_t ngx_http_js_ext_keys_header_out(njs_vm_t *vm, |
| njs_value_t *value, njs_value_t *keys); |
| static njs_int_t ngx_http_js_ext_status(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_http_js_ext_send_header(njs_vm_t *vm, njs_value_t *args, |
| njs_uint_t nargs, njs_index_t unused); |
| static njs_int_t ngx_http_js_ext_send(njs_vm_t *vm, njs_value_t *args, |
| njs_uint_t nargs, njs_index_t unused); |
| static njs_int_t ngx_http_js_ext_send_buffer(njs_vm_t *vm, njs_value_t *args, |
| njs_uint_t nargs, njs_index_t unused); |
| static njs_int_t ngx_http_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_http_js_ext_done(njs_vm_t *vm, njs_value_t *args, |
| njs_uint_t nargs, njs_index_t unused); |
| static njs_int_t ngx_http_js_ext_finish(njs_vm_t *vm, njs_value_t *args, |
| njs_uint_t nargs, njs_index_t unused); |
| static njs_int_t ngx_http_js_ext_return(njs_vm_t *vm, njs_value_t *args, |
| njs_uint_t nargs, njs_index_t unused); |
| static njs_int_t ngx_http_js_ext_internal_redirect(njs_vm_t *vm, |
| njs_value_t *args, njs_uint_t nargs, njs_index_t unused); |
| |
| static njs_int_t ngx_http_js_ext_get_http_version(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_http_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_http_js_ext_get_request_body(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_http_js_ext_header_in(njs_vm_t *vm, |
| njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, |
| njs_value_t *retval); |
| #if defined(nginx_version) && (nginx_version < 1023000) |
| static njs_int_t ngx_http_js_header_cookie(njs_vm_t *vm, |
| ngx_http_request_t *r, ngx_list_t *headers, njs_str_t *name, |
| njs_value_t *setval, njs_value_t *retval); |
| #if (NGX_HTTP_X_FORWARDED_FOR) |
| static njs_int_t ngx_http_js_header_x_forwarded_for(njs_vm_t *vm, |
| ngx_http_request_t *r, ngx_list_t *headers, njs_str_t *name, |
| njs_value_t *setval, njs_value_t *retval); |
| #endif |
| static njs_int_t ngx_http_js_header_in_array(njs_vm_t *vm, |
| ngx_http_request_t *r, ngx_array_t *array, u_char sep, njs_value_t *retval); |
| #endif |
| static njs_int_t ngx_http_js_ext_keys_header_in(njs_vm_t *vm, |
| njs_value_t *value, njs_value_t *keys); |
| static njs_int_t ngx_http_js_ext_get_arg(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_http_js_ext_keys_arg(njs_vm_t *vm, njs_value_t *value, |
| njs_value_t *keys); |
| static njs_int_t ngx_http_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_int_t ngx_http_js_ext_subrequest(njs_vm_t *vm, njs_value_t *args, |
| njs_uint_t nargs, njs_index_t unused); |
| static ngx_int_t ngx_http_js_subrequest(ngx_http_request_t *r, |
| njs_str_t *uri_arg, njs_str_t *args_arg, njs_function_t *callback, |
| ngx_http_request_t **sr); |
| static ngx_int_t ngx_http_js_subrequest_done(ngx_http_request_t *r, |
| void *data, ngx_int_t rc); |
| static njs_int_t ngx_http_js_ext_get_parent(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_http_js_ext_get_response_body(njs_vm_t *vm, |
| njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, |
| njs_value_t *retval); |
| |
| #if defined(nginx_version) && (nginx_version >= 1023000) |
| static njs_int_t ngx_http_js_header_in(njs_vm_t *vm, ngx_http_request_t *r, |
| unsigned flags, njs_str_t *name, njs_value_t *retval); |
| static njs_int_t ngx_http_js_header_out(njs_vm_t *vm, ngx_http_request_t *r, |
| unsigned flags, njs_str_t *name, njs_value_t *setval, njs_value_t *retval); |
| static njs_int_t ngx_http_js_content_encoding(njs_vm_t *vm, |
| ngx_http_request_t *r, unsigned flags, njs_str_t *name, |
| njs_value_t *setval, njs_value_t *retval); |
| static njs_int_t ngx_http_js_content_length(njs_vm_t *vm, ngx_http_request_t *r, |
| unsigned flags, njs_str_t *name, njs_value_t *setval, |
| njs_value_t *retval); |
| static njs_int_t ngx_http_js_content_type(njs_vm_t *vm, ngx_http_request_t *r, |
| unsigned flags, njs_str_t *name, njs_value_t *setval, |
| njs_value_t *retval); |
| static njs_int_t ngx_http_js_header_out_special(njs_vm_t *vm, |
| ngx_http_request_t *r, njs_str_t *v, njs_value_t *setval, |
| njs_value_t *retval, ngx_table_elt_t **hh); |
| static njs_int_t ngx_http_js_header_generic(njs_vm_t *vm, |
| ngx_http_request_t *r, ngx_list_t *headers, ngx_table_elt_t **ph, |
| unsigned flags, njs_str_t *name, njs_value_t *retval); |
| #endif |
| |
| static njs_host_event_t ngx_http_js_set_timer(njs_external_ptr_t external, |
| uint64_t delay, njs_vm_event_t vm_event); |
| static void ngx_http_js_clear_timer(njs_external_ptr_t external, |
| njs_host_event_t event); |
| static void ngx_http_js_timer_handler(ngx_event_t *ev); |
| static ngx_pool_t *ngx_http_js_pool(njs_vm_t *vm, ngx_http_request_t *r); |
| static ngx_resolver_t *ngx_http_js_resolver(njs_vm_t *vm, |
| ngx_http_request_t *r); |
| static ngx_msec_t ngx_http_js_resolver_timeout(njs_vm_t *vm, |
| ngx_http_request_t *r); |
| static ngx_msec_t ngx_http_js_fetch_timeout(njs_vm_t *vm, |
| ngx_http_request_t *r); |
| static size_t ngx_http_js_buffer_size(njs_vm_t *vm, ngx_http_request_t *r); |
| static size_t ngx_http_js_max_response_buffer_size(njs_vm_t *vm, |
| ngx_http_request_t *r); |
| static void ngx_http_js_handle_vm_event(ngx_http_request_t *r, |
| njs_vm_event_t vm_event, njs_value_t *args, njs_uint_t nargs); |
| static void ngx_http_js_handle_event(ngx_http_request_t *r, |
| njs_vm_event_t vm_event, njs_value_t *args, njs_uint_t nargs); |
| |
| static ngx_int_t ngx_http_js_init(ngx_conf_t *cf); |
| static char *ngx_http_js_import(ngx_conf_t *cf, ngx_command_t *cmd, |
| void *conf); |
| static char *ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); |
| static char *ngx_http_js_var(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); |
| static char *ngx_http_js_content(ngx_conf_t *cf, ngx_command_t *cmd, |
| void *conf); |
| static char *ngx_http_js_body_filter_set(ngx_conf_t *cf, ngx_command_t *cmd, |
| void *conf); |
| static void *ngx_http_js_create_main_conf(ngx_conf_t *cf); |
| static char *ngx_http_js_init_main_conf(ngx_conf_t *cf, void *conf); |
| static void *ngx_http_js_create_loc_conf(ngx_conf_t *cf); |
| static char *ngx_http_js_merge_loc_conf(ngx_conf_t *cf, void *parent, |
| void *child); |
| |
| #if (NGX_HTTP_SSL) |
| static char * ngx_http_js_set_ssl(ngx_conf_t *cf, ngx_http_js_loc_conf_t *jlcf); |
| #endif |
| static ngx_ssl_t *ngx_http_js_ssl(njs_vm_t *vm, ngx_http_request_t *r); |
| static ngx_flag_t ngx_http_js_ssl_verify(njs_vm_t *vm, ngx_http_request_t *r); |
| |
| #if (NGX_HTTP_SSL) |
| |
| static ngx_conf_bitmask_t ngx_http_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_http_js_commands[] = { |
| |
| { ngx_string("js_import"), |
| NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE13, |
| ngx_http_js_import, |
| NGX_HTTP_MAIN_CONF_OFFSET, |
| 0, |
| NULL }, |
| |
| { ngx_string("js_path"), |
| NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, |
| ngx_conf_set_str_array_slot, |
| NGX_HTTP_MAIN_CONF_OFFSET, |
| offsetof(ngx_http_js_main_conf_t, paths), |
| NULL }, |
| |
| { ngx_string("js_set"), |
| NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE2, |
| ngx_http_js_set, |
| 0, |
| 0, |
| NULL }, |
| |
| { ngx_string("js_var"), |
| NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE12, |
| ngx_http_js_var, |
| 0, |
| 0, |
| NULL }, |
| |
| { ngx_string("js_content"), |
| NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1, |
| ngx_http_js_content, |
| NGX_HTTP_LOC_CONF_OFFSET, |
| 0, |
| NULL }, |
| |
| { ngx_string("js_header_filter"), |
| NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1, |
| ngx_conf_set_str_slot, |
| NGX_HTTP_LOC_CONF_OFFSET, |
| offsetof(ngx_http_js_loc_conf_t, header_filter), |
| NULL }, |
| |
| { ngx_string("js_body_filter"), |
| NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE12, |
| ngx_http_js_body_filter_set, |
| NGX_HTTP_LOC_CONF_OFFSET, |
| 0, |
| NULL }, |
| |
| { ngx_string("js_fetch_buffer_size"), |
| NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, |
| ngx_conf_set_size_slot, |
| NGX_HTTP_LOC_CONF_OFFSET, |
| offsetof(ngx_http_js_loc_conf_t, buffer_size), |
| NULL }, |
| |
| { ngx_string("js_fetch_max_response_buffer_size"), |
| NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, |
| ngx_conf_set_size_slot, |
| NGX_HTTP_LOC_CONF_OFFSET, |
| offsetof(ngx_http_js_loc_conf_t, max_response_body_size), |
| NULL }, |
| |
| { ngx_string("js_fetch_timeout"), |
| NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, |
| ngx_conf_set_msec_slot, |
| NGX_HTTP_LOC_CONF_OFFSET, |
| offsetof(ngx_http_js_loc_conf_t, timeout), |
| NULL }, |
| |
| #if (NGX_HTTP_SSL) |
| |
| { ngx_string("js_fetch_ciphers"), |
| NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, |
| ngx_conf_set_str_slot, |
| NGX_HTTP_LOC_CONF_OFFSET, |
| offsetof(ngx_http_js_loc_conf_t, ssl_ciphers), |
| NULL }, |
| |
| { ngx_string("js_fetch_protocols"), |
| NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, |
| ngx_conf_set_bitmask_slot, |
| NGX_HTTP_LOC_CONF_OFFSET, |
| offsetof(ngx_http_js_loc_conf_t, ssl_protocols), |
| &ngx_http_js_ssl_protocols }, |
| |
| { ngx_string("js_fetch_verify"), |
| NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, |
| ngx_conf_set_flag_slot, |
| NGX_HTTP_LOC_CONF_OFFSET, |
| offsetof(ngx_http_js_loc_conf_t, ssl_verify), |
| NULL }, |
| |
| { ngx_string("js_fetch_verify_depth"), |
| NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, |
| ngx_conf_set_num_slot, |
| NGX_HTTP_LOC_CONF_OFFSET, |
| offsetof(ngx_http_js_loc_conf_t, ssl_verify_depth), |
| NULL }, |
| |
| { ngx_string("js_fetch_trusted_certificate"), |
| NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, |
| ngx_conf_set_str_slot, |
| NGX_HTTP_LOC_CONF_OFFSET, |
| offsetof(ngx_http_js_loc_conf_t, ssl_trusted_certificate), |
| NULL }, |
| |
| #endif |
| |
| ngx_null_command |
| }; |
| |
| |
| static ngx_http_module_t ngx_http_js_module_ctx = { |
| NULL, /* preconfiguration */ |
| ngx_http_js_init, /* postconfiguration */ |
| |
| ngx_http_js_create_main_conf, /* create main configuration */ |
| ngx_http_js_init_main_conf, /* init main configuration */ |
| |
| NULL, /* create server configuration */ |
| NULL, /* merge server configuration */ |
| |
| ngx_http_js_create_loc_conf, /* create location configuration */ |
| ngx_http_js_merge_loc_conf /* merge location configuration */ |
| }; |
| |
| |
| ngx_module_t ngx_http_js_module = { |
| NGX_MODULE_V1, |
| &ngx_http_js_module_ctx, /* module context */ |
| ngx_http_js_commands, /* module directives */ |
| NGX_HTTP_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 ngx_http_output_header_filter_pt ngx_http_next_header_filter; |
| static ngx_http_output_body_filter_pt ngx_http_next_body_filter; |
| |
| |
| static njs_int_t ngx_http_js_request_proto_id; |
| |
| |
| static njs_external_t ngx_http_js_ext_request[] = { |
| |
| { |
| .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL, |
| .name.symbol = NJS_SYMBOL_TO_STRING_TAG, |
| .u.property = { |
| .value = "Request", |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_PROPERTY, |
| .name.string = njs_str("uri"), |
| .enumerable = 1, |
| .u.property = { |
| .handler = ngx_js_ext_string, |
| .magic32 = offsetof(ngx_http_request_t, uri), |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_PROPERTY, |
| .name.string = njs_str("method"), |
| .enumerable = 1, |
| .u.property = { |
| .handler = ngx_js_ext_string, |
| .magic32 = offsetof(ngx_http_request_t, method_name), |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_PROPERTY, |
| .name.string = njs_str("httpVersion"), |
| .enumerable = 1, |
| .u.property = { |
| .handler = ngx_http_js_ext_get_http_version, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_PROPERTY, |
| .name.string = njs_str("remoteAddress"), |
| .enumerable = 1, |
| .u.property = { |
| .handler = ngx_http_js_ext_get_remote_address, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_PROPERTY, |
| .name.string = njs_str("requestBody"), |
| .u.property = { |
| .handler = ngx_http_js_ext_get_request_body, |
| .magic32 = NGX_JS_STRING, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_PROPERTY, |
| .name.string = njs_str("requestText"), |
| .enumerable = 1, |
| .u.property = { |
| .handler = ngx_http_js_ext_get_request_body, |
| .magic32 = NGX_JS_STRING, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_PROPERTY, |
| .name.string = njs_str("requestBuffer"), |
| .u.property = { |
| .handler = ngx_http_js_ext_get_request_body, |
| .magic32 = NGX_JS_BUFFER, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_PROPERTY, |
| .name.string = njs_str("parent"), |
| .u.property = { |
| .handler = ngx_http_js_ext_get_parent, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_PROPERTY, |
| .name.string = njs_str("responseBody"), |
| .u.property = { |
| .handler = ngx_http_js_ext_get_response_body, |
| .magic32 = NGX_JS_STRING, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_PROPERTY, |
| .name.string = njs_str("responseText"), |
| .enumerable = 1, |
| .u.property = { |
| .handler = ngx_http_js_ext_get_response_body, |
| .magic32 = NGX_JS_STRING, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_PROPERTY, |
| .name.string = njs_str("responseBuffer"), |
| .u.property = { |
| .handler = ngx_http_js_ext_get_response_body, |
| .magic32 = NGX_JS_BUFFER, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_OBJECT, |
| .name.string = njs_str("headersIn"), |
| .enumerable = 1, |
| .u.object = { |
| .enumerable = 1, |
| .prop_handler = ngx_http_js_ext_header_in, |
| .keys = ngx_http_js_ext_keys_header_in, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_PROPERTY, |
| .name.string = njs_str("rawHeadersIn"), |
| .u.property = { |
| .handler = ngx_http_js_ext_raw_header, |
| .magic32 = 0, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_OBJECT, |
| .name.string = njs_str("args"), |
| .enumerable = 1, |
| .u.object = { |
| .enumerable = 1, |
| .prop_handler = ngx_http_js_ext_get_arg, |
| .keys = ngx_http_js_ext_keys_arg, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_OBJECT, |
| .name.string = njs_str("variables"), |
| .u.object = { |
| .writable = 1, |
| .prop_handler = ngx_http_js_ext_variables, |
| .magic32 = NGX_JS_STRING, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_OBJECT, |
| .name.string = njs_str("rawVariables"), |
| .u.object = { |
| .writable = 1, |
| .prop_handler = ngx_http_js_ext_variables, |
| .magic32 = NGX_JS_BUFFER, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_PROPERTY, |
| .name.string = njs_str("status"), |
| .writable = 1, |
| .enumerable = 1, |
| .u.property = { |
| .handler = ngx_http_js_ext_status, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_OBJECT, |
| .name.string = njs_str("headersOut"), |
| .enumerable = 1, |
| .u.object = { |
| .writable = 1, |
| .configurable = 1, |
| .enumerable = 1, |
| .prop_handler = ngx_http_js_ext_header_out, |
| .keys = ngx_http_js_ext_keys_header_out, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_PROPERTY, |
| .name.string = njs_str("rawHeadersOut"), |
| .u.property = { |
| .handler = ngx_http_js_ext_raw_header, |
| .magic32 = 1, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_METHOD, |
| .name.string = njs_str("subrequest"), |
| .writable = 1, |
| .configurable = 1, |
| .enumerable = 1, |
| .u.method = { |
| .native = ngx_http_js_ext_subrequest, |
| } |
| }, |
| |
| { |
| .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("sendHeader"), |
| .writable = 1, |
| .configurable = 1, |
| .enumerable = 1, |
| .u.method = { |
| .native = ngx_http_js_ext_send_header, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_METHOD, |
| .name.string = njs_str("send"), |
| .writable = 1, |
| .configurable = 1, |
| .enumerable = 1, |
| .u.method = { |
| .native = ngx_http_js_ext_send, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_METHOD, |
| .name.string = njs_str("sendBuffer"), |
| .writable = 1, |
| .configurable = 1, |
| .enumerable = 1, |
| .u.method = { |
| .native = ngx_http_js_ext_send_buffer, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_METHOD, |
| .name.string = njs_str("setReturnValue"), |
| .writable = 1, |
| .configurable = 1, |
| .enumerable = 1, |
| .u.method = { |
| .native = ngx_http_js_ext_set_return_value, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_METHOD, |
| .name.string = njs_str("done"), |
| .writable = 1, |
| .configurable = 1, |
| .enumerable = 1, |
| .u.method = { |
| .native = ngx_http_js_ext_done, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_METHOD, |
| .name.string = njs_str("finish"), |
| .writable = 1, |
| .configurable = 1, |
| .enumerable = 1, |
| .u.method = { |
| .native = ngx_http_js_ext_finish, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_METHOD, |
| .name.string = njs_str("return"), |
| .writable = 1, |
| .configurable = 1, |
| .enumerable = 1, |
| .u.method = { |
| .native = ngx_http_js_ext_return, |
| } |
| }, |
| |
| { |
| .flags = NJS_EXTERN_METHOD, |
| .name.string = njs_str("internalRedirect"), |
| .writable = 1, |
| .configurable = 1, |
| .enumerable = 1, |
| .u.method = { |
| .native = ngx_http_js_ext_internal_redirect, |
| } |
| }, |
| |
| }; |
| |
| |
| static njs_vm_ops_t ngx_http_js_ops = { |
| ngx_http_js_set_timer, |
| ngx_http_js_clear_timer, |
| NULL, |
| }; |
| |
| |
| static uintptr_t ngx_http_js_uptr[] = { |
| offsetof(ngx_http_request_t, connection), |
| (uintptr_t) ngx_http_js_pool, |
| (uintptr_t) ngx_http_js_resolver, |
| (uintptr_t) ngx_http_js_resolver_timeout, |
| (uintptr_t) ngx_http_js_handle_event, |
| (uintptr_t) ngx_http_js_ssl, |
| (uintptr_t) ngx_http_js_ssl_verify, |
| (uintptr_t) ngx_http_js_fetch_timeout, |
| (uintptr_t) ngx_http_js_buffer_size, |
| (uintptr_t) ngx_http_js_max_response_buffer_size, |
| }; |
| |
| |
| static njs_vm_meta_t ngx_http_js_metas = { |
| .size = njs_nitems(ngx_http_js_uptr), |
| .values = ngx_http_js_uptr |
| }; |
| |
| |
| static ngx_int_t |
| ngx_http_js_content_handler(ngx_http_request_t *r) |
| { |
| ngx_int_t rc; |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "http js content handler"); |
| |
| rc = ngx_http_read_client_request_body(r, |
| ngx_http_js_content_event_handler); |
| |
| if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { |
| return rc; |
| } |
| |
| return NGX_DONE; |
| } |
| |
| |
| static void |
| ngx_http_js_content_event_handler(ngx_http_request_t *r) |
| { |
| ngx_int_t rc; |
| ngx_http_js_ctx_t *ctx; |
| ngx_http_js_loc_conf_t *jlcf; |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "http js content event handler"); |
| |
| rc = ngx_http_js_init_vm(r); |
| |
| if (rc == NGX_ERROR || rc == NGX_DECLINED) { |
| ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); |
| return; |
| } |
| |
| jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); |
| |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "http js content call \"%V\"" , &jlcf->content); |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); |
| |
| /* |
| * status is expected to be overriden by finish(), return() or |
| * internalRedirect() methods, otherwise the content handler is |
| * considered invalid. |
| */ |
| |
| ctx->status = NGX_HTTP_INTERNAL_SERVER_ERROR; |
| |
| rc = ngx_js_call(ctx->vm, &jlcf->content, r->connection->log, |
| &ctx->request, 1); |
| |
| if (rc == NGX_ERROR) { |
| ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); |
| return; |
| } |
| |
| if (rc == NGX_AGAIN) { |
| r->write_event_handler = ngx_http_js_content_write_event_handler; |
| return; |
| } |
| |
| ngx_http_js_content_finalize(r, ctx); |
| } |
| |
| |
| static void |
| ngx_http_js_content_write_event_handler(ngx_http_request_t *r) |
| { |
| ngx_event_t *wev; |
| ngx_connection_t *c; |
| ngx_http_js_ctx_t *ctx; |
| ngx_http_core_loc_conf_t *clcf; |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "http js content write event handler"); |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); |
| |
| if (!njs_vm_pending(ctx->vm)) { |
| ngx_http_js_content_finalize(r, ctx); |
| return; |
| } |
| |
| c = r->connection; |
| wev = c->write; |
| |
| if (wev->timedout) { |
| ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out"); |
| ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT); |
| return; |
| } |
| |
| if (ngx_http_output_filter(r, NULL) == NGX_ERROR) { |
| ngx_http_finalize_request(r, NGX_ERROR); |
| return; |
| } |
| |
| clcf = ngx_http_get_module_loc_conf(r->main, ngx_http_core_module); |
| |
| if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) { |
| ngx_http_finalize_request(r, NGX_ERROR); |
| return; |
| } |
| |
| if (!wev->delayed) { |
| if (wev->active && !wev->ready) { |
| ngx_add_timer(wev, clcf->send_timeout); |
| |
| } else if (wev->timer_set) { |
| ngx_del_timer(wev); |
| } |
| } |
| } |
| |
| |
| static void |
| ngx_http_js_content_finalize(ngx_http_request_t *r, ngx_http_js_ctx_t *ctx) |
| { |
| ngx_str_t args; |
| ngx_int_t rc; |
| ngx_uint_t flags; |
| |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "http js content rc: %i", ctx->status); |
| |
| if (ctx->redirect_uri.len) { |
| if (ctx->redirect_uri.data[0] == '@') { |
| ngx_http_named_location(r, &ctx->redirect_uri); |
| |
| } else { |
| ngx_str_null(&args); |
| flags = NGX_HTTP_LOG_UNSAFE; |
| |
| rc = ngx_http_parse_unsafe_uri(r, &ctx->redirect_uri, &args, |
| &flags); |
| if (rc != NGX_OK) { |
| ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); |
| return; |
| } |
| |
| ngx_http_internal_redirect(r, &ctx->redirect_uri, &args); |
| } |
| } |
| |
| ngx_http_finalize_request(r, ctx->status); |
| } |
| |
| |
| static ngx_int_t |
| ngx_http_js_header_filter(ngx_http_request_t *r) |
| { |
| ngx_int_t rc; |
| njs_int_t pending; |
| ngx_http_js_ctx_t *ctx; |
| ngx_http_js_loc_conf_t *jlcf; |
| |
| jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); |
| |
| if (jlcf->header_filter.len == 0) { |
| return ngx_http_next_header_filter(r); |
| } |
| |
| rc = ngx_http_js_init_vm(r); |
| |
| if (rc == NGX_ERROR || rc == NGX_DECLINED) { |
| return NGX_ERROR; |
| } |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); |
| |
| pending = njs_vm_pending(ctx->vm); |
| |
| rc = ngx_js_call(ctx->vm, &jlcf->header_filter, r->connection->log, |
| &ctx->request, 1); |
| |
| if (rc == NGX_ERROR) { |
| return NGX_ERROR; |
| } |
| |
| if (!pending && rc == NGX_AGAIN) { |
| ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, |
| "async operation inside \"%V\" header filter", |
| &jlcf->header_filter); |
| return NGX_ERROR; |
| } |
| |
| return ngx_http_next_header_filter(r); |
| } |
| |
| |
| static ngx_int_t |
| ngx_http_js_body_filter(ngx_http_request_t *r, ngx_chain_t *in) |
| { |
| size_t len; |
| u_char *p; |
| ngx_int_t rc; |
| njs_int_t ret, pending; |
| ngx_buf_t *b; |
| ngx_chain_t *out, *cl; |
| ngx_connection_t *c; |
| ngx_http_js_ctx_t *ctx; |
| njs_opaque_value_t last_key, last; |
| ngx_http_js_loc_conf_t *jlcf; |
| njs_opaque_value_t arguments[3]; |
| |
| static const njs_str_t last_str = njs_str("last"); |
| |
| jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); |
| |
| if (jlcf->body_filter.len == 0) { |
| return ngx_http_next_body_filter(r, in); |
| } |
| |
| rc = ngx_http_js_init_vm(r); |
| |
| if (rc == NGX_ERROR || rc == NGX_DECLINED) { |
| return NGX_ERROR; |
| } |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); |
| |
| if (ctx->done) { |
| return ngx_http_next_body_filter(r, in); |
| } |
| |
| c = r->connection; |
| |
| ctx->filter = 1; |
| ctx->last_out = &out; |
| |
| njs_value_assign(&arguments[0], &ctx->request); |
| |
| njs_vm_value_string_set(ctx->vm, njs_value_arg(&last_key), |
| last_str.start, last_str.length); |
| |
| while (in != NULL) { |
| ctx->buf = in->buf; |
| b = ctx->buf; |
| |
| if (!ctx->done) { |
| len = b->last - b->pos; |
| |
| p = ngx_pnalloc(r->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, jlcf->buffer_type, |
| njs_value_arg(&arguments[1]), p, len); |
| if (ret != NJS_OK) { |
| return ret; |
| } |
| |
| njs_value_boolean_set(njs_value_arg(&last), b->last_buf); |
| |
| ret = njs_vm_object_alloc(ctx->vm, njs_value_arg(&arguments[2]), |
| njs_value_arg(&last_key), |
| njs_value_arg(&last), NULL); |
| if (ret != NJS_OK) { |
| return ret; |
| } |
| |
| pending = njs_vm_pending(ctx->vm); |
| |
| rc = ngx_js_call(ctx->vm, &jlcf->body_filter, c->log, &arguments[0], |
| 3); |
| |
| if (rc == NGX_ERROR) { |
| return NGX_ERROR; |
| } |
| |
| if (!pending && rc == NGX_AGAIN) { |
| ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, |
| "async operation inside \"%V\" body filter", |
| &jlcf->body_filter); |
| 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 = b; |
| |
| *ctx->last_out = cl; |
| ctx->last_out = &cl->next; |
| } |
| |
| in = in->next; |
| } |
| |
| *ctx->last_out = NULL; |
| |
| if (out != NULL || c->buffered) { |
| rc = ngx_http_next_body_filter(r, out); |
| |
| ngx_chain_update_chains(c->pool, &ctx->free, &ctx->busy, &out, |
| (ngx_buf_tag_t) &ngx_http_js_module); |
| |
| } else { |
| rc = NGX_OK; |
| } |
| |
| return rc; |
| } |
| |
| |
| static ngx_int_t |
| ngx_http_js_variable_set(ngx_http_request_t *r, ngx_http_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_http_js_ctx_t *ctx; |
| |
| rc = ngx_http_js_init_vm(r); |
| |
| if (rc == NGX_ERROR) { |
| return NGX_ERROR; |
| } |
| |
| if (rc == NGX_DECLINED) { |
| v->not_found = 1; |
| return NGX_OK; |
| } |
| |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "http js variable call \"%V\"", fname); |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); |
| |
| pending = njs_vm_pending(ctx->vm); |
| |
| rc = ngx_js_call(ctx->vm, fname, r->connection->log, &ctx->request, 1); |
| |
| if (rc == NGX_ERROR) { |
| v->not_found = 1; |
| return NGX_OK; |
| } |
| |
| if (!pending && rc == NGX_AGAIN) { |
| ngx_log_error(NGX_LOG_ERR, r->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_http_js_variable_var(ngx_http_request_t *r, ngx_http_variable_value_t *v, |
| uintptr_t data) |
| { |
| ngx_http_complex_value_t *cv = (ngx_http_complex_value_t *) data; |
| |
| ngx_str_t value; |
| |
| if (cv != NULL) { |
| if (ngx_http_complex_value(r, 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_http_js_init_vm(ngx_http_request_t *r) |
| { |
| njs_int_t rc; |
| ngx_str_t exception; |
| ngx_http_js_ctx_t *ctx; |
| ngx_pool_cleanup_t *cln; |
| ngx_http_js_main_conf_t *jmcf; |
| |
| jmcf = ngx_http_get_module_main_conf(r, ngx_http_js_module); |
| if (jmcf->vm == NULL) { |
| return NGX_DECLINED; |
| } |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); |
| |
| if (ctx == NULL) { |
| ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_js_ctx_t)); |
| if (ctx == NULL) { |
| return NGX_ERROR; |
| } |
| |
| njs_value_invalid_set(njs_value_arg(&ctx->retval)); |
| |
| ngx_http_set_ctx(r, ctx, ngx_http_js_module); |
| } |
| |
| if (ctx->vm) { |
| return NGX_OK; |
| } |
| |
| ctx->vm = njs_vm_clone(jmcf->vm, r); |
| if (ctx->vm == NULL) { |
| return NGX_ERROR; |
| } |
| |
| cln = ngx_pool_cleanup_add(r->pool, 0); |
| if (cln == NULL) { |
| return NGX_ERROR; |
| } |
| |
| ctx->log = r->connection->log; |
| |
| cln->handler = ngx_http_js_cleanup_ctx; |
| cln->data = ctx; |
| |
| if (njs_vm_start(ctx->vm) == NJS_ERROR) { |
| ngx_js_retval(ctx->vm, NULL, &exception); |
| |
| ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, |
| "js exception: %V", &exception); |
| |
| return NGX_ERROR; |
| } |
| |
| rc = njs_vm_external_create(ctx->vm, njs_value_arg(&ctx->request), |
| ngx_http_js_request_proto_id, r, 0); |
| if (rc != NJS_OK) { |
| return NGX_ERROR; |
| } |
| |
| return NGX_OK; |
| } |
| |
| |
| static void |
| ngx_http_js_cleanup_ctx(void *data) |
| { |
| ngx_http_js_ctx_t *ctx = data; |
| |
| if (njs_vm_pending(ctx->vm)) { |
| ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "pending events"); |
| } |
| |
| njs_vm_destroy(ctx->vm); |
| } |
| |
| |
| static void |
| ngx_http_js_cleanup_vm(void *data) |
| { |
| njs_vm_t *vm = data; |
| |
| njs_vm_destroy(vm); |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_ext_keys_header(njs_vm_t *vm, njs_value_t *value, njs_value_t *keys, |
| ngx_list_t *headers) |
| { |
| int64_t i, length; |
| njs_int_t rc; |
| njs_str_t hdr; |
| ngx_uint_t item; |
| njs_value_t *start; |
| ngx_list_part_t *part; |
| ngx_table_elt_t *header, *h; |
| |
| part = &headers->part; |
| item = 0; |
| length = 0; |
| |
| while (part) { |
| if (item >= part->nelts) { |
| part = part->next; |
| item = 0; |
| continue; |
| } |
| |
| header = part->elts; |
| h = &header[item++]; |
| |
| if (h->hash == 0) { |
| continue; |
| } |
| |
| start = njs_vm_array_start(vm, keys); |
| |
| for (i = 0; i < length; i++) { |
| njs_value_string_get(njs_argument(start, i), &hdr); |
| |
| if (h->key.len == hdr.length |
| && ngx_strncasecmp(h->key.data, hdr.start, hdr.length) == 0) |
| { |
| break; |
| } |
| } |
| |
| if (i == length) { |
| value = njs_vm_array_push(vm, keys); |
| if (value == NULL) { |
| return NJS_ERROR; |
| } |
| |
| rc = njs_vm_value_string_set(vm, value, h->key.data, h->key.len); |
| if (rc != NJS_OK) { |
| return NJS_ERROR; |
| } |
| |
| length++; |
| } |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| #if defined(nginx_version) && (nginx_version < 1023000) |
| static ngx_table_elt_t * |
| ngx_http_js_get_header(ngx_list_part_t *part, u_char *data, size_t len) |
| { |
| ngx_uint_t i; |
| ngx_table_elt_t *header, *h; |
| |
| header = part->elts; |
| |
| for (i = 0; /* void */ ; i++) { |
| |
| if (i >= part->nelts) { |
| if (part->next == NULL) { |
| break; |
| } |
| |
| part = part->next; |
| header = part->elts; |
| i = 0; |
| } |
| |
| h = &header[i]; |
| |
| if (h->hash == 0) { |
| continue; |
| } |
| |
| if (h->key.len == len && ngx_strncasecmp(h->key.data, data, len) == 0) { |
| return h; |
| } |
| } |
| |
| return NULL; |
| } |
| #endif |
| |
| |
| static njs_int_t |
| ngx_http_js_ext_raw_header(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; |
| ngx_uint_t i; |
| njs_value_t *array, *elem; |
| ngx_list_part_t *part; |
| ngx_list_t *headers; |
| ngx_table_elt_t *header, *h; |
| ngx_http_request_t *r; |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, value); |
| if (r == NULL) { |
| njs_value_undefined_set(retval); |
| return NJS_DECLINED; |
| } |
| |
| headers = (njs_vm_prop_magic32(prop) == 1) ? &r->headers_out.headers |
| : &r->headers_in.headers; |
| |
| rc = njs_vm_array_alloc(vm, retval, 8); |
| if (rc != NJS_OK) { |
| return NJS_ERROR; |
| } |
| |
| part = &headers->part; |
| header = part->elts; |
| |
| for (i = 0; /* void */ ; i++) { |
| |
| if (i >= part->nelts) { |
| if (part->next == NULL) { |
| break; |
| } |
| |
| part = part->next; |
| header = part->elts; |
| i = 0; |
| } |
| |
| h = &header[i]; |
| |
| if (h->hash == 0) { |
| continue; |
| } |
| |
| array = njs_vm_array_push(vm, retval); |
| if (array == NULL) { |
| return NJS_ERROR; |
| } |
| |
| rc = njs_vm_array_alloc(vm, array, 2); |
| if (rc != NJS_OK) { |
| return NJS_ERROR; |
| } |
| |
| elem = njs_vm_array_push(vm, array); |
| if (elem == NULL) { |
| return NJS_ERROR; |
| } |
| |
| rc = njs_vm_value_string_set(vm, elem, h->key.data, h->key.len); |
| if (rc != NJS_OK) { |
| return NJS_ERROR; |
| } |
| |
| elem = njs_vm_array_push(vm, array); |
| if (elem == NULL) { |
| return NJS_ERROR; |
| } |
| |
| rc = njs_vm_value_string_set(vm, elem, h->value.data, h->value.len); |
| if (rc != NJS_OK) { |
| return NJS_ERROR; |
| } |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_ext_header_out(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 name; |
| ngx_http_request_t *r; |
| ngx_http_js_header_t *h; |
| |
| static ngx_http_js_header_t headers_out[] = { |
| #if defined(nginx_version) && (nginx_version < 1023000) |
| { njs_str("Age"), ngx_http_js_header_single }, |
| { njs_str("Content-Type"), ngx_http_js_content_type }, |
| { njs_str("Content-Length"), ngx_http_js_content_length }, |
| { njs_str("Content-Encoding"), ngx_http_js_content_encoding }, |
| { njs_str("Etag"), ngx_http_js_header_single }, |
| { njs_str("Expires"), ngx_http_js_header_single }, |
| { njs_str("Last-Modified"), ngx_http_js_header_single }, |
| { njs_str("Location"), ngx_http_js_header_single }, |
| { njs_str("Set-Cookie"), ngx_http_js_header_array }, |
| { njs_str("Retry-After"), ngx_http_js_header_single }, |
| { njs_str(""), ngx_http_js_header_generic }, |
| #else |
| { njs_str("Age"), NJS_HEADER_SINGLE, ngx_http_js_header_out }, |
| { njs_str("Content-Encoding"), 0, ngx_http_js_content_encoding }, |
| { njs_str("Content-Length"), 0, ngx_http_js_content_length }, |
| { njs_str("Content-Type"), 0, ngx_http_js_content_type }, |
| { njs_str("Etag"), NJS_HEADER_SINGLE, ngx_http_js_header_out }, |
| { njs_str("Expires"), NJS_HEADER_SINGLE, ngx_http_js_header_out }, |
| { njs_str("Last-Modified"), NJS_HEADER_SINGLE, ngx_http_js_header_out }, |
| { njs_str("Location"), NJS_HEADER_SINGLE, ngx_http_js_header_out }, |
| { njs_str("Set-Cookie"), NJS_HEADER_ARRAY, ngx_http_js_header_out }, |
| { njs_str("Retry-After"), NJS_HEADER_SINGLE, ngx_http_js_header_out }, |
| { njs_str(""), 0, ngx_http_js_header_out }, |
| #endif |
| }; |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, value); |
| if (r == NULL) { |
| if (retval != NULL) { |
| njs_value_undefined_set(retval); |
| } |
| |
| return NJS_DECLINED; |
| } |
| |
| rc = njs_vm_prop_name(vm, prop, &name); |
| if (rc != NJS_OK) { |
| if (retval != NULL) { |
| njs_value_undefined_set(retval); |
| } |
| |
| return NJS_DECLINED; |
| } |
| |
| for (h = headers_out; h->name.length > 0; h++) { |
| if (h->name.length == name.length |
| && ngx_strncasecmp(h->name.start, name.start, name.length) == 0) |
| { |
| break; |
| } |
| } |
| |
| #if defined(nginx_version) && (nginx_version < 1023000) |
| return h->handler(vm, r, &r->headers_out.headers, &name, setval, retval); |
| #else |
| return h->handler(vm, r, h->flags, &name, setval, retval); |
| #endif |
| } |
| |
| |
| #if defined(nginx_version) && (nginx_version < 1023000) |
| static njs_int_t |
| ngx_http_js_header_single(njs_vm_t *vm, ngx_http_request_t *r, |
| ngx_list_t *headers, njs_str_t *name, njs_value_t *setval, |
| njs_value_t *retval) |
| { |
| njs_int_t rc; |
| ngx_table_elt_t *h; |
| |
| if (retval != NULL && setval == NULL) { |
| h = ngx_http_js_get_header(&headers->part, name->start, name->length); |
| if (h == NULL) { |
| njs_value_undefined_set(retval); |
| return NJS_DECLINED; |
| } |
| |
| rc = njs_vm_value_string_set(vm, retval, h->value.data, h->value.len); |
| if (rc != NJS_OK) { |
| return NJS_ERROR; |
| } |
| |
| return NJS_OK; |
| } |
| |
| return ngx_http_js_header_generic(vm, r, headers, name, setval, retval); |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_header_out_special(njs_vm_t *vm, ngx_http_request_t *r, |
| njs_str_t *v, njs_value_t *setval, njs_value_t *retval, |
| ngx_table_elt_t **hh) |
| { |
| u_char *p; |
| int64_t length; |
| njs_int_t rc; |
| njs_str_t s; |
| ngx_list_t *headers; |
| ngx_table_elt_t *h; |
| njs_opaque_value_t lvalue; |
| |
| headers = &r->headers_out.headers; |
| |
| if (retval != NULL && setval == NULL) { |
| return ngx_http_js_header_single(vm, r, headers, v, setval, retval); |
| } |
| |
| if (setval != NULL && njs_value_is_array(setval)) { |
| rc = njs_vm_array_length(vm, setval, &length); |
| if (rc != NJS_OK) { |
| return NJS_ERROR; |
| } |
| |
| setval = njs_vm_array_prop(vm, setval, length - 1, &lvalue); |
| } |
| |
| if (ngx_js_string(vm, setval, &s) != NGX_OK) { |
| return NJS_ERROR; |
| } |
| |
| h = ngx_http_js_get_header(&headers->part, v->start, v->length); |
| |
| if (h != NULL && s.length == 0) { |
| h->hash = 0; |
| h = NULL; |
| } |
| |
| if (h == NULL && s.length != 0) { |
| h = ngx_list_push(headers); |
| if (h == NULL) { |
| return NJS_ERROR; |
| } |
| |
| p = ngx_pnalloc(r->pool, v->length); |
| if (p == NULL) { |
| h->hash = 0; |
| return NJS_ERROR; |
| } |
| |
| ngx_memcpy(p, v->start, v->length); |
| |
| h->key.data = p; |
| h->key.len = v->length; |
| } |
| |
| if (h != NULL) { |
| p = ngx_pnalloc(r->pool, s.length); |
| if (p == NULL) { |
| h->hash = 0; |
| return NJS_ERROR; |
| } |
| |
| ngx_memcpy(p, s.start, s.length); |
| |
| h->value.data = p; |
| h->value.len = s.length; |
| h->hash = 1; |
| } |
| |
| if (hh != NULL) { |
| *hh = h; |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_header_array(njs_vm_t *vm, ngx_http_request_t *r, |
| ngx_list_t *headers, njs_str_t *name, njs_value_t *setval, |
| njs_value_t *retval) |
| { |
| size_t len; |
| u_char *data; |
| njs_int_t rc; |
| ngx_uint_t i; |
| njs_value_t *value; |
| ngx_list_part_t *part; |
| ngx_table_elt_t *header, *h; |
| |
| if (retval != NULL && setval == NULL) { |
| rc = njs_vm_array_alloc(vm, retval, 4); |
| if (rc != NJS_OK) { |
| return NJS_ERROR; |
| } |
| |
| len = name->length; |
| data = name->start; |
| |
| part = &headers->part; |
| header = part->elts; |
| |
| for (i = 0; /* void */ ; i++) { |
| |
| if (i >= part->nelts) { |
| if (part->next == NULL) { |
| break; |
| } |
| |
| part = part->next; |
| header = part->elts; |
| i = 0; |
| } |
| |
| h = &header[i]; |
| |
| if (h->hash == 0 |
| || h->key.len != len |
| || ngx_strncasecmp(h->key.data, data, len) != 0) |
| { |
| continue; |
| } |
| |
| value = njs_vm_array_push(vm, retval); |
| if (value == NULL) { |
| return NJS_ERROR; |
| } |
| |
| rc = njs_vm_value_string_set(vm, value, h->value.data, |
| h->value.len); |
| if (rc != NJS_OK) { |
| return NJS_ERROR; |
| } |
| } |
| |
| return NJS_OK; |
| } |
| |
| return ngx_http_js_header_generic(vm, r, headers, name, setval, retval); |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_header_generic(njs_vm_t *vm, ngx_http_request_t *r, |
| ngx_list_t *headers, njs_str_t *name, njs_value_t *setval, |
| njs_value_t *retval) |
| { |
| u_char *data, *p, *start, *end; |
| size_t len; |
| int64_t length; |
| njs_value_t *array; |
| njs_int_t rc; |
| njs_str_t s; |
| ngx_uint_t i; |
| ngx_list_part_t *part; |
| ngx_table_elt_t *header, *h; |
| njs_opaque_value_t lvalue; |
| |
| part = &headers->part; |
| |
| if (retval != NULL && setval == NULL) { |
| header = part->elts; |
| |
| p = NULL; |
| start = NULL; |
| end = NULL; |
| |
| for (i = 0; /* void */ ; i++) { |
| |
| if (i >= part->nelts) { |
| if (part->next == NULL) { |
| break; |
| } |
| |
| part = part->next; |
| header = part->elts; |
| i = 0; |
| } |
| |
| h = &header[i]; |
| |
| if (h->hash == 0 |
| || h->key.len != name->length |
| || ngx_strncasecmp(h->key.data, name->start, name->length) != 0) |
| { |
| continue; |
| } |
| |
| if (p == NULL) { |
| start = h->value.data; |
| end = h->value.data + h->value.len; |
| p = end; |
| continue; |
| } |
| |
| if (p + h->value.len + 1 > end) { |
| len = njs_max(p + h->value.len + 1 - start, 2 * (end - start)); |
| |
| data = ngx_pnalloc(r->pool, len); |
| if (data == NULL) { |
| return NJS_ERROR; |
| } |
| |
| p = ngx_cpymem(data, start, p - start); |
| start = data; |
| end = data + len; |
| } |
| |
| *p++ = ','; |
| p = ngx_cpymem(p, h->value.data, h->value.len); |
| } |
| |
| if (p == NULL) { |
| njs_value_undefined_set(retval); |
| return NJS_DECLINED; |
| } |
| |
| return njs_vm_value_string_set(vm, retval, start, p - start); |
| } |
| |
| header = part->elts; |
| |
| for (i = 0; /* void */ ; i++) { |
| |
| if (i >= part->nelts) { |
| if (part->next == NULL) { |
| break; |
| } |
| |
| part = part->next; |
| header = part->elts; |
| i = 0; |
| } |
| |
| h = &header[i]; |
| |
| if (h->hash == 0 |
| || h->key.len != name->length |
| || ngx_strncasecmp(h->key.data, name->start, name->length) != 0) |
| { |
| continue; |
| } |
| |
| h->hash = 0; |
| } |
| |
| if (retval == NULL) { |
| return NJS_OK; |
| } |
| |
| if (njs_value_is_array(setval)) { |
| array = setval; |
| |
| rc = njs_vm_array_length(vm, array, &length); |
| if (rc != NJS_OK) { |
| return NJS_ERROR; |
| } |
| |
| if (length == 0) { |
| return NJS_OK; |
| } |
| |
| } else { |
| array = NULL; |
| length = 1; |
| } |
| |
| for (i = 0; i < (ngx_uint_t) length; i++) { |
| if (array != NULL) { |
| setval = njs_vm_array_prop(vm, array, i, &lvalue); |
| } |
| |
| if (ngx_js_string(vm, setval, &s) != NGX_OK) { |
| return NJS_ERROR; |
| } |
| |
| if (s.length == 0) { |
| continue; |
| } |
| |
| h = ngx_list_push(headers); |
| if (h == NULL) { |
| return NJS_ERROR; |
| } |
| |
| p = ngx_pnalloc(r->pool, name->length); |
| if (p == NULL) { |
| h->hash = 0; |
| return NJS_ERROR; |
| } |
| |
| ngx_memcpy(p, name->start, name->length); |
| |
| h->key.data = p; |
| h->key.len = name->length; |
| |
| p = ngx_pnalloc(r->pool, s.length); |
| if (p == NULL) { |
| h->hash = 0; |
| return NJS_ERROR; |
| } |
| |
| ngx_memcpy(p, s.start, s.length); |
| |
| h->value.data = p; |
| h->value.len = s.length; |
| h->hash = 1; |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_content_encoding(njs_vm_t *vm, ngx_http_request_t *r, |
| ngx_list_t *headers, njs_str_t *v, njs_value_t *setval, njs_value_t *retval) |
| { |
| njs_int_t rc; |
| ngx_table_elt_t *h; |
| |
| rc = ngx_http_js_header_out_special(vm, r, v, setval, retval, &h); |
| if (rc == NJS_ERROR) { |
| return NJS_ERROR; |
| } |
| |
| if (setval != NULL || retval == NULL) { |
| r->headers_out.content_encoding = h; |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_content_length(njs_vm_t *vm, ngx_http_request_t *r, |
| ngx_list_t *headers, njs_str_t *v, njs_value_t *setval, njs_value_t *retval) |
| { |
| u_char *p, *start; |
| njs_int_t rc; |
| ngx_int_t n; |
| ngx_table_elt_t *h; |
| u_char content_len[NGX_OFF_T_LEN]; |
| |
| if (retval != NULL && setval == NULL) { |
| if (r->headers_out.content_length == NULL |
| && r->headers_out.content_length_n >= 0) |
| { |
| p = ngx_sprintf(content_len, "%O", r->headers_out.content_length_n); |
| |
| start = njs_vm_value_string_alloc(vm, retval, p - content_len); |
| if (start == NULL) { |
| return NJS_ERROR; |
| } |
| |
| ngx_memcpy(start, content_len, p - content_len); |
| |
| return NJS_OK; |
| } |
| } |
| |
| rc = ngx_http_js_header_out_special(vm, r, v, setval, retval, &h); |
| if (rc == NJS_ERROR) { |
| return NJS_ERROR; |
| } |
| |
| if (setval != NULL || retval == NULL) { |
| if (h != NULL) { |
| n = ngx_atoi(h->value.data, h->value.len); |
| if (n == NGX_ERROR) { |
| h->hash = 0; |
| njs_vm_error(vm, "failed converting argument " |
| "to positive integer"); |
| return NJS_ERROR; |
| } |
| |
| r->headers_out.content_length = h; |
| r->headers_out.content_length_n = n; |
| |
| } else { |
| ngx_http_clear_content_length(r); |
| } |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_content_type(njs_vm_t *vm, ngx_http_request_t *r, |
| ngx_list_t *headers, njs_str_t *v, njs_value_t *setval, njs_value_t *retval) |
| { |
| int64_t length; |
| njs_int_t rc; |
| njs_str_t s; |
| ngx_str_t *hdr; |
| njs_opaque_value_t lvalue; |
| |
| if (retval != NULL && setval == NULL) { |
| hdr = &r->headers_out.content_type; |
| |
| if (hdr->len == 0) { |
| njs_value_undefined_set(retval); |
| return NJS_OK; |
| } |
| |
| return njs_vm_value_string_set(vm, retval, hdr->data, hdr->len); |
| } |
| |
| if (setval != NULL && njs_value_is_array(setval)) { |
| rc = njs_vm_array_length(vm, setval, &length); |
| if (rc != NJS_OK) { |
| return NJS_ERROR; |
| } |
| |
| setval = njs_vm_array_prop(vm, setval, length - 1, &lvalue); |
| } |
| |
| if (ngx_js_string(vm, setval, &s) != NGX_OK) { |
| return NJS_ERROR; |
| } |
| |
| r->headers_out.content_type.len = s.length; |
| r->headers_out.content_type_len = r->headers_out.content_type.len; |
| r->headers_out.content_type.data = s.start; |
| r->headers_out.content_type_lowcase = NULL; |
| |
| return NJS_OK; |
| } |
| #endif |
| |
| |
| static njs_int_t |
| ngx_http_js_ext_keys_header_out(njs_vm_t *vm, njs_value_t *value, |
| njs_value_t *keys) |
| { |
| njs_int_t rc; |
| ngx_http_request_t *r; |
| |
| rc = njs_vm_array_alloc(vm, keys, 8); |
| if (rc != NJS_OK) { |
| return NJS_ERROR; |
| } |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, value); |
| if (r == NULL) { |
| return NJS_OK; |
| } |
| |
| if (r->headers_out.content_type.len) { |
| value = njs_vm_array_push(vm, keys); |
| if (value == NULL) { |
| return NJS_ERROR; |
| } |
| |
| rc = njs_vm_value_string_set(vm, value, (u_char *) "Content-Type", |
| njs_length("Content-Type")); |
| if (rc != NJS_OK) { |
| return NJS_ERROR; |
| } |
| } |
| |
| if (r->headers_out.content_length == NULL |
| && r->headers_out.content_length_n >= 0) |
| { |
| value = njs_vm_array_push(vm, keys); |
| if (value == NULL) { |
| return NJS_ERROR; |
| } |
| |
| rc = njs_vm_value_string_set(vm, value, (u_char *) "Content-Length", |
| njs_length("Content-Length")); |
| if (rc != NJS_OK) { |
| return NJS_ERROR; |
| } |
| } |
| |
| return ngx_http_js_ext_keys_header(vm, value, keys, |
| &r->headers_out.headers); |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_ext_status(njs_vm_t *vm, njs_object_prop_t *prop, |
| njs_value_t *value, njs_value_t *setval, njs_value_t *retval) |
| { |
| ngx_int_t n; |
| ngx_http_request_t *r; |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, value); |
| if (r == NULL) { |
| njs_value_undefined_set(retval); |
| return NJS_DECLINED; |
| } |
| |
| if (setval == NULL) { |
| njs_value_number_set(retval, r->headers_out.status); |
| return NJS_OK; |
| } |
| |
| if (ngx_js_integer(vm, setval, &n) != NGX_OK) { |
| return NJS_ERROR; |
| } |
| |
| r->headers_out.status = n; |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_ext_send_header(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, |
| njs_index_t unused) |
| { |
| ngx_http_request_t *r; |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, |
| njs_argument(args, 0)); |
| if (r == NULL) { |
| njs_vm_error(vm, "\"this\" is not an external"); |
| return NJS_ERROR; |
| } |
| |
| if (ngx_http_set_content_type(r) != NGX_OK) { |
| return NJS_ERROR; |
| } |
| |
| if (ngx_http_send_header(r) == NGX_ERROR) { |
| return NJS_ERROR; |
| } |
| |
| njs_value_undefined_set(njs_vm_retval(vm)); |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_ext_send(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, |
| njs_index_t unused) |
| { |
| njs_int_t ret; |
| njs_str_t s; |
| ngx_buf_t *b; |
| uintptr_t next; |
| ngx_uint_t n; |
| ngx_chain_t *out, *cl, **ll; |
| ngx_http_js_ctx_t *ctx; |
| ngx_http_request_t *r; |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, |
| njs_argument(args, 0)); |
| if (r == NULL) { |
| njs_vm_error(vm, "\"this\" is not an external"); |
| return NJS_ERROR; |
| } |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); |
| |
| if (ctx->filter) { |
| njs_vm_error(vm, "cannot send while in body filter"); |
| return NJS_ERROR; |
| } |
| |
| out = NULL; |
| ll = &out; |
| |
| for (n = 1; n < nargs; n++) { |
| next = 0; |
| |
| for ( ;; ) { |
| ret = njs_vm_value_string_copy(vm, &s, njs_argument(args, n), |
| &next); |
| |
| if (ret == NJS_DECLINED) { |
| break; |
| } |
| |
| if (ret == NJS_ERROR) { |
| return NJS_ERROR; |
| } |
| |
| if (s.length == 0) { |
| continue; |
| } |
| |
| /* TODO: njs_value_release(vm, value) in buf completion */ |
| |
| b = ngx_calloc_buf(r->pool); |
| if (b == NULL) { |
| return NJS_ERROR; |
| } |
| |
| b->start = s.start; |
| b->pos = b->start; |
| b->end = s.start + s.length; |
| b->last = b->end; |
| b->memory = 1; |
| |
| cl = ngx_alloc_chain_link(r->pool); |
| if (cl == NULL) { |
| return NJS_ERROR; |
| } |
| |
| cl->buf = b; |
| |
| *ll = cl; |
| ll = &cl->next; |
| } |
| } |
| |
| *ll = NULL; |
| |
| if (ngx_http_output_filter(r, out) == NGX_ERROR) { |
| return NJS_ERROR; |
| } |
| |
| njs_value_undefined_set(njs_vm_retval(vm)); |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_ext_send_buffer(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; |
| ngx_chain_t *cl; |
| njs_value_t *flags, *value; |
| ngx_http_js_ctx_t *ctx; |
| ngx_http_request_t *r; |
| njs_opaque_value_t lvalue; |
| |
| static const njs_str_t last_key = njs_str("last"); |
| static const njs_str_t flush_key = njs_str("flush"); |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, |
| njs_argument(args, 0)); |
| if (r == NULL) { |
| njs_vm_error(vm, "\"this\" is not an external"); |
| return NJS_ERROR; |
| } |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); |
| |
| if (!ctx->filter) { |
| njs_vm_error(vm, "cannot send buffer while not filtering"); |
| 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(r->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_http_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_http_js_ext_set_return_value(njs_vm_t *vm, njs_value_t *args, |
| njs_uint_t nargs, njs_index_t unused) |
| { |
| ngx_http_js_ctx_t *ctx; |
| ngx_http_request_t *r; |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, |
| njs_argument(args, 0)); |
| if (r == NULL) { |
| njs_vm_error(vm, "\"this\" is not an external"); |
| return NJS_ERROR; |
| } |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_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_http_js_ext_done(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, |
| njs_index_t unused) |
| { |
| ngx_http_js_ctx_t *ctx; |
| ngx_http_request_t *r; |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, |
| njs_argument(args, 0)); |
| if (r == NULL) { |
| njs_vm_error(vm, "\"this\" is not an external"); |
| return NJS_ERROR; |
| } |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); |
| |
| if (!ctx->filter) { |
| njs_vm_error(vm, "cannot set done while not filtering"); |
| return NJS_ERROR; |
| } |
| |
| ctx->done = 1; |
| |
| njs_value_undefined_set(njs_vm_retval(vm)); |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_ext_finish(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, |
| njs_index_t unused) |
| { |
| ngx_http_js_ctx_t *ctx; |
| ngx_http_request_t *r; |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, |
| njs_argument(args, 0)); |
| if (r == NULL) { |
| njs_vm_error(vm, "\"this\" is not an external"); |
| return NJS_ERROR; |
| } |
| |
| if (ngx_http_send_special(r, NGX_HTTP_LAST) == NGX_ERROR) { |
| return NJS_ERROR; |
| } |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); |
| |
| ctx->status = NGX_OK; |
| |
| njs_value_undefined_set(njs_vm_retval(vm)); |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_ext_return(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, |
| njs_index_t unused) |
| { |
| njs_str_t text; |
| ngx_int_t status; |
| ngx_http_js_ctx_t *ctx; |
| ngx_http_request_t *r; |
| ngx_http_complex_value_t cv; |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, |
| njs_argument(args, 0)); |
| if (r == NULL) { |
| njs_vm_error(vm, "\"this\" is not an external"); |
| return NJS_ERROR; |
| } |
| |
| if (ngx_js_integer(vm, njs_arg(args, nargs, 1), &status) != NGX_OK) { |
| return NJS_ERROR; |
| } |
| |
| if (status < 0 || status > 999) { |
| njs_vm_error(vm, "code is out of range"); |
| return NJS_ERROR; |
| } |
| |
| if (ngx_js_string(vm, njs_arg(args, nargs, 2), &text) != NGX_OK) { |
| njs_vm_error(vm, "failed to convert text"); |
| return NJS_ERROR; |
| } |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); |
| |
| if (status < NGX_HTTP_BAD_REQUEST || text.length) { |
| ngx_memzero(&cv, sizeof(ngx_http_complex_value_t)); |
| |
| cv.value.data = text.start; |
| cv.value.len = text.length; |
| |
| ctx->status = ngx_http_send_response(r, status, NULL, &cv); |
| |
| if (ctx->status == NGX_ERROR) { |
| njs_vm_error(vm, "failed to send response"); |
| return NJS_ERROR; |
| } |
| |
| } else { |
| ctx->status = status; |
| } |
| |
| njs_value_undefined_set(njs_vm_retval(vm)); |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_ext_internal_redirect(njs_vm_t *vm, njs_value_t *args, |
| njs_uint_t nargs, njs_index_t unused) |
| { |
| njs_str_t uri; |
| ngx_http_js_ctx_t *ctx; |
| ngx_http_request_t *r; |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, |
| njs_argument(args, 0)); |
| if (r == NULL) { |
| njs_vm_error(vm, "\"this\" is not an external"); |
| return NJS_ERROR; |
| } |
| |
| if (r->parent != NULL) { |
| njs_vm_error(vm, "internalRedirect cannot be called from a subrequest"); |
| return NJS_ERROR; |
| } |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); |
| |
| if (ngx_js_string(vm, njs_arg(args, nargs, 1), &uri) != NGX_OK) { |
| njs_vm_error(vm, "failed to convert uri arg"); |
| return NJS_ERROR; |
| } |
| |
| if (uri.length == 0) { |
| njs_vm_error(vm, "uri is empty"); |
| return NJS_ERROR; |
| } |
| |
| ctx->redirect_uri.data = uri.start; |
| ctx->redirect_uri.len = uri.length; |
| |
| ctx->status = NGX_DONE; |
| |
| njs_value_undefined_set(njs_vm_retval(vm)); |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_ext_get_http_version(njs_vm_t *vm, njs_object_prop_t *prop, |
| njs_value_t *value, njs_value_t *setval, njs_value_t *retval) |
| { |
| ngx_str_t v; |
| ngx_http_request_t *r; |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, value); |
| if (r == NULL) { |
| njs_value_undefined_set(retval); |
| return NJS_DECLINED; |
| } |
| |
| switch (r->http_version) { |
| |
| case NGX_HTTP_VERSION_9: |
| ngx_str_set(&v, "0.9"); |
| break; |
| |
| case NGX_HTTP_VERSION_10: |
| ngx_str_set(&v, "1.0"); |
| break; |
| |
| case NGX_HTTP_VERSION_11: |
| ngx_str_set(&v, "1.1"); |
| break; |
| |
| case NGX_HTTP_VERSION_20: |
| ngx_str_set(&v, "2.0"); |
| break; |
| |
| #if (NGX_HTTP_VERSION_30) |
| case NGX_HTTP_VERSION_30: |
| ngx_str_set(&v, "3.0"); |
| break; |
| #endif |
| |
| default: |
| ngx_str_set(&v, ""); |
| break; |
| } |
| |
| return njs_vm_value_string_set(vm, retval, v.data, v.len); |
| } |
| |
| |
| static njs_int_t |
| ngx_http_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_http_request_t *r; |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, value); |
| if (r == NULL) { |
| njs_value_undefined_set(retval); |
| return NJS_DECLINED; |
| } |
| |
| c = r->connection; |
| |
| return njs_vm_value_string_set(vm, retval, c->addr_text.data, |
| c->addr_text.len); |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_ext_get_request_body(njs_vm_t *vm, njs_object_prop_t *prop, |
| njs_value_t *value, njs_value_t *setval, njs_value_t *retval) |
| { |
| u_char *p, *body; |
| size_t len; |
| ngx_buf_t *buf; |
| njs_int_t ret; |
| njs_value_t *request_body; |
| ngx_chain_t *cl; |
| ngx_http_js_ctx_t *ctx; |
| ngx_http_request_t *r; |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, value); |
| if (r == NULL) { |
| njs_value_undefined_set(retval); |
| return NJS_DECLINED; |
| } |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); |
| request_body = (njs_value_t *) &ctx->request_body; |
| |
| if (!njs_value_is_null(request_body)) { |
| if ((njs_vm_prop_magic32(prop) == NGX_JS_BUFFER) |
| == (uint32_t) njs_value_is_buffer(request_body)) |
| { |
| njs_value_assign(retval, request_body); |
| return NJS_OK; |
| } |
| } |
| |
| if (r->request_body == NULL || r->request_body->bufs == NULL) { |
| njs_value_undefined_set(retval); |
| return NJS_DECLINED; |
| } |
| |
| if (r->request_body->temp_file) { |
| njs_vm_error(vm, "request body is in a file"); |
| return NJS_ERROR; |
| } |
| |
| cl = r->request_body->bufs; |
| buf = cl->buf; |
| |
| if (cl->next == NULL) { |
| len = buf->last - buf->pos; |
| body = buf->pos; |
| |
| goto done; |
| } |
| |
| len = buf->last - buf->pos; |
| cl = cl->next; |
| |
| for ( /* void */ ; cl; cl = cl->next) { |
| buf = cl->buf; |
| len += buf->last - buf->pos; |
| } |
| |
| p = ngx_pnalloc(r->pool, len); |
| if (p == NULL) { |
| njs_vm_memory_error(vm); |
| return NJS_ERROR; |
| } |
| |
| body = p; |
| cl = r->request_body->bufs; |
| |
| for ( /* void */ ; cl; cl = cl->next) { |
| buf = cl->buf; |
| p = ngx_cpymem(p, buf->pos, buf->last - buf->pos); |
| } |
| |
| done: |
| |
| ret = ngx_js_prop(vm, njs_vm_prop_magic32(prop), request_body, body, len); |
| if (ret != NJS_OK) { |
| return NJS_ERROR; |
| } |
| |
| njs_value_assign(retval, request_body); |
| |
| return NJS_OK; |
| } |
| |
| |
| #if defined(nginx_version) && (nginx_version < 1023000) |
| static njs_int_t |
| ngx_http_js_ext_header_in(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 name; |
| ngx_http_request_t *r; |
| ngx_http_js_header_t *h; |
| |
| static ngx_http_js_header_t headers_in[] = { |
| { njs_str("Content-Type"), ngx_http_js_header_single }, |
| { njs_str("Cookie"), ngx_http_js_header_cookie }, |
| { njs_str("ETag"), ngx_http_js_header_single }, |
| { njs_str("From"), ngx_http_js_header_single }, |
| { njs_str("Max-Forwards"), ngx_http_js_header_single }, |
| { njs_str("Referer"), ngx_http_js_header_single }, |
| { njs_str("Proxy-Authorization"), ngx_http_js_header_single }, |
| { njs_str("User-Agent"), ngx_http_js_header_single }, |
| #if (NGX_HTTP_X_FORWARDED_FOR) |
| { njs_str("X-Forwarded-For"), ngx_http_js_header_x_forwarded_for }, |
| #endif |
| { njs_str(""), ngx_http_js_header_generic }, |
| }; |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, value); |
| if (r == NULL) { |
| if (retval != NULL) { |
| njs_value_undefined_set(retval); |
| } |
| |
| return NJS_DECLINED; |
| } |
| |
| rc = njs_vm_prop_name(vm, prop, &name); |
| if (rc != NJS_OK) { |
| if (retval != NULL) { |
| njs_value_undefined_set(retval); |
| } |
| |
| return NJS_DECLINED; |
| } |
| |
| for (h = headers_in; h->name.length > 0; h++) { |
| if (h->name.length == name.length |
| && ngx_strncasecmp(h->name.start, name.start, name.length) == 0) |
| { |
| break; |
| } |
| } |
| |
| return h->handler(vm, r, &r->headers_in.headers, &name, setval, retval); |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_header_cookie(njs_vm_t *vm, ngx_http_request_t *r, |
| ngx_list_t *headers, njs_str_t *name, njs_value_t *setval, |
| njs_value_t *retval) |
| { |
| return ngx_http_js_header_in_array(vm, r, &r->headers_in.cookies, |
| ';', retval); |
| } |
| |
| |
| #if (NGX_HTTP_X_FORWARDED_FOR) |
| static njs_int_t |
| ngx_http_js_header_x_forwarded_for(njs_vm_t *vm, ngx_http_request_t *r, |
| ngx_list_t *headers, njs_str_t *name, njs_value_t *setval, |
| njs_value_t *retval) |
| { |
| return ngx_http_js_header_in_array(vm, r, &r->headers_in.x_forwarded_for, |
| ',', retval); |
| } |
| #endif |
| |
| |
| static njs_int_t |
| ngx_http_js_header_in_array(njs_vm_t *vm, ngx_http_request_t *r, |
| ngx_array_t *array, u_char sep, njs_value_t *retval) |
| { |
| u_char *p, *end; |
| size_t len; |
| ngx_uint_t i, n; |
| ngx_table_elt_t **hh; |
| |
| n = array->nelts; |
| hh = array->elts; |
| |
| len = 0; |
| |
| for (i = 0; i < n; i++) { |
| len += hh[i]->value.len + 1; |
| } |
| |
| if (len == 0) { |
| njs_value_undefined_set(retval); |
| return NJS_DECLINED; |
| } |
| |
| len -= 1; |
| |
| if (n == 1) { |
| return njs_vm_value_string_set(vm, retval, (*hh)->value.data, |
| (*hh)->value.len); |
| } |
| |
| p = njs_vm_value_string_alloc(vm, retval, len); |
| if (p == NULL) { |
| return NJS_ERROR; |
| } |
| |
| end = p + len; |
| |
| |
| for (i = 0; /* void */ ; i++) { |
| |
| p = ngx_copy(p, hh[i]->value.data, hh[i]->value.len); |
| |
| if (p == end) { |
| break; |
| } |
| |
| *p++ = sep; |
| } |
| |
| return NJS_OK; |
| } |
| #else |
| static njs_int_t |
| ngx_http_js_ext_header_in(njs_vm_t *vm, njs_object_prop_t *prop, |
| njs_value_t *value, njs_value_t *unused, njs_value_t *retval) |
| { |
| unsigned flags; |
| njs_int_t rc; |
| njs_str_t name, *h; |
| ngx_http_request_t *r; |
| |
| static njs_str_t single_headers_in[] = { |
| njs_str("Content-Type"), |
| njs_str("ETag"), |
| njs_str("From"), |
| njs_str("Max-Forwards"), |
| njs_str("Referer"), |
| njs_str("Proxy-Authorization"), |
| njs_str("User-Agent"), |
| njs_str(""), |
| }; |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, value); |
| if (r == NULL) { |
| if (retval != NULL) { |
| njs_value_undefined_set(retval); |
| } |
| |
| return NJS_DECLINED; |
| } |
| |
| rc = njs_vm_prop_name(vm, prop, &name); |
| if (rc != NJS_OK) { |
| if (retval != NULL) { |
| njs_value_undefined_set(retval); |
| } |
| |
| return NJS_DECLINED; |
| } |
| |
| flags = 0; |
| |
| for (h = single_headers_in; h->length > 0; h++) { |
| if (h->length == name.length |
| && ngx_strncasecmp(h->start, name.start, name.length) == 0) |
| { |
| flags |= NJS_HEADER_SINGLE; |
| break; |
| } |
| } |
| |
| return ngx_http_js_header_in(vm, r, flags, &name, retval); |
| } |
| #endif |
| |
| |
| static njs_int_t |
| ngx_http_js_ext_keys_header_in(njs_vm_t *vm, njs_value_t *value, |
| njs_value_t *keys) |
| { |
| njs_int_t rc; |
| ngx_http_request_t *r; |
| |
| rc = njs_vm_array_alloc(vm, keys, 8); |
| if (rc != NJS_OK) { |
| return NJS_ERROR; |
| } |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, value); |
| if (r == NULL) { |
| return NJS_OK; |
| } |
| |
| return ngx_http_js_ext_keys_header(vm, value, keys, &r->headers_in.headers); |
| } |
| |
| static njs_int_t |
| ngx_http_js_ext_get_arg(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 *v, key; |
| ngx_str_t arg; |
| ngx_http_request_t *r; |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, value); |
| if (r == NULL) { |
| njs_value_undefined_set(retval); |
| return NJS_DECLINED; |
| } |
| |
| rc = njs_vm_prop_name(vm, prop, &key); |
| if (rc != NJS_OK) { |
| njs_value_undefined_set(retval); |
| return NJS_DECLINED; |
| } |
| |
| v = &key; |
| |
| if (ngx_http_arg(r, v->start, v->length, &arg) == NGX_OK) { |
| return njs_vm_value_string_set(vm, retval, arg.data, arg.len); |
| } |
| |
| njs_value_undefined_set(retval); |
| |
| return NJS_DECLINED; |
| } |
| |
| |
| static njs_int_t |
| ngx_http_js_ext_keys_arg(njs_vm_t *vm, njs_value_t *value, njs_value_t *keys) |
| { |
| u_char *v, *p, *start, *end; |
| njs_int_t rc; |
| ngx_http_request_t *r; |
| |
| rc = njs_vm_array_alloc(vm, keys, 8); |
| if (rc != NJS_OK) { |
| return NJS_ERROR; |
| } |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, value); |
| if (r == NULL) { |
| return NJS_OK; |
| } |
| |
| start = r->args.data; |
| end = start + r->args.len; |
| |
| while (start < end) { |
| p = ngx_strlchr(start, end, '&'); |
| if (p == NULL) { |
| p = end; |
| } |
| |
| v = ngx_strlchr(start, p, '='); |
| if (v == NULL) { |
| v = p; |
| } |
| |
| if (v != start) { |
| value = njs_vm_array_push(vm, keys); |
| if (value == NULL) { |
| return NJS_ERROR; |
| } |
| |
| rc = njs_vm_value_string_set(vm, value, start, v - start); |
| if (rc != NJS_OK) { |
| return NJS_ERROR; |
| } |
| } |
| |
| start = p + 1; |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| ngx_http_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, s; |
| ngx_str_t name; |
| ngx_uint_t key; |
| ngx_http_request_t *r; |
| ngx_http_variable_t *v; |
| ngx_http_core_main_conf_t *cmcf; |
| ngx_http_variable_value_t *vv; |
| |
| r = njs_vm_external(vm, ngx_http_js_request_proto_id, value); |
| if (r == 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_http_get_variable(r, &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_http_get_module_main_conf(r, ngx_http_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, &s) != NGX_OK) { |
| return NJS_ERROR; |
| } |
| |
| if (v->set_handler != NULL) { |
| vv = ngx_pcalloc(r->pool, sizeof(ngx_http_variable_value_t)); |
| if (vv == NULL) { |
| njs_vm_error(vm, "internal error"); |
| return NJS_ERROR; |
| } |
| |
| vv->valid = 1; |
| vv->not_found = 0; |
| vv->data = s.start; |
| vv->len = s.length; |
| |
| v->set_handler(r, vv, v->data); |
| |
| return NJS_OK; |
| } |
| |
| if (!(v->flags & NGX_HTTP_VAR_INDEXED)) { |
| njs_vm_error(vm, "variable is not writable"); |
| return NJS_ERROR; |
| } |
| |
| vv = &r->variables[v->index]; |
| |
| vv->valid = 1; |
| vv->not_found = 0; |
| |
| vv->data = ngx_pnalloc(r->pool, s.length); |
| if (vv->data == NULL) { |
|
|