| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #ifdef XP_WIN |
| #include <windows.h> |
| #else |
| #include <unistd.h> |
| #endif |
| |
| #include <jsapi.h> |
| #include <js/Initialization.h> |
| #include <js/Conversions.h> |
| #include <js/Wrapper.h> |
| |
| #include "config.h" |
| #include "http.h" |
| #include "utf8.h" |
| #include "util.h" |
| |
| static bool enableSharedMemory = true; |
| |
| static JSClassOps global_ops = { |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| JS_GlobalObjectTraceHook |
| }; |
| |
| /* The class of the global object. */ |
| static JSClass global_class = { |
| "global", |
| JSCLASS_GLOBAL_FLAGS, |
| &global_ops |
| }; |
| |
| |
| static void |
| req_dtor(JSFreeOp* fop, JSObject* obj) |
| { |
| http_dtor(fop, obj); |
| } |
| |
| // With JSClass.construct. |
| static const JSClassOps clsOps = { |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| req_dtor, |
| nullptr, |
| nullptr, |
| nullptr |
| }; |
| |
| static const JSClass CouchHTTPClass = { |
| "CouchHTTP", /* name */ |
| JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(2), /* flags */ |
| &clsOps |
| }; |
| |
| static bool |
| req_ctor(JSContext* cx, unsigned int argc, JS::Value* vp) |
| { |
| bool ret; |
| JS::CallArgs args = JS::CallArgsFromVp(argc, vp); |
| JSObject* obj = JS_NewObjectForConstructor(cx, &CouchHTTPClass, args); |
| if(!obj) { |
| JS_ReportErrorUTF8(cx, "Failed to create CouchHTTP instance"); |
| return false; |
| } |
| ret = http_ctor(cx, obj); |
| args.rval().setObject(*obj); |
| return ret; |
| } |
| |
| static bool |
| req_open(JSContext* cx, unsigned int argc, JS::Value* vp) |
| { |
| JSObject* obj = JS_THIS_OBJECT(cx, vp); |
| JS::CallArgs args = JS::CallArgsFromVp(argc, vp); |
| bool ret = false; |
| |
| if(argc == 2) { |
| ret = http_open(cx, obj, args[0], args[1], JS::BooleanValue(false)); |
| } else if(argc == 3) { |
| ret = http_open(cx, obj, args[0], args[1], args[2]); |
| } else { |
| JS_ReportErrorUTF8(cx, "Invalid call to CouchHTTP.open"); |
| } |
| |
| args.rval().setUndefined(); |
| return ret; |
| } |
| |
| |
| static bool |
| req_set_hdr(JSContext* cx, unsigned int argc, JS::Value* vp) |
| { |
| JSObject* obj = JS_THIS_OBJECT(cx, vp); |
| JS::CallArgs args = JS::CallArgsFromVp(argc, vp); |
| bool ret = false; |
| |
| if(argc == 2) { |
| ret = http_set_hdr(cx, obj, args[0], args[1]); |
| } else { |
| JS_ReportErrorUTF8(cx, "Invalid call to CouchHTTP.set_header"); |
| } |
| |
| args.rval().setUndefined(); |
| return ret; |
| } |
| |
| |
| static bool |
| req_send(JSContext* cx, unsigned int argc, JS::Value* vp) |
| { |
| JSObject* obj = JS_THIS_OBJECT(cx, vp); |
| JS::CallArgs args = JS::CallArgsFromVp(argc, vp); |
| bool ret = false; |
| |
| if(argc == 1) { |
| ret = http_send(cx, obj, args[0]); |
| } else { |
| JS_ReportErrorUTF8(cx, "Invalid call to CouchHTTP.send"); |
| } |
| |
| args.rval().setUndefined(); |
| return ret; |
| } |
| |
| static bool |
| req_status(JSContext* cx, unsigned int argc, JS::Value* vp) |
| { |
| JS::CallArgs args = JS::CallArgsFromVp(argc, vp); |
| JSObject* obj = JS_THIS_OBJECT(cx, vp); |
| int status = http_status(cx, obj); |
| |
| if(status < 0) |
| return false; |
| |
| args.rval().set(JS::Int32Value(status)); |
| return true; |
| } |
| |
| static bool |
| base_url(JSContext *cx, unsigned int argc, JS::Value* vp) |
| { |
| JS::CallArgs args = JS::CallArgsFromVp(argc, vp); |
| JSObject* obj = JS_THIS_OBJECT(cx, vp); |
| couch_args *cargs = (couch_args*)JS_GetContextPrivate(cx); |
| JS::Value uri_val; |
| bool rc = http_uri(cx, obj, cargs, &uri_val); |
| args.rval().set(uri_val); |
| return rc; |
| } |
| |
| static void |
| SetStandardCompartmentOptions(JS::CompartmentOptions& options) |
| { |
| options.creationOptions().setSharedMemoryAndAtomicsEnabled(enableSharedMemory); |
| } |
| |
| static JSObject* |
| NewSandbox(JSContext* cx, bool lazy) |
| { |
| JS::CompartmentOptions options; |
| SetStandardCompartmentOptions(options); |
| JS::RootedObject obj(cx, JS_NewGlobalObject(cx, &global_class, nullptr, |
| JS::DontFireOnNewGlobalHook, options)); |
| if (!obj) |
| return nullptr; |
| |
| { |
| JSAutoCompartment ac(cx, obj); |
| if (!lazy && !JS_InitStandardClasses(cx, obj)) |
| return nullptr; |
| |
| JS::RootedValue value(cx, JS::BooleanValue(lazy)); |
| if (!JS_DefineProperty(cx, obj, "lazy", value, JSPROP_PERMANENT | JSPROP_READONLY)) |
| return nullptr; |
| |
| JS_FireOnNewGlobalObject(cx, obj); |
| } |
| |
| if (!JS_WrapObject(cx, &obj)) |
| return nullptr; |
| return obj; |
| } |
| |
| static bool |
| evalcx(JSContext *cx, unsigned int argc, JS::Value* vp) |
| { |
| JS::CallArgs args = JS::CallArgsFromVp(argc, vp); |
| bool ret = false; |
| |
| JS::RootedString str(cx, JS::ToString(cx, args[0])); |
| if (!str) |
| return false; |
| |
| JS::RootedObject sandbox(cx); |
| if (args.hasDefined(1)) { |
| sandbox = JS::ToObject(cx, args[1]); |
| if (!sandbox) |
| return false; |
| } |
| JS_BeginRequest(cx); |
| JSAutoRequest ar(cx); |
| |
| js::AutoStableStringChars strChars(cx); |
| if (!strChars.initTwoByte(cx, str)) |
| return false; |
| |
| mozilla::Range<const char16_t> chars = strChars.twoByteRange(); |
| size_t srclen = chars.length(); |
| const char16_t* src = chars.begin().get(); |
| |
| if (!sandbox) { |
| sandbox = NewSandbox(cx, false); |
| if (!sandbox) |
| return false; |
| } |
| |
| if(srclen == 0) { |
| args.rval().setObject(*sandbox); |
| } else { |
| mozilla::Maybe<JSAutoCompartment> ac; |
| unsigned flags; |
| JSObject* unwrapped = UncheckedUnwrap(sandbox, true, &flags); |
| if (flags & js::Wrapper::CROSS_COMPARTMENT) { |
| sandbox = unwrapped; |
| ac.emplace(cx, sandbox); |
| } |
| |
| JS::CompileOptions opts(cx); |
| JS::RootedValue rval(cx); |
| opts.setFileAndLine("<unknown>", 1); |
| if (!JS::Evaluate(cx, opts, src, srclen, args.rval())) { |
| return false; |
| } |
| } |
| ret = true; |
| if (!JS_WrapValue(cx, args.rval())) |
| return false; |
| |
| return ret; |
| } |
| |
| |
| static bool |
| gc(JSContext* cx, unsigned int argc, JS::Value* vp) |
| { |
| JS::CallArgs args = JS::CallArgsFromVp(argc, vp); |
| JS_GC(cx); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| |
| static bool |
| print(JSContext* cx, unsigned int argc, JS::Value* vp) |
| { |
| JS::CallArgs args = JS::CallArgsFromVp(argc, vp); |
| couch_print(cx, argc, args); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| |
| static bool |
| quit(JSContext* cx, unsigned int argc, JS::Value* vp) |
| { |
| JS::CallArgs args = JS::CallArgsFromVp(argc, vp); |
| |
| int exit_code = args[0].toInt32();; |
| exit(exit_code); |
| } |
| |
| |
| static bool |
| readline(JSContext* cx, unsigned int argc, JS::Value* vp) |
| { |
| JSString* line; |
| JS::CallArgs args = JS::CallArgsFromVp(argc, vp); |
| |
| /* GC Occasionally */ |
| JS_MaybeGC(cx); |
| |
| line = couch_readline(cx, stdin); |
| if(line == NULL) return false; |
| |
| // return with JSString* instead of JSValue in the past |
| args.rval().setString(line); |
| return true; |
| } |
| |
| |
| static bool |
| seal(JSContext* cx, unsigned int argc, JS::Value* vp) |
| { |
| JS::CallArgs args = JS::CallArgsFromVp(argc, vp); |
| JS::RootedObject target(cx); |
| target = JS::ToObject(cx, args[0]); |
| if (!target) { |
| args.rval().setUndefined(); |
| return true; |
| } |
| bool deep = false; |
| deep = args[1].toBoolean(); |
| bool ret = deep ? JS_DeepFreezeObject(cx, target) : JS_FreezeObject(cx, target); |
| args.rval().setUndefined(); |
| return ret; |
| } |
| |
| |
| static bool |
| js_sleep(JSContext* cx, unsigned int argc, JS::Value* vp) |
| { |
| JS::CallArgs args = JS::CallArgsFromVp(argc, vp); |
| |
| int duration = args[0].toInt32(); |
| |
| #ifdef XP_WIN |
| Sleep(duration); |
| #else |
| usleep(duration * 1000); |
| #endif |
| |
| return true; |
| } |
| |
| JSPropertySpec CouchHTTPProperties[] = { |
| JS_PSG("status", req_status, 0), |
| JS_PSG("base_url", base_url, 0), |
| JS_PS_END |
| }; |
| |
| |
| JSFunctionSpec CouchHTTPFunctions[] = { |
| JS_FN("_open", req_open, 3, 0), |
| JS_FN("_setRequestHeader", req_set_hdr, 2, 0), |
| JS_FN("_send", req_send, 1, 0), |
| JS_FS_END |
| }; |
| |
| |
| JSFunctionSpec TestSuiteFunctions[] = { |
| JS_FN("sleep", js_sleep, 1, 0), |
| JS_FS_END |
| }; |
| |
| |
| static JSFunctionSpec global_functions[] = { |
| JS_FN("evalcx", evalcx, 0, 0), |
| JS_FN("gc", gc, 0, 0), |
| JS_FN("print", print, 0, 0), |
| JS_FN("quit", quit, 0, 0), |
| JS_FN("readline", readline, 0, 0), |
| JS_FN("seal", seal, 0, 0), |
| JS_FS_END |
| }; |
| |
| |
| static bool |
| csp_allows(JSContext* cx) |
| { |
| couch_args *args = (couch_args*)JS_GetContextPrivate(cx); |
| if(args->eval) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| |
| static JSSecurityCallbacks security_callbacks = { |
| csp_allows, |
| nullptr |
| }; |
| |
| |
| int |
| main(int argc, const char* argv[]) |
| { |
| JSContext* cx = NULL; |
| JSObject* klass = NULL; |
| char* scriptsrc; |
| size_t slen; |
| int i; |
| |
| couch_args* args = couch_parse_args(argc, argv); |
| |
| JS_Init(); |
| cx = JS_NewContext(args->stack_size, 8L * 1024L); |
| if(cx == NULL) |
| return 1; |
| |
| if (!JS::InitSelfHostedCode(cx)) |
| return 1; |
| |
| JS::SetWarningReporter(cx, couch_error); |
| JS::SetOutOfMemoryCallback(cx, couch_oom, NULL); |
| JS_SetContextPrivate(cx, args); |
| JS_SetSecurityCallbacks(cx, &security_callbacks); |
| |
| JSAutoRequest ar(cx); |
| JS::CompartmentOptions options; |
| JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr, |
| JS::FireOnNewGlobalHook, options)); |
| if (!global) |
| return 1; |
| |
| JSAutoCompartment ac(cx, global); |
| |
| if(!JS_InitStandardClasses(cx, global)) |
| return 1; |
| |
| if(couch_load_funcs(cx, global, global_functions) != true) |
| return 1; |
| |
| if(args->use_http) { |
| http_check_enabled(); |
| |
| klass = JS_InitClass( |
| cx, global, |
| NULL, |
| &CouchHTTPClass, req_ctor, |
| 0, |
| CouchHTTPProperties, CouchHTTPFunctions, |
| NULL, NULL |
| ); |
| |
| if(!klass) |
| { |
| fprintf(stderr, "Failed to initialize CouchHTTP class.\n"); |
| exit(2); |
| } |
| } |
| |
| if(args->use_test_funs) { |
| if(couch_load_funcs(cx, global, TestSuiteFunctions) != true) |
| return 1; |
| } |
| |
| for(i = 0 ; args->scripts[i] ; i++) { |
| slen = couch_readfile(args->scripts[i], &scriptsrc); |
| |
| // Compile and run |
| JS::CompileOptions options(cx); |
| options.setFileAndLine(args->scripts[i], 1); |
| JS::RootedScript script(cx); |
| |
| if(!JS_CompileScript(cx, scriptsrc, slen, options, &script)) { |
| fprintf(stderr, "Failed to compile script.\n"); |
| return 1; |
| } |
| |
| free(scriptsrc); |
| |
| JS::RootedValue result(cx); |
| if(JS_ExecuteScript(cx, script, &result) != true) { |
| fprintf(stderr, "Failed to execute script.\n"); |
| return 1; |
| } |
| |
| // Give the GC a chance to run. |
| JS_MaybeGC(cx); |
| } |
| |
| return 0; |
| } |