/* * Copyright (C) Roman Arutyunyan * Copyright (C) NGINX, Inc. */ #include #include #include #include typedef struct { njs_vm_t *vm; ngx_array_t *paths; const njs_extern_t *req_proto; } ngx_http_js_main_conf_t; typedef struct { ngx_str_t content; } ngx_http_js_loc_conf_t; typedef struct { njs_vm_t *vm; ngx_log_t *log; ngx_uint_t done; ngx_int_t status; njs_opaque_value_t request; njs_opaque_value_t request_body; ngx_str_t redirect_uri; } ngx_http_js_ctx_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; 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_variable(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_get_string(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data); static njs_int_t ngx_http_js_ext_keys_header(njs_vm_t *vm, void *obj, njs_value_t *keys, uintptr_t data); static ngx_table_elt_t *ngx_http_js_get_header(ngx_list_part_t *part, u_char *data, size_t len); static njs_int_t ngx_http_js_ext_get_header_out(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data); static njs_int_t ngx_http_js_ext_set_header_out(njs_vm_t *vm, void *obj, uintptr_t data, njs_str_t *value); static njs_int_t ngx_http_js_ext_delete_header_out(njs_vm_t *vm, void *obj, uintptr_t data, njs_bool_t delete); static njs_int_t ngx_http_js_ext_keys_header_out(njs_vm_t *vm, void *obj, njs_value_t *keys); /*FIXME*/ static njs_int_t ngx_http_js_ext_get_status(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data); static njs_int_t ngx_http_js_ext_set_status(njs_vm_t *vm, void *obj, uintptr_t data, njs_str_t *value); 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_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_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused); static njs_int_t ngx_http_js_ext_warn(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused); static njs_int_t ngx_http_js_ext_error(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused); static njs_int_t ngx_http_js_ext_log_core(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, ngx_uint_t level); static njs_int_t ngx_http_js_ext_get_http_version(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data); static njs_int_t ngx_http_js_ext_get_remote_address(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data); static njs_int_t ngx_http_js_ext_get_request_body(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data); static njs_int_t ngx_http_js_ext_get_header_in(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data); static njs_int_t ngx_http_js_ext_keys_header_in(njs_vm_t *vm, void *obj, njs_value_t *keys); /*FIXME*/ static njs_int_t ngx_http_js_ext_get_arg(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data); static njs_int_t ngx_http_js_ext_keys_arg(njs_vm_t *vm, void *obj, njs_value_t *keys); static njs_int_t ngx_http_js_ext_get_variable(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data); static njs_int_t ngx_http_js_ext_set_variable(njs_vm_t *vm, void *obj, uintptr_t data, njs_str_t *value); 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_value_t *value, void *obj, uintptr_t data); static njs_int_t ngx_http_js_ext_get_reply_body(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data); 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 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 njs_int_t ngx_http_js_string(njs_vm_t *vm, njs_value_t *value, njs_str_t *str); static char *ngx_http_js_include(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_content(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static void *ngx_http_js_create_main_conf(ngx_conf_t *cf); 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); static ngx_command_t ngx_http_js_commands[] = { { ngx_string("js_include"), NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, ngx_http_js_include, 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_content"), NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1, ngx_http_js_content, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_js_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ ngx_http_js_create_main_conf, /* create main configuration */ NULL, /* 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 njs_external_t ngx_http_js_ext_request[] = { { njs_str("uri"), NJS_EXTERN_PROPERTY, NULL, 0, ngx_http_js_ext_get_string, NULL, NULL, NULL, NULL, offsetof(ngx_http_request_t, uri) }, { njs_str("method"), NJS_EXTERN_PROPERTY, NULL, 0, ngx_http_js_ext_get_string, NULL, NULL, NULL, NULL, offsetof(ngx_http_request_t, method_name) }, { njs_str("httpVersion"), NJS_EXTERN_PROPERTY, NULL, 0, ngx_http_js_ext_get_http_version, NULL, NULL, NULL, NULL, 0 }, { njs_str("remoteAddress"), NJS_EXTERN_PROPERTY, NULL, 0, ngx_http_js_ext_get_remote_address, NULL, NULL, NULL, NULL, 0 }, { njs_str("parent"), NJS_EXTERN_PROPERTY, NULL, 0, ngx_http_js_ext_get_parent, NULL, NULL, NULL, NULL, 0 }, { njs_str("requestBody"), NJS_EXTERN_PROPERTY, NULL, 0, ngx_http_js_ext_get_request_body, NULL, NULL, NULL, NULL, 0 }, { njs_str("responseBody"), NJS_EXTERN_PROPERTY, NULL, 0, ngx_http_js_ext_get_reply_body, NULL, NULL, NULL, NULL, 0 }, { njs_str("headersIn"), NJS_EXTERN_OBJECT, NULL, 0, ngx_http_js_ext_get_header_in, NULL, NULL, ngx_http_js_ext_keys_header_in, NULL, 0 }, { njs_str("args"), NJS_EXTERN_OBJECT, NULL, 0, ngx_http_js_ext_get_arg, NULL, NULL, ngx_http_js_ext_keys_arg, NULL, 0 }, { njs_str("variables"), NJS_EXTERN_OBJECT, NULL, 0, ngx_http_js_ext_get_variable, ngx_http_js_ext_set_variable, NULL, NULL, NULL, 0 }, { njs_str("status"), NJS_EXTERN_PROPERTY, NULL, 0, ngx_http_js_ext_get_status, ngx_http_js_ext_set_status, NULL, NULL, NULL, 0 }, { njs_str("headersOut"), NJS_EXTERN_OBJECT, NULL, 0, ngx_http_js_ext_get_header_out, ngx_http_js_ext_set_header_out, ngx_http_js_ext_delete_header_out, ngx_http_js_ext_keys_header_out, NULL, 0 }, { njs_str("subrequest"), NJS_EXTERN_METHOD, NULL, 0, NULL, NULL, NULL, NULL, ngx_http_js_ext_subrequest, 0 }, { njs_str("log"), NJS_EXTERN_METHOD, NULL, 0, NULL, NULL, NULL, NULL, ngx_http_js_ext_log, 0 }, { njs_str("warn"), NJS_EXTERN_METHOD, NULL, 0, NULL, NULL, NULL, NULL, ngx_http_js_ext_warn, 0 }, { njs_str("error"), NJS_EXTERN_METHOD, NULL, 0, NULL, NULL, NULL, NULL, ngx_http_js_ext_error, 0 }, { njs_str("sendHeader"), NJS_EXTERN_METHOD, NULL, 0, NULL, NULL, NULL, NULL, ngx_http_js_ext_send_header, 0 }, { njs_str("send"), NJS_EXTERN_METHOD, NULL, 0, NULL, NULL, NULL, NULL, ngx_http_js_ext_send, 0 }, { njs_str("finish"), NJS_EXTERN_METHOD, NULL, 0, NULL, NULL, NULL, NULL, ngx_http_js_ext_finish, 0 }, { njs_str("return"), NJS_EXTERN_METHOD, NULL, 0, NULL, NULL, NULL, NULL, ngx_http_js_ext_return, 0 }, { njs_str("internalRedirect"), NJS_EXTERN_METHOD, NULL, 0, NULL, NULL, NULL, NULL, ngx_http_js_ext_internal_redirect, 0 }, }; static njs_external_t ngx_http_js_externals[] = { { njs_str("request"), NJS_EXTERN_OBJECT, ngx_http_js_ext_request, njs_nitems(ngx_http_js_ext_request), NULL, NULL, NULL, NULL, NULL, 0 }, }; static njs_vm_ops_t ngx_http_js_ops = { ngx_http_js_set_timer, ngx_http_js_clear_timer }; 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; njs_str_t name, exception; njs_function_t *func; 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); name.start = jlcf->content.data; name.length = jlcf->content.len; func = njs_vm_function(ctx->vm, &name); if (func == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "js function \"%V\" not found", &jlcf->content); ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } /* * 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; if (njs_vm_call(ctx->vm, func, njs_value_arg(&ctx->request), 1) != NJS_OK) { njs_vm_retval_string(ctx->vm, &exception); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "js exception: %*s", exception.length, exception.start); ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (njs_vm_pending(ctx->vm)) { 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_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_http_split_args(r, &ctx->redirect_uri, &args); ngx_http_internal_redirect(r, &ctx->redirect_uri, &args); } } ngx_http_finalize_request(r, ctx->status); } static ngx_int_t ngx_http_js_variable(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; njs_str_t name, value, exception; njs_function_t *func; 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); name.start = fname->data; name.length = fname->len; func = njs_vm_function(ctx->vm, &name); if (func == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "js function \"%V\" not found", fname); v->not_found = 1; return NGX_OK; } pending = njs_vm_pending(ctx->vm); if (njs_vm_call(ctx->vm, func, njs_value_arg(&ctx->request), 1) != NJS_OK) { njs_vm_retval_string(ctx->vm, &exception); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "js exception: %*s", exception.length, exception.start); v->not_found = 1; return NGX_OK; } if (njs_vm_retval_string(ctx->vm, &value) != NJS_OK) { return NGX_ERROR; } if (!pending && njs_vm_pending(ctx->vm)) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "async operation inside \"%V\" variable handler", fname); return NGX_ERROR; } v->len = value.length; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = value.start; return NGX_OK; } static ngx_int_t ngx_http_js_init_vm(ngx_http_request_t *r) { njs_int_t rc; njs_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; } 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) { njs_vm_retval_string(ctx->vm, &exception); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "js exception: %*s", exception.length, exception.start); return NGX_ERROR; } rc = njs_vm_external_create(ctx->vm, njs_value_arg(&ctx->request), jmcf->req_proto, r); 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_get_string(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data) { char *p = obj; ngx_str_t *field; field = (ngx_str_t *) (p + data); return njs_vm_value_string_set(vm, value, field->data, field->len); } static njs_int_t ngx_http_js_ext_keys_header(njs_vm_t *vm, void *obj, njs_value_t *keys, uintptr_t data) { char *p = obj; njs_int_t rc, cookie, x_for; ngx_uint_t item; ngx_list_t *headers; njs_value_t *value; ngx_list_part_t *part; ngx_table_elt_t *header, *h; rc = njs_vm_array_alloc(vm, keys, 8); if (rc != NJS_OK) { return NJS_ERROR; } headers = (ngx_list_t *) (p + data); part = &headers->part; item = 0; cookie = 0; x_for = 0; while (part) { if (item >= part->nelts) { part = part->next; item = 0; continue; } header = part->elts; h = &header[item++]; if (h->hash == 0) { continue; } if (h->key.len == njs_length("Cookie") && ngx_strncasecmp(h->key.data, (u_char *) "Cookie", h->key.len) == 0) { if (cookie) { continue; } cookie = 1; } if (h->key.len == njs_length("X-Forwarded-For") && ngx_strncasecmp(h->key.data, (u_char *) "X-Forwarded-For", h->key.len) == 0) { if (x_for) { continue; } x_for = 1; } 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; } } return NJS_OK; } 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; } static njs_int_t ngx_http_js_ext_get_header_out(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data) { u_char *p, *start; njs_str_t *v; ngx_str_t *hdr; ngx_table_elt_t *h; ngx_http_request_t *r; u_char content_len[NGX_OFF_T_LEN]; r = (ngx_http_request_t *) obj; v = (njs_str_t *) data; if (v->length == njs_length("Content-Type") && ngx_strncasecmp(v->start, (u_char *) "Content-Type", v->length) == 0) { hdr = &r->headers_out.content_type; return njs_vm_value_string_set(vm, value, hdr->data, hdr->len); } if (v->length == njs_length("Content-Length") && ngx_strncasecmp(v->start, (u_char *) "Content-Length", v->length) == 0) { 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, value, p - content_len); if (start == NULL) { return NJS_ERROR; } ngx_memcpy(start, content_len, p - content_len); return NJS_OK; } } h = ngx_http_js_get_header(&r->headers_out.headers.part, v->start, v->length); if (h == NULL) { njs_value_undefined_set(value); return NJS_OK; } return njs_vm_value_string_set(vm, value, h->value.data, h->value.len); } static njs_int_t ngx_http_js_ext_set_header_out(njs_vm_t *vm, void *obj, uintptr_t data, njs_str_t *value) { u_char *p; ngx_int_t n; njs_str_t *v; ngx_table_elt_t *h; ngx_http_request_t *r; r = (ngx_http_request_t *) obj; v = (njs_str_t *) data; if (v->length == njs_length("Content-Type") && ngx_strncasecmp(v->start, (u_char *) "Content-Type", v->length) == 0) { r->headers_out.content_type.len = value->length; r->headers_out.content_type_len = r->headers_out.content_type.len; r->headers_out.content_type.data = value->start; r->headers_out.content_type_lowcase = NULL; return NJS_OK; } h = ngx_http_js_get_header(&r->headers_out.headers.part, v->start, v->length); if (h != NULL && value->length == 0) { h->hash = 0; h = NULL; } if (h == NULL && value->length != 0) { h = ngx_list_push(&r->headers_out.headers); if (h == NULL) { return NJS_ERROR; } p = ngx_pnalloc(r->pool, v->length); if (p == NULL) { 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, value->length); if (p == NULL) { return NJS_ERROR; } ngx_memcpy(p, value->start, value->length); h->value.data = p; h->value.len = value->length; h->hash = 1; } if (v->length == njs_length("Content-Encoding") && ngx_strncasecmp(v->start, (u_char *) "Content-Encoding", v->length) == 0) { r->headers_out.content_encoding = h; } if (v->length == njs_length("Content-Length") && ngx_strncasecmp(v->start, (u_char *) "Content-Length", v->length) == 0) { if (h != NULL) { n = ngx_atoi(value->start, value->length); if (n == NGX_ERROR) { h->hash = 0; njs_vm_error(vm, "failed converting argument to 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_ext_delete_header_out(njs_vm_t *vm, void *obj, uintptr_t data, njs_bool_t unused) { njs_str_t value; value = njs_str_value(""); return ngx_http_js_ext_set_header_out(vm, obj, data, &value); } static njs_int_t ngx_http_js_ext_keys_header_out(njs_vm_t *vm, void *obj, njs_value_t *keys) { return ngx_http_js_ext_keys_header(vm, obj, keys, offsetof(ngx_http_request_t, headers_out.headers)); } static njs_int_t ngx_http_js_ext_get_status(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data) { ngx_http_request_t *r; r = (ngx_http_request_t *) obj; njs_value_number_set(value, r->headers_out.status); return NJS_OK; } static njs_int_t ngx_http_js_ext_set_status(njs_vm_t *vm, void *obj, uintptr_t data, njs_str_t *value) { ngx_int_t n; ngx_http_request_t *r; n = ngx_atoi(value->start, value->length); if (n == NGX_ERROR) { return NJS_ERROR; } r = (ngx_http_request_t *) obj; 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, njs_arg(args, nargs, 0)); if (njs_slow_path(r == NULL)) { 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; } 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_request_t *r; r = njs_vm_external(vm, njs_arg(args, nargs, 0)); if (njs_slow_path(r == NULL)) { 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; } 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, njs_arg(args, nargs, 0)); if (njs_slow_path(r == NULL)) { 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; 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; njs_value_t *value; ngx_http_js_ctx_t *ctx; ngx_http_request_t *r; ngx_http_complex_value_t cv; r = njs_vm_external(vm, njs_arg(args, nargs, 0)); if (njs_slow_path(r == NULL)) { return NJS_ERROR; } value = njs_arg(args, nargs, 1); if (!njs_value_is_valid_number(value)) { njs_vm_error(vm, "code is not a number"); return NJS_ERROR; } status = njs_value_number(value); if (status < 0 || status > 999) { njs_vm_error(vm, "code is out of range"); return NJS_ERROR; } if (ngx_http_js_string(vm, njs_arg(args, nargs, 2), &text) != NJS_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; } 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, njs_arg(args, nargs, 0)); if (njs_slow_path(r == NULL)) { return NJS_ERROR; } ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); if (ngx_http_js_string(vm, njs_arg(args, nargs, 1), &uri) != NJS_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; return NJS_OK; } static njs_int_t ngx_http_js_ext_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) { return ngx_http_js_ext_log_core(vm, args, nargs, NGX_LOG_INFO); } static njs_int_t ngx_http_js_ext_warn(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) { return ngx_http_js_ext_log_core(vm, args, nargs, NGX_LOG_WARN); } static njs_int_t ngx_http_js_ext_error(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) { return ngx_http_js_ext_log_core(vm, args, nargs, NGX_LOG_ERR); } static njs_int_t ngx_http_js_ext_log_core(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, ngx_uint_t level) { njs_str_t msg; ngx_connection_t *c; ngx_log_handler_pt handler; ngx_http_request_t *r; r = njs_vm_external(vm, njs_arg(args, nargs, 0)); if (njs_slow_path(r == NULL)) { return NJS_ERROR; } c = r->connection; if (njs_vm_value_to_string(vm, &msg, njs_arg(args, nargs, 1)) == NJS_ERROR) { return NJS_ERROR; } handler = c->log->handler; c->log->handler = NULL; ngx_log_error(level, c->log, 0, "js: %*s", msg.length, msg.start); c->log->handler = handler; return NJS_OK; } static njs_int_t ngx_http_js_ext_get_http_version(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data) { ngx_str_t v; ngx_http_request_t *r; r = (ngx_http_request_t *) obj; 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; default: /* NGX_HTTP_VERSION_11 */ ngx_str_set(&v, "1.1"); break; } return njs_vm_value_string_set(vm, value, v.data, v.len); } static njs_int_t ngx_http_js_ext_get_remote_address(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data) { ngx_connection_t *c; ngx_http_request_t *r; r = (ngx_http_request_t *) obj; c = r->connection; return njs_vm_value_string_set(vm, value, c->addr_text.data, c->addr_text.len); } static njs_int_t ngx_http_js_ext_get_request_body(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data) { 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 = (ngx_http_request_t *) obj; 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)) { njs_value_assign(value, request_body); return NJS_OK; } if (r->request_body == NULL || r->request_body->bufs == NULL) { njs_value_undefined_set(value); return NJS_OK; } 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 = njs_vm_value_string_set(vm, request_body, body, len); if (ret != NJS_OK) { return NJS_ERROR; } njs_value_assign(value, request_body); return NJS_OK; } static njs_int_t ngx_http_js_ext_get_header_in(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data) { u_char *p, *end, sep; size_t len; njs_str_t *v; ngx_uint_t i, n; ngx_array_t *a; ngx_table_elt_t *h, **hh; ngx_http_request_t *r; r = (ngx_http_request_t *) obj; v = (njs_str_t *) data; if (v->length == njs_length("Cookie") && ngx_strncasecmp(v->start, (u_char *) "Cookie", v->length) == 0) { sep = ';'; a = &r->headers_in.cookies; goto multi; } #if (NGX_HTTP_X_FORWARDED_FOR) if (v->length == njs_length("X-Forwarded-For") && ngx_strncasecmp(v->start, (u_char *) "X-Forwarded-For", v->length) == 0) { sep = ','; a = &r->headers_in.x_forwarded_for; goto multi; } #endif h = ngx_http_js_get_header(&r->headers_in.headers.part, v->start, v->length); if (h == NULL) { njs_value_undefined_set(value); return NJS_OK; } return njs_vm_value_string_set(vm, value, h->value.data, h->value.len); multi: /* Cookie, X-Forwarded-For */ n = a->nelts; hh = a->elts; len = 0; for (i = 0; i < n; i++) { len += hh[i]->value.len + 2; } if (len == 0) { njs_value_undefined_set(value); return NJS_OK; } len -= 2; if (n == 1) { return njs_vm_value_string_set(vm, value, (*hh)->value.data, (*hh)->value.len); } p = njs_vm_value_string_alloc(vm, value, 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; *p++ = ' '; } return NJS_OK; } static njs_int_t ngx_http_js_ext_keys_header_in(njs_vm_t *vm, void *obj, njs_value_t *keys) { return ngx_http_js_ext_keys_header(vm, obj, keys, offsetof(ngx_http_request_t, headers_in.headers)); } static njs_int_t ngx_http_js_ext_get_arg(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data) { njs_str_t *v; ngx_str_t arg; ngx_http_request_t *r; r = (ngx_http_request_t *) obj; v = (njs_str_t *) data; if (ngx_http_arg(r, v->start, v->length, &arg) == NGX_OK) { return njs_vm_value_string_set(vm, value, arg.data, arg.len); } njs_value_undefined_set(value); return NJS_OK; } static njs_int_t ngx_http_js_ext_keys_arg(njs_vm_t *vm, void *obj, njs_value_t *keys) { u_char *v, *p, *start, *end; njs_int_t rc; njs_value_t *value; ngx_http_request_t *r; r = (ngx_http_request_t *) obj; rc = njs_vm_array_alloc(vm, keys, 8); if (rc != NJS_OK) { return NJS_ERROR; } 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_get_variable(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data) { njs_str_t *v; ngx_str_t name; ngx_uint_t key; ngx_http_request_t *r; ngx_http_variable_value_t *vv; r = (ngx_http_request_t *) obj; v = (njs_str_t *) data; name.data = v->start; name.len = v->length; 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(value); return NJS_OK; } return njs_vm_value_string_set(vm, value, vv->data, vv->len); } static njs_int_t ngx_http_js_ext_set_variable(njs_vm_t *vm, void *obj, uintptr_t data, njs_str_t *value) { njs_str_t *val; ngx_str_t name; ngx_uint_t key; ngx_http_request_t *r; ngx_http_variable_t *v; ngx_http_variable_value_t *vv; ngx_http_core_main_conf_t *cmcf; r = (ngx_http_request_t *) obj; val = (njs_str_t *) data; name.data = val->start; name.len = val->length; 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 (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 = value->start; vv->len = value->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, value->length); if (vv->data == NULL) { njs_vm_error(vm, "internal error"); return NJS_ERROR; } vv->len = value->length; ngx_memcpy(vv->data, value->start, vv->len); return NJS_OK; } 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) { ngx_int_t rc; njs_str_t uri_arg, args_arg, method_name, body_arg; ngx_uint_t method, methods_max, has_body; njs_value_t *value, *arg, *options; njs_function_t *callback; ngx_http_js_ctx_t *ctx; ngx_http_request_t *r, *sr; ngx_http_request_body_t *rb; static const struct { ngx_str_t name; ngx_uint_t value; } methods[] = { { ngx_string("GET"), NGX_HTTP_GET }, { ngx_string("POST"), NGX_HTTP_POST }, { ngx_string("HEAD"), NGX_HTTP_HEAD }, { ngx_string("OPTIONS"), NGX_HTTP_OPTIONS }, { ngx_string("PROPFIND"), NGX_HTTP_PROPFIND }, { ngx_string("PUT"), NGX_HTTP_PUT }, { ngx_string("MKCOL"), NGX_HTTP_MKCOL }, { ngx_string("DELETE"), NGX_HTTP_DELETE }, { ngx_string("COPY"), NGX_HTTP_COPY }, { ngx_string("MOVE"), NGX_HTTP_MOVE }, { ngx_string("PROPPATCH"), NGX_HTTP_PROPPATCH }, { ngx_string("LOCK"), NGX_HTTP_LOCK }, { ngx_string("UNLOCK"), NGX_HTTP_UNLOCK }, { ngx_string("PATCH"), NGX_HTTP_PATCH }, { ngx_string("TRACE"), NGX_HTTP_TRACE }, }; static const njs_str_t args_key = njs_str("args"); static const njs_str_t method_key = njs_str("method"); static const njs_str_t body_key = njs_str("body"); r = njs_vm_external(vm, njs_arg(args, nargs, 0)); if (njs_slow_path(r == NULL)) { return NJS_ERROR; } ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); if (ctx->vm != vm) { njs_vm_error(vm, "subrequest can only be created for " "the primary request"); return NJS_ERROR; } if (ngx_http_js_string(vm, njs_arg(args, nargs, 1), &uri_arg) != NJS_OK) { njs_vm_error(vm, "failed to convert uri arg"); return NJS_ERROR; } if (uri_arg.length == 0) { njs_vm_error(vm, "uri is empty"); return NJS_ERROR; } options = NULL; callback = NULL; method = 0; methods_max = sizeof(methods) / sizeof(methods[0]); args_arg.length = 0; args_arg.start = NULL; has_body = 0; arg = njs_arg(args, nargs, 2); if (njs_value_is_string(arg)) { if (njs_vm_value_to_string(vm, &args_arg, arg) != NJS_OK) { njs_vm_error(vm, "failed to convert args"); return NJS_ERROR; } } else if (njs_value_is_function(arg)) { callback = njs_value_function(arg); } else if (njs_value_is_object(arg)) { options = arg; } else if (!njs_value_is_null_or_undefined(arg)) { njs_vm_error(vm, "failed to convert args"); return NJS_ERROR; } if (options != NULL) { value = njs_vm_object_prop(vm, options, &args_key); if (value != NULL) { if (ngx_http_js_string(vm, value, &args_arg) != NJS_OK) { njs_vm_error(vm, "failed to convert options.args"); return NJS_ERROR; } } value = njs_vm_object_prop(vm, options, &method_key); if (value != NULL) { if (ngx_http_js_string(vm, value, &method_name) != NJS_OK) { njs_vm_error(vm, "failed to convert options.method"); return NJS_ERROR; } while (method < methods_max) { if (method_name.length == methods[method].name.len && ngx_memcmp(method_name.start, methods[method].name.data, method_name.length) == 0) { break; } method++; } } value = njs_vm_object_prop(vm, options, &body_key); if (value != NULL) { if (ngx_http_js_string(vm, value, &body_arg) != NJS_OK) { njs_vm_error(vm, "failed to convert options.body"); return NJS_ERROR; } has_body = 1; } } arg = njs_arg(args, nargs, 3); if (callback == NULL && !njs_value_is_undefined(arg)) { if (!njs_value_is_function(arg)) { njs_vm_error(vm, "callback is not a function"); return NJS_ERROR; } else { callback = njs_value_function(arg); } } rc = ngx_http_js_subrequest(r, &uri_arg, &args_arg, callback, &sr); if (rc != NGX_OK) { return NJS_ERROR; } if (method != methods_max) { sr->method = methods[method].value; sr->method_name = methods[method].name; } else { sr->method = NGX_HTTP_UNKNOWN; sr->method_name.len = method_name.length; sr->method_name.data = method_name.start; } sr->header_only = (sr->method == NGX_HTTP_HEAD) || (callback == NULL); if (has_body) { rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); if (rb == NULL) { goto memory_error; } if (body_arg.length != 0) { rb->bufs = ngx_alloc_chain_link(r->pool); if (rb->bufs == NULL) { goto memory_error; } rb->bufs->next = NULL; rb->bufs->buf = ngx_calloc_buf(r->pool); if (rb->bufs->buf == NULL) { goto memory_error; } rb->bufs->buf->memory = 1; rb->bufs->buf->last_buf = 1; rb->bufs->buf->pos = body_arg.start; rb->bufs->buf->last = body_arg.start + body_arg.length; } sr->request_body = rb; sr->headers_in.content_length_n = body_arg.length; sr->headers_in.chunked = 0; } return NJS_OK; memory_error: njs_vm_error(ctx->vm, "internal error"); return NJS_ERROR; } 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) { ngx_int_t flags; ngx_str_t uri, args; njs_vm_event_t vm_event; ngx_http_js_ctx_t *ctx; ngx_http_post_subrequest_t *ps; ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); flags = NGX_HTTP_SUBREQUEST_BACKGROUND; if (callback != NULL) { ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t)); if (ps == NULL) { njs_vm_error(ctx->vm, "internal error"); return NJS_ERROR; } vm_event = njs_vm_add_event(ctx->vm, callback, 1, NULL, NULL); if (vm_event == NULL) { njs_vm_error(ctx->vm, "internal error"); return NJS_ERROR; } ps->handler = ngx_http_js_subrequest_done; ps->data = vm_event; flags |= NGX_HTTP_SUBREQUEST_IN_MEMORY; } else { ps = NULL; vm_event = NULL; } uri.len = uri_arg->length; uri.data = uri_arg->start; args.len = args_arg->length; args.data = args_arg->start; if (ngx_http_subrequest(r, &uri, args.len ? &args : NULL, sr, ps, flags) != NGX_OK) { if (vm_event != NULL) { njs_vm_del_event(ctx->vm, vm_event); } njs_vm_error(ctx->vm, "subrequest creation failed"); return NJS_ERROR; } return NJS_OK; } static ngx_int_t ngx_http_js_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc) { njs_vm_event_t vm_event = data; njs_int_t ret; ngx_http_js_ctx_t *ctx; njs_opaque_value_t reply; ngx_http_js_main_conf_t *jmcf; if (rc != NGX_OK || r->connection->error || r->buffered) { return rc; } ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); if (ctx && ctx->done) { return NGX_OK; } if (ctx == NULL) { ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_js_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_js_module); } ctx->done = 1; jmcf = ngx_http_get_module_main_conf(r, ngx_http_js_module); ctx = ngx_http_get_module_ctx(r->parent, ngx_http_js_module); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "js subrequest done s: %ui parent ctx: %p", r->headers_out.status, ctx); if (ctx == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "js subrequest: failed to get the parent context"); return NGX_ERROR; } ret = njs_vm_external_create(ctx->vm, njs_value_arg(&reply), jmcf->req_proto, r); if (ret != NJS_OK) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "js subrequest reply creation failed"); return NGX_ERROR; } ngx_http_js_handle_event(r->parent, vm_event, njs_value_arg(&reply), 1); return NGX_OK; } static njs_int_t ngx_http_js_ext_get_parent(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data) { ngx_http_js_ctx_t *ctx; ngx_http_request_t *r; r = (ngx_http_request_t *) obj; ctx = r->parent ? ngx_http_get_module_ctx(r->parent, ngx_http_js_module) : NULL; if (ctx == NULL || ctx->vm != vm) { njs_vm_error(vm, "parent can only be returned for a subrequest"); return NJS_ERROR; } njs_value_assign(value, njs_value_arg(&ctx->request)); return NJS_OK; } static njs_int_t ngx_http_js_ext_get_reply_body(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data) { size_t len; u_char *p; ngx_buf_t *b; ngx_http_request_t *r; r = (ngx_http_request_t *) obj; b = r->out ? r->out->buf : NULL; len = b ? b->last - b->pos : 0; p = njs_vm_value_string_alloc(vm, value, len); if (p == NULL) { return NJS_ERROR; } if (len) { ngx_memcpy(p, b->pos, len); } return NJS_OK; } static njs_host_event_t ngx_http_js_set_timer(njs_external_ptr_t external, uint64_t delay, njs_vm_event_t vm_event) { ngx_event_t *ev; ngx_http_request_t *r; ngx_http_js_event_t *js_event; r = (ngx_http_request_t *) external; ev = ngx_pcalloc(r->pool, sizeof(ngx_event_t)); if (ev == NULL) { return NULL; } js_event = ngx_palloc(r->pool, sizeof(ngx_http_js_event_t)); if (js_event == NULL) { return NULL; } js_event->request = r; js_event->vm_event = vm_event; js_event->ident = r->connection->fd; ev->data = js_event; ev->log = r->connection->log; ev->handler = ngx_http_js_timer_handler; ngx_add_timer(ev, delay); return ev; } static void ngx_http_js_clear_timer(njs_external_ptr_t external, njs_host_event_t event) { ngx_event_t *ev = event; if (ev->timer_set) { ngx_del_timer(ev); } } static void ngx_http_js_timer_handler(ngx_event_t *ev) { ngx_connection_t *c; ngx_http_request_t *r; ngx_http_js_event_t *js_event; js_event = (ngx_http_js_event_t *) ev->data; r = js_event->request; c = r->connection; ngx_http_js_handle_event(r, js_event->vm_event, NULL, 0); ngx_http_run_posted_requests(c); } 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) { njs_int_t rc; njs_str_t exception; ngx_http_js_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); njs_vm_post_event(ctx->vm, vm_event, args, nargs); rc = njs_vm_run(ctx->vm); if (rc == NJS_ERROR) { njs_vm_retval_string(ctx->vm, &exception); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "js exception: %*s", exception.length, exception.start); ngx_http_finalize_request(r, NGX_ERROR); } if (rc == NJS_OK) { ngx_http_post_request(r, NULL); } } static njs_int_t ngx_http_js_string(njs_vm_t *vm, njs_value_t *value, njs_str_t *str) { if (!njs_value_is_null_or_undefined(value)) { if (njs_vm_value_to_string(vm, str, value) == NJS_ERROR) { return NJS_ERROR; } } else { str->start = NULL; str->length = 0; } return NJS_OK; } static char * ngx_http_js_include(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_js_main_conf_t *jmcf = conf; size_t size; u_char *start, *end; ssize_t n; ngx_fd_t fd; ngx_str_t *m, *value, file; njs_int_t rc; njs_str_t text, path; ngx_uint_t i; njs_vm_opt_t options; ngx_file_info_t fi; ngx_pool_cleanup_t *cln; if (jmcf->vm) { return "is duplicate"; } value = cf->args->elts; file = value[1]; if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) { return NGX_CONF_ERROR; } fd = ngx_open_file(file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); if (fd == NGX_INVALID_FILE) { ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, ngx_open_file_n " \"%s\" failed", file.data); return NGX_CONF_ERROR; } if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, ngx_fd_info_n " \"%s\" failed", file.data); (void) ngx_close_file(fd); return NGX_CONF_ERROR; } size = ngx_file_size(&fi); start = ngx_pnalloc(cf->pool, size); if (start == NULL) { (void) ngx_close_file(fd); return NGX_CONF_ERROR; } n = ngx_read_fd(fd, start, size); if (n == -1) { ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, ngx_read_fd_n " \"%s\" failed", file.data); (void) ngx_close_file(fd); return NGX_CONF_ERROR; } if ((size_t) n != size) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, ngx_read_fd_n " has read only %z of %O from \"%s\"", n, size, file.data); (void) ngx_close_file(fd); return NGX_CONF_ERROR; } if (ngx_close_file(fd) == NGX_FILE_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, ngx_close_file_n " %s failed", file.data); } end = start + size; ngx_memzero(&options, sizeof(njs_vm_opt_t)); options.backtrace = 1; options.ops = &ngx_http_js_ops; options.argv = ngx_argv; options.argc = ngx_argc; file = value[1]; options.file.start = file.data; options.file.length = file.len; jmcf->vm = njs_vm_create(&options); if (jmcf->vm == NULL) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "failed to create JS VM"); return NGX_CONF_ERROR; } cln = ngx_pool_cleanup_add(cf->pool, 0); if (cln == NULL) { return NGX_CONF_ERROR; } cln->handler = ngx_http_js_cleanup_vm; cln->data = jmcf->vm; path.start = ngx_cycle->prefix.data; path.length = ngx_cycle->prefix.len; rc = njs_vm_add_path(jmcf->vm, &path); if (rc != NJS_OK) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "failed to add path"); return NGX_CONF_ERROR; } if (jmcf->paths != NGX_CONF_UNSET_PTR) { m = jmcf->paths->elts; for (i = 0; i < jmcf->paths->nelts; i++) { if (ngx_conf_full_name(cf->cycle, &m[i], 0) != NGX_OK) { return NGX_CONF_ERROR; } path.start = m[i].data; path.length = m[i].len; rc = njs_vm_add_path(jmcf->vm, &path); if (rc != NJS_OK) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "failed to add path"); return NGX_CONF_ERROR; } } } jmcf->req_proto = njs_vm_external_prototype(jmcf->vm, &ngx_http_js_externals[0]); if (jmcf->req_proto == NULL) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "failed to add request proto"); return NGX_CONF_ERROR; } rc = njs_vm_compile(jmcf->vm, &start, end); if (rc != NJS_OK) { njs_vm_retval_string(jmcf->vm, &text); ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%*s, included", text.length, text.start); return NGX_CONF_ERROR; } if (start != end) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "extra characters in js script: \"%*s\", included", end - start, start); return NGX_CONF_ERROR; } return NGX_CONF_OK; } static char * ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_str_t *value, *fname; ngx_http_variable_t *v; value = cf->args->elts; if (value[1].data[0] != '$') { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid variable name \"%V\"", &value[1]); return NGX_CONF_ERROR; } value[1].len--; value[1].data++; v = ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE); if (v == NULL) { return NGX_CONF_ERROR; } fname = ngx_palloc(cf->pool, sizeof(ngx_str_t)); if (fname == NULL) { return NGX_CONF_ERROR; } *fname = value[2]; v->get_handler = ngx_http_js_variable; v->data = (uintptr_t) fname; return NGX_CONF_OK; } static char * ngx_http_js_content(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_js_loc_conf_t *jlcf = conf; ngx_str_t *value; ngx_http_core_loc_conf_t *clcf; if (jlcf->content.data) { return "is duplicate"; } value = cf->args->elts; jlcf->content = value[1]; clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); clcf->handler = ngx_http_js_content_handler; return NGX_CONF_OK; } static void * ngx_http_js_create_main_conf(ngx_conf_t *cf) { ngx_http_js_main_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_js_main_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->vm = NULL; * conf->req_proto = NULL; */ conf->paths = NGX_CONF_UNSET_PTR; return conf; } static void * ngx_http_js_create_loc_conf(ngx_conf_t *cf) { ngx_http_js_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_js_loc_conf_t)); if (conf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * * conf->content = { 0, NULL }; */ return conf; } static char * ngx_http_js_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { return NGX_CONF_OK; }