/* * Copyright (C) NGINX, Inc. */ #include struct nxt_js_s { uint32_t index; }; typedef struct { nxt_str_t name; nxt_str_t text; } nxt_js_module_t; struct nxt_js_conf_s { nxt_mp_t *pool; njs_vm_t *vm; njs_uint_t protos; njs_external_t *proto; nxt_str_t init; nxt_array_t *modules; /* of nxt_js_module_t */ nxt_array_t *funcs; uint8_t test; /* 1 bit */ }; njs_mod_t * nxt_js_module_loader(njs_vm_t *vm, njs_external_ptr_t external, njs_str_t *name) { nxt_str_t text; nxt_uint_t i, n; nxt_js_conf_t *jcf; nxt_js_module_t *modules, *module; jcf = external; module = NULL; n = jcf->modules->nelts; modules = jcf->modules->elts; for (i = 0; i < n; i++) { if (nxt_strstr_eq(name, &modules[i].name)) { module = &modules[i]; break; } } if (module == NULL) { return NULL; } text.length = module->text.length; text.start = njs_mp_alloc(vm->mem_pool, text.length); if (nxt_slow_path(text.start == NULL)) { return NULL; } nxt_memcpy(text.start, module->text.start, text.length); return njs_vm_compile_module(vm, name, &text.start, &text.start[text.length]); } njs_int_t nxt_js_proto_id; nxt_js_conf_t * nxt_js_conf_new(nxt_mp_t *mp, nxt_bool_t test) { nxt_js_conf_t *jcf; jcf = nxt_mp_zget(mp, sizeof(nxt_js_conf_t)); if (nxt_slow_path(jcf == NULL)) { return NULL; } jcf->pool = mp; jcf->test = test; jcf->modules = nxt_array_create(mp, 4, sizeof(nxt_js_module_t)); if (nxt_slow_path(jcf->modules == NULL)) { return NULL; } jcf->funcs = nxt_array_create(mp, 4, sizeof(nxt_str_t)); if (nxt_slow_path(jcf->funcs == NULL)) { return NULL; } return jcf; } void nxt_js_conf_release(nxt_js_conf_t *jcf) { njs_vm_destroy(jcf->vm); } void nxt_js_set_proto(nxt_js_conf_t *jcf, njs_external_t *proto, njs_uint_t n) { jcf->protos = n; jcf->proto = proto; } static njs_vm_t * nxt_js_vm_create(nxt_js_conf_t *jcf) { u_char *p; size_t size; njs_vm_t *vm; nxt_uint_t i; njs_vm_opt_t opts; nxt_js_module_t *module, *mod; static const nxt_str_t import_str = nxt_string("import"); static const nxt_str_t from_str = nxt_string("from"); static const nxt_str_t global_str = nxt_string("globalThis"); njs_vm_opt_init(&opts); opts.backtrace = 1; opts.file.start = (u_char *) "default"; opts.file.length = 7; if (jcf->test || jcf->modules->nelts == 0) { goto done; } opts.external = jcf; size = 0; module = jcf->modules->elts; for (i = 0; i < jcf->modules->nelts; i++) { mod = &module[i]; size += import_str.length + 1 + mod->name.length + 1 + from_str.length + 2 + mod->name.length + 3; size += global_str.length + 1 + mod->name.length + 3 + mod->name.length + 2; } p = nxt_mp_nget(jcf->pool, size); if (nxt_slow_path(p == NULL)) { return NULL; } jcf->init.length = size; jcf->init.start = p; for (i = 0; i < jcf->modules->nelts; i++) { mod = &module[i]; p = nxt_cpymem(p, import_str.start, import_str.length); *p++ = ' '; p = nxt_cpymem(p, mod->name.start, mod->name.length); *p++ = ' '; p = nxt_cpymem(p, from_str.start, from_str.length); *p++ = ' '; *p++ = '\"'; p = nxt_cpymem(p, mod->name.start, mod->name.length); *p++ = '\"'; *p++ = ';'; *p++ = '\n'; p = nxt_cpymem(p, global_str.start, global_str.length); *p++ = '.'; p = nxt_cpymem(p, mod->name.start, mod->name.length); *p++ = ' '; *p++ = '='; *p++ = ' '; p = nxt_cpymem(p, mod->name.start, mod->name.length); *p++ = ';'; *p++ = '\n'; } done: vm = njs_vm_create(&opts); if (nxt_fast_path(vm != NULL)) { njs_vm_set_module_loader(vm, nxt_js_module_loader, jcf); } return vm; } nxt_int_t nxt_js_add_module(nxt_js_conf_t *jcf, nxt_str_t *name, nxt_str_t *text) { nxt_js_module_t *module; module = nxt_array_add(jcf->modules); if (nxt_slow_path(module == NULL)) { return NXT_ERROR; } module->name = *name; module->text.length = text->length; module->text.start = nxt_mp_nget(jcf->pool, text->length); if (nxt_slow_path(module->text.start == NULL)) { return NXT_ERROR; } nxt_memcpy(module->text.start, text->start, text->length); return NXT_OK; } nxt_js_t * nxt_js_add_tpl(nxt_js_conf_t *jcf, nxt_str_t *str, nxt_uint_t flags) { size_t size; u_char *p, *start; nxt_js_t *js; nxt_str_t *func; static const nxt_str_t func_str = nxt_string("function(uri, host, remoteAddr, " "args, headers, cookies, vars) {" " return "); /* * Append a newline character if newline is true. * Append a terminating null character if strz is true. */ static const nxt_str_t newline_str = nxt_string(" + '\\x0A'"); static const nxt_str_t strz_str = nxt_string(" + '\\x00'"); size = func_str.length + str->length + 1; if (flags & NXT_TSTR_NEWLINE) { size += newline_str.length; } if (flags & NXT_TSTR_STRZ) { size += strz_str.length; } start = nxt_mp_nget(jcf->pool, size); if (nxt_slow_path(start == NULL)) { return NULL; } p = start; p = nxt_cpymem(p, func_str.start, func_str.length); p = nxt_cpymem(p, str->start, str->length); if (flags & NXT_TSTR_NEWLINE) { p = nxt_cpymem(p, newline_str.start, newline_str.length); } if (flags & NXT_TSTR_STRZ) { p = nxt_cpymem(p, strz_str.start, strz_str.length); } *p++ = '}'; js = nxt_mp_get(jcf->pool, sizeof(nxt_js_t)); if (nxt_slow_path(js == NULL)) { return NULL; } func = nxt_array_add(jcf->funcs); if (nxt_slow_path(func == NULL)) { return NULL; } func->start = start; func->length = p - start; js->index = jcf->funcs->nelts - 1; return js; } nxt_int_t nxt_js_compile(nxt_js_conf_t *jcf) { size_t size; u_char *p, *start; njs_int_t ret; nxt_str_t *func; nxt_uint_t i; if (jcf->test) { return NXT_OK; } jcf->vm = nxt_js_vm_create(jcf); if (nxt_slow_path(jcf->vm == NULL)) { return NXT_ERROR; } size = jcf->init.length + 2; func = jcf->funcs->elts; for (i = 0; i < jcf->funcs->nelts; i++) { size += func[i].length + 1; } start = nxt_mp_nget(jcf->pool, size); if (nxt_slow_path(start == NULL)) { return NXT_ERROR; } p = nxt_cpymem(start, jcf->init.start, jcf->init.length); *p++ = '['; func = jcf->funcs->elts; for (i = 0; i < jcf->funcs->nelts; i++) { p = nxt_cpymem(p, func[i].start, func[i].length); *p++ = ','; } *p++ = ']'; nxt_js_proto_id = njs_vm_external_prototype(jcf->vm, jcf->proto, jcf->protos); if (nxt_slow_path(nxt_js_proto_id < 0)) { return NXT_ERROR; } ret = njs_vm_compile(jcf->vm, &start, p); return (ret == NJS_OK) ? NXT_OK : NXT_ERROR; } nxt_int_t nxt_js_test(nxt_js_conf_t *jcf, nxt_str_t *str, u_char *error) { u_char *start; njs_vm_t *vm; njs_int_t ret; vm = nxt_js_vm_create(jcf); if (nxt_slow_path(vm == NULL)) { return NXT_ERROR; } start = nxt_mp_nget(jcf->pool, str->length); if (nxt_slow_path(start == NULL)) { goto fail; } nxt_memcpy(start, str->start, str->length); ret = njs_vm_compile(vm, &start, start + str->length); if (nxt_slow_path(ret != NJS_OK)) { (void) nxt_js_error(vm, error); goto fail; } njs_vm_destroy(vm); return NXT_OK; fail: njs_vm_destroy(vm); return NXT_ERROR; } nxt_int_t nxt_js_call(nxt_task_t *task, nxt_js_conf_t *jcf, nxt_js_cache_t *cache, nxt_js_t *js, nxt_str_t *str, void *ctx) { njs_vm_t *vm; njs_int_t ret; njs_str_t res; njs_uint_t i, n; njs_value_t *value; njs_function_t *func; njs_opaque_value_t retval, opaque_value, arguments[7]; static const njs_str_t js_args[] = { njs_str("uri"), njs_str("host"), njs_str("remoteAddr"), njs_str("args"), njs_str("headers"), njs_str("cookies"), njs_str("vars"), }; vm = cache->vm; if (vm == NULL) { vm = njs_vm_clone(jcf->vm, ctx); if (nxt_slow_path(vm == NULL)) { return NXT_ERROR; } cache->vm = vm; ret = njs_vm_start(vm, &cache->array); if (ret != NJS_OK) { return NXT_ERROR; } } value = njs_vm_array_prop(vm, &cache->array, js->index, &opaque_value); func = njs_value_function(value); ret = njs_vm_external_create(vm, njs_value_arg(&opaque_value), nxt_js_proto_id, ctx, 0); if (nxt_slow_path(ret != NJS_OK)) { return NXT_ERROR; } n = nxt_nitems(js_args); for (i = 0; i < n; i++) { value = njs_vm_object_prop(vm, njs_value_arg(&opaque_value), &js_args[i], &arguments[i]); if (nxt_slow_path(value == NULL)) { return NXT_ERROR; } } ret = njs_vm_invoke(vm, func, njs_value_arg(&arguments), n, njs_value_arg(&retval)); if (ret != NJS_OK) { ret = njs_vm_exception_string(vm, &res); if (ret == NJS_OK) { nxt_alert(task, "js exception: %V", &res); } return NXT_ERROR; } ret = njs_vm_value_string(vm, &res, njs_value_arg(&retval)); str->length = res.length; str->start = res.start; return NXT_OK; } void nxt_js_release(nxt_js_cache_t *cache) { if (cache->vm != NULL) { njs_vm_destroy(cache->vm); } } nxt_int_t nxt_js_error(njs_vm_t *vm, u_char *error) { njs_int_t ret; njs_str_t res; nxt_str_t err; ret = njs_vm_exception_string(vm, &res); if (nxt_slow_path(ret != NJS_OK)) { return NXT_ERROR; } err.start = res.start; err.length = res.length; nxt_sprintf(error, error + NXT_MAX_ERROR_STR, "\"%V\"%Z", &err); return NXT_OK; }