blob: ecedfbd3b202bb076dfe6f176706b6362ad7356d [file] [log] [blame]
// 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_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;
}