michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim: set ts=8 sts=4 et sw=4 tw=99: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * The Components.Sandbox object. michael@0: */ michael@0: michael@0: #include "AccessCheck.h" michael@0: #include "jsfriendapi.h" michael@0: #include "jsproxy.h" michael@0: #include "js/OldDebugAPI.h" michael@0: #include "js/StructuredClone.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsGlobalWindow.h" michael@0: #include "nsIScriptContext.h" michael@0: #include "nsIScriptObjectPrincipal.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIURI.h" michael@0: #include "nsJSUtils.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsPrincipal.h" michael@0: #include "nsXMLHttpRequest.h" michael@0: #include "WrapperFactory.h" michael@0: #include "xpcprivate.h" michael@0: #include "XPCQuickStubs.h" michael@0: #include "XPCWrapper.h" michael@0: #include "XrayWrapper.h" michael@0: #include "mozilla/dom/BindingUtils.h" michael@0: #include "mozilla/dom/indexedDB/IndexedDatabaseManager.h" michael@0: #include "mozilla/dom/PromiseBinding.h" michael@0: #include "mozilla/dom/TextDecoderBinding.h" michael@0: #include "mozilla/dom/TextEncoderBinding.h" michael@0: #include "mozilla/dom/URLBinding.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace JS; michael@0: using namespace js; michael@0: using namespace xpc; michael@0: michael@0: using mozilla::dom::DestroyProtoAndIfaceCache; michael@0: using mozilla::dom::indexedDB::IndexedDatabaseManager; michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(SandboxPrivate) michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(SandboxPrivate) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(SandboxPrivate) michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SandboxPrivate) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIScriptObjectPrincipal) michael@0: NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal) michael@0: NS_INTERFACE_MAP_ENTRY(nsIGlobalObject) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: const char kScriptSecurityManagerContractID[] = NS_SCRIPTSECURITYMANAGER_CONTRACTID; michael@0: michael@0: class nsXPCComponents_utils_Sandbox : public nsIXPCComponents_utils_Sandbox, michael@0: public nsIXPCScriptable michael@0: { michael@0: public: michael@0: // Aren't macros nice? michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIXPCCOMPONENTS_UTILS_SANDBOX michael@0: NS_DECL_NSIXPCSCRIPTABLE michael@0: michael@0: public: michael@0: nsXPCComponents_utils_Sandbox(); michael@0: virtual ~nsXPCComponents_utils_Sandbox(); michael@0: michael@0: private: michael@0: static nsresult CallOrConstruct(nsIXPConnectWrappedNative *wrapper, michael@0: JSContext *cx, HandleObject obj, michael@0: const CallArgs &args, bool *_retval); michael@0: }; michael@0: michael@0: already_AddRefed michael@0: xpc::NewSandboxConstructor() michael@0: { michael@0: nsCOMPtr sbConstructor = michael@0: new nsXPCComponents_utils_Sandbox(); michael@0: return sbConstructor.forget(); michael@0: } michael@0: michael@0: static bool michael@0: SandboxDump(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() == 0) michael@0: return true; michael@0: michael@0: RootedString str(cx, ToString(cx, args[0])); michael@0: if (!str) michael@0: return false; michael@0: michael@0: size_t length; michael@0: const jschar *chars = JS_GetStringCharsZAndLength(cx, str, &length); michael@0: if (!chars) michael@0: return false; michael@0: michael@0: nsDependentString wstr(chars, length); michael@0: char *cstr = ToNewUTF8String(wstr); michael@0: if (!cstr) michael@0: return false; michael@0: michael@0: #if defined(XP_MACOSX) michael@0: // Be nice and convert all \r to \n. michael@0: char *c = cstr, *cEnd = cstr + strlen(cstr); michael@0: while (c < cEnd) { michael@0: if (*c == '\r') michael@0: *c = '\n'; michael@0: c++; michael@0: } michael@0: #endif michael@0: #ifdef ANDROID michael@0: __android_log_write(ANDROID_LOG_INFO, "GeckoDump", cstr); michael@0: #endif michael@0: michael@0: fputs(cstr, stdout); michael@0: fflush(stdout); michael@0: NS_Free(cstr); michael@0: args.rval().setBoolean(true); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: SandboxDebug(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: #ifdef DEBUG michael@0: return SandboxDump(cx, argc, vp); michael@0: #else michael@0: return true; michael@0: #endif michael@0: } michael@0: michael@0: static bool michael@0: SandboxImport(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() < 1 || args[0].isPrimitive()) { michael@0: XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx); michael@0: return false; michael@0: } michael@0: michael@0: RootedString funname(cx); michael@0: if (args.length() > 1) { michael@0: // Use the second parameter as the function name. michael@0: funname = ToString(cx, args[1]); michael@0: if (!funname) michael@0: return false; michael@0: } else { michael@0: // NB: funobj must only be used to get the JSFunction out. michael@0: RootedObject funobj(cx, &args[0].toObject()); michael@0: if (js::IsProxy(funobj)) { michael@0: funobj = XPCWrapper::UnsafeUnwrapSecurityWrapper(funobj); michael@0: } michael@0: michael@0: JSAutoCompartment ac(cx, funobj); michael@0: michael@0: RootedValue funval(cx, ObjectValue(*funobj)); michael@0: JSFunction *fun = JS_ValueToFunction(cx, funval); michael@0: if (!fun) { michael@0: XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx); michael@0: return false; michael@0: } michael@0: michael@0: // Use the actual function name as the name. michael@0: funname = JS_GetFunctionId(fun); michael@0: if (!funname) { michael@0: XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: RootedId id(cx); michael@0: if (!JS_StringToId(cx, funname, &id)) michael@0: return false; michael@0: michael@0: // We need to resolve the this object, because this function is used michael@0: // unbound and should still work and act on the original sandbox. michael@0: RootedObject thisObject(cx, JS_THIS_OBJECT(cx, vp)); michael@0: if (!thisObject) { michael@0: XPCThrower::Throw(NS_ERROR_UNEXPECTED, cx); michael@0: return false; michael@0: } michael@0: if (!JS_SetPropertyById(cx, thisObject, id, args[0])) michael@0: return false; michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: CreateXMLHttpRequest(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager(); michael@0: if (!ssm) michael@0: return false; michael@0: michael@0: nsIPrincipal *subjectPrincipal = ssm->GetCxSubjectPrincipal(cx); michael@0: if (!subjectPrincipal) michael@0: return false; michael@0: michael@0: RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); michael@0: MOZ_ASSERT(global); michael@0: michael@0: nsIScriptObjectPrincipal *sop = michael@0: static_cast(xpc_GetJSPrivate(global)); michael@0: nsCOMPtr iglobal = do_QueryInterface(sop); michael@0: michael@0: nsCOMPtr xhr = new nsXMLHttpRequest(); michael@0: nsresult rv = xhr->Init(subjectPrincipal, nullptr, iglobal, nullptr); michael@0: if (NS_FAILED(rv)) michael@0: return false; michael@0: michael@0: rv = nsContentUtils::WrapNative(cx, xhr, args.rval()); michael@0: if (NS_FAILED(rv)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: IsProxy(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() < 1) { michael@0: JS_ReportError(cx, "Function requires at least 1 argument"); michael@0: return false; michael@0: } michael@0: if (!args[0].isObject()) { michael@0: args.rval().setBoolean(false); michael@0: return true; michael@0: } michael@0: michael@0: RootedObject obj(cx, &args[0].toObject()); michael@0: obj = js::CheckedUnwrap(obj); michael@0: NS_ENSURE_TRUE(obj, false); michael@0: michael@0: args.rval().setBoolean(js::IsScriptedProxy(obj)); michael@0: return true; michael@0: } michael@0: michael@0: namespace xpc { michael@0: michael@0: bool michael@0: ExportFunction(JSContext *cx, HandleValue vfunction, HandleValue vscope, HandleValue voptions, michael@0: MutableHandleValue rval) michael@0: { michael@0: bool hasOptions = !voptions.isUndefined(); michael@0: if (!vscope.isObject() || !vfunction.isObject() || (hasOptions && !voptions.isObject())) { michael@0: JS_ReportError(cx, "Invalid argument"); michael@0: return false; michael@0: } michael@0: michael@0: RootedObject funObj(cx, &vfunction.toObject()); michael@0: RootedObject targetScope(cx, &vscope.toObject()); michael@0: ExportOptions options(cx, hasOptions ? &voptions.toObject() : nullptr); michael@0: if (hasOptions && !options.Parse()) michael@0: return false; michael@0: michael@0: // We can only export functions to scopes those are transparent for us, michael@0: // so if there is a security wrapper around targetScope we must throw. michael@0: targetScope = CheckedUnwrap(targetScope); michael@0: if (!targetScope) { michael@0: JS_ReportError(cx, "Permission denied to export function into scope"); michael@0: return false; michael@0: } michael@0: michael@0: if (js::IsScriptedProxy(targetScope)) { michael@0: JS_ReportError(cx, "Defining property on proxy object is not allowed"); michael@0: return false; michael@0: } michael@0: michael@0: { michael@0: // We need to operate in the target scope from here on, let's enter michael@0: // its compartment. michael@0: JSAutoCompartment ac(cx, targetScope); michael@0: michael@0: // Unwrapping to see if we have a callable. michael@0: funObj = UncheckedUnwrap(funObj); michael@0: if (!JS_ObjectIsCallable(cx, funObj)) { michael@0: JS_ReportError(cx, "First argument must be a function"); michael@0: return false; michael@0: } michael@0: michael@0: RootedId id(cx, options.defineAs); michael@0: if (JSID_IS_VOID(id)) { michael@0: // If there wasn't any function name specified, michael@0: // copy the name from the function being imported. michael@0: JSFunction *fun = JS_GetObjectFunction(funObj); michael@0: RootedString funName(cx, JS_GetFunctionId(fun)); michael@0: if (!funName) michael@0: funName = JS_InternString(cx, ""); michael@0: michael@0: if (!JS_StringToId(cx, funName, &id)) michael@0: return false; michael@0: } michael@0: MOZ_ASSERT(JSID_IS_STRING(id)); michael@0: michael@0: // The function forwarder will live in the target compartment. Since michael@0: // this function will be referenced from its private slot, to avoid a michael@0: // GC hazard, we must wrap it to the same compartment. michael@0: if (!JS_WrapObject(cx, &funObj)) michael@0: return false; michael@0: michael@0: // And now, let's create the forwarder function in the target compartment michael@0: // for the function the be exported. michael@0: if (!NewFunctionForwarder(cx, id, funObj, /* doclone = */ true, rval)) { michael@0: JS_ReportError(cx, "Exporting function failed"); michael@0: return false; michael@0: } michael@0: michael@0: // We have the forwarder function in the target compartment. If michael@0: // defineAs was set, we also need to define it as a property on michael@0: // the target. michael@0: if (!JSID_IS_VOID(options.defineAs)) { michael@0: if (!JS_DefinePropertyById(cx, targetScope, id, rval, michael@0: JS_PropertyStub, JS_StrictPropertyStub, michael@0: JSPROP_ENUMERATE)) { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Finally we have to re-wrap the exported function back to the caller compartment. michael@0: if (!JS_WrapValue(cx, rval)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Expected type of the arguments and the return value: michael@0: * function exportFunction(function funToExport, michael@0: * object targetScope, michael@0: * [optional] object options) michael@0: */ michael@0: static bool michael@0: ExportFunction(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() < 2) { michael@0: JS_ReportError(cx, "Function requires at least 2 arguments"); michael@0: return false; michael@0: } michael@0: michael@0: RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue()); michael@0: return ExportFunction(cx, args[0], args[1], options, args.rval()); michael@0: } michael@0: } /* namespace xpc */ michael@0: michael@0: static bool michael@0: GetFilenameAndLineNumber(JSContext *cx, nsACString &filename, unsigned &lineno) michael@0: { michael@0: JS::AutoFilename scriptFilename; michael@0: if (JS::DescribeScriptedCaller(cx, &scriptFilename, &lineno)) { michael@0: if (const char *cfilename = scriptFilename.get()) { michael@0: filename.Assign(nsDependentCString(cfilename)); michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: xpc::IsReflector(JSObject *obj) michael@0: { michael@0: return IS_WN_REFLECTOR(obj) || dom::IsDOMObject(obj); michael@0: } michael@0: michael@0: enum ForwarderCloneTags { michael@0: SCTAG_BASE = JS_SCTAG_USER_MIN, michael@0: SCTAG_REFLECTOR michael@0: }; michael@0: michael@0: static JSObject * michael@0: CloneNonReflectorsRead(JSContext *cx, JSStructuredCloneReader *reader, uint32_t tag, michael@0: uint32_t data, void *closure) michael@0: { michael@0: MOZ_ASSERT(closure, "Null pointer!"); michael@0: AutoObjectVector *reflectors = static_cast(closure); michael@0: if (tag == SCTAG_REFLECTOR) { michael@0: MOZ_ASSERT(!data); michael@0: michael@0: size_t idx; michael@0: if (JS_ReadBytes(reader, &idx, sizeof(size_t))) { michael@0: RootedObject reflector(cx, reflectors->handleAt(idx)); michael@0: MOZ_ASSERT(reflector, "No object pointer?"); michael@0: MOZ_ASSERT(IsReflector(reflector), "Object pointer must be a reflector!"); michael@0: michael@0: if (!JS_WrapObject(cx, &reflector)) michael@0: return nullptr; michael@0: MOZ_ASSERT(WrapperFactory::IsXrayWrapper(reflector) || michael@0: IsReflector(reflector)); michael@0: michael@0: return reflector; michael@0: } michael@0: } michael@0: michael@0: JS_ReportError(cx, "CloneNonReflectorsRead error"); michael@0: return nullptr; michael@0: } michael@0: michael@0: static bool michael@0: CloneNonReflectorsWrite(JSContext *cx, JSStructuredCloneWriter *writer, michael@0: Handle obj, void *closure) michael@0: { michael@0: MOZ_ASSERT(closure, "Null pointer!"); michael@0: michael@0: // We need to maintain a list of reflectors to make sure all these objects michael@0: // are properly rooter. Only their indices will be serialized. michael@0: AutoObjectVector *reflectors = static_cast(closure); michael@0: if (IsReflector(obj)) { michael@0: if (!reflectors->append(obj)) michael@0: return false; michael@0: michael@0: size_t idx = reflectors->length()-1; michael@0: if (JS_WriteUint32Pair(writer, SCTAG_REFLECTOR, 0) && michael@0: JS_WriteBytes(writer, &idx, sizeof(size_t))) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: JS_ReportError(cx, "CloneNonReflectorsWrite error"); michael@0: return false; michael@0: } michael@0: michael@0: static const JSStructuredCloneCallbacks gForwarderStructuredCloneCallbacks = { michael@0: CloneNonReflectorsRead, michael@0: CloneNonReflectorsWrite, michael@0: nullptr, michael@0: nullptr, michael@0: nullptr, michael@0: nullptr michael@0: }; michael@0: michael@0: /* michael@0: * This is a special structured cloning, that clones only non-reflectors. michael@0: * The function assumes the cx is already entered the compartment we want michael@0: * to clone to, and that if val is an object is from the compartment we michael@0: * clone from. michael@0: */ michael@0: static bool michael@0: CloneNonReflectors(JSContext *cx, MutableHandleValue val) michael@0: { michael@0: JSAutoStructuredCloneBuffer buffer; michael@0: AutoObjectVector rootedReflectors(cx); michael@0: { michael@0: // For parsing val we have to enter its compartment. michael@0: // (unless it's a primitive) michael@0: Maybe ac; michael@0: if (val.isObject()) { michael@0: ac.construct(cx, &val.toObject()); michael@0: } else if (val.isString() && !JS_WrapValue(cx, val)) { michael@0: return false; michael@0: } michael@0: michael@0: if (!buffer.write(cx, val, michael@0: &gForwarderStructuredCloneCallbacks, michael@0: &rootedReflectors)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Now recreate the clones in the target compartment. michael@0: if (!buffer.read(cx, val, michael@0: &gForwarderStructuredCloneCallbacks, michael@0: &rootedReflectors)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: namespace xpc { michael@0: michael@0: bool michael@0: EvalInWindow(JSContext *cx, const nsAString &source, HandleObject scope, MutableHandleValue rval) michael@0: { michael@0: // If we cannot unwrap we must not eval in it. michael@0: RootedObject targetScope(cx, CheckedUnwrap(scope)); michael@0: if (!targetScope) { michael@0: JS_ReportError(cx, "Permission denied to eval in target scope"); michael@0: return false; michael@0: } michael@0: michael@0: // Make sure that we have a window object. michael@0: RootedObject inner(cx, CheckedUnwrap(targetScope, /* stopAtOuter = */ false)); michael@0: nsCOMPtr global; michael@0: nsCOMPtr window; michael@0: if (!JS_IsGlobalObject(inner) || michael@0: !(global = GetNativeForGlobal(inner)) || michael@0: !(window = do_QueryInterface(global))) michael@0: { michael@0: JS_ReportError(cx, "Second argument must be a window"); michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr context = michael@0: (static_cast(window.get()))->GetScriptContext(); michael@0: if (!context) { michael@0: JS_ReportError(cx, "Script context needed"); michael@0: return false; michael@0: } michael@0: michael@0: nsCString filename; michael@0: unsigned lineNo; michael@0: if (!GetFilenameAndLineNumber(cx, filename, lineNo)) { michael@0: // Default values for non-scripted callers. michael@0: filename.Assign("Unknown"); michael@0: lineNo = 0; michael@0: } michael@0: michael@0: RootedObject cxGlobal(cx, JS::CurrentGlobalOrNull(cx)); michael@0: { michael@0: // CompileOptions must be created from the context michael@0: // we will execute this script in. michael@0: JSContext *wndCx = context->GetNativeContext(); michael@0: AutoCxPusher pusher(wndCx); michael@0: JS::CompileOptions compileOptions(wndCx); michael@0: compileOptions.setFileAndLine(filename.get(), lineNo); michael@0: michael@0: // We don't want the JS engine to automatically report michael@0: // uncaught exceptions. michael@0: nsJSUtils::EvaluateOptions evaluateOptions; michael@0: evaluateOptions.setReportUncaught(false); michael@0: michael@0: nsresult rv = nsJSUtils::EvaluateString(wndCx, michael@0: source, michael@0: targetScope, michael@0: compileOptions, michael@0: evaluateOptions, michael@0: rval); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: // If there was an exception we get it as a return value, if michael@0: // the evaluation failed for some other reason, then a default michael@0: // exception is raised. michael@0: MOZ_ASSERT(!JS_IsExceptionPending(wndCx), michael@0: "Exception should be delivered as return value."); michael@0: if (rval.isUndefined()) { michael@0: MOZ_ASSERT(rv == NS_ERROR_OUT_OF_MEMORY); michael@0: return false; michael@0: } michael@0: michael@0: // If there was an exception thrown we should set it michael@0: // on the calling context. michael@0: RootedValue exn(wndCx, rval); michael@0: // First we should reset the return value. michael@0: rval.set(UndefinedValue()); michael@0: michael@0: // Then clone the exception. michael@0: JSAutoCompartment ac(wndCx, cxGlobal); michael@0: if (CloneNonReflectors(wndCx, &exn)) michael@0: js::SetPendingExceptionCrossContext(cx, exn); michael@0: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Let's clone the return value back to the callers compartment. michael@0: if (!CloneNonReflectors(cx, rval)) { michael@0: rval.set(UndefinedValue()); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Expected type of the arguments: michael@0: * value evalInWindow(string script, michael@0: * object window) michael@0: */ michael@0: static bool michael@0: EvalInWindow(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() < 2) { michael@0: JS_ReportError(cx, "Function requires two arguments"); michael@0: return false; michael@0: } michael@0: michael@0: if (!args[0].isString() || !args[1].isObject()) { michael@0: JS_ReportError(cx, "Invalid arguments"); michael@0: return false; michael@0: } michael@0: michael@0: RootedString srcString(cx, args[0].toString()); michael@0: RootedObject targetScope(cx, &args[1].toObject()); michael@0: michael@0: nsDependentJSString srcDepString; michael@0: if (!srcDepString.init(cx, srcString)) { michael@0: JS_ReportError(cx, "Source string is invalid"); michael@0: return false; michael@0: } michael@0: michael@0: return EvalInWindow(cx, srcDepString, targetScope, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: CreateObjectIn(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() < 1) { michael@0: JS_ReportError(cx, "Function requires at least 1 argument"); michael@0: return false; michael@0: } michael@0: michael@0: RootedObject optionsObj(cx); michael@0: bool calledWithOptions = args.length() > 1; michael@0: if (calledWithOptions) { michael@0: if (!args[1].isObject()) { michael@0: JS_ReportError(cx, "Expected the 2nd argument (options) to be an object"); michael@0: return false; michael@0: } michael@0: optionsObj = &args[1].toObject(); michael@0: } michael@0: michael@0: CreateObjectInOptions options(cx, optionsObj); michael@0: if (calledWithOptions && !options.Parse()) michael@0: return false; michael@0: michael@0: return xpc::CreateObjectIn(cx, args[0], options, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: CloneInto(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() < 2) { michael@0: JS_ReportError(cx, "Function requires at least 2 arguments"); michael@0: return false; michael@0: } michael@0: michael@0: RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue()); michael@0: return xpc::CloneInto(cx, args[0], args[1], options, args.rval()); michael@0: } michael@0: michael@0: } /* namespace xpc */ michael@0: michael@0: static bool michael@0: sandbox_enumerate(JSContext *cx, HandleObject obj) michael@0: { michael@0: return JS_EnumerateStandardClasses(cx, obj); michael@0: } michael@0: michael@0: static bool michael@0: sandbox_resolve(JSContext *cx, HandleObject obj, HandleId id) michael@0: { michael@0: bool resolved; michael@0: return JS_ResolveStandardClass(cx, obj, id, &resolved); michael@0: } michael@0: michael@0: static void michael@0: sandbox_finalize(JSFreeOp *fop, JSObject *obj) michael@0: { michael@0: nsIScriptObjectPrincipal *sop = michael@0: static_cast(xpc_GetJSPrivate(obj)); michael@0: MOZ_ASSERT(sop); michael@0: static_cast(sop)->ForgetGlobalObject(); michael@0: NS_IF_RELEASE(sop); michael@0: DestroyProtoAndIfaceCache(obj); michael@0: } michael@0: michael@0: static bool michael@0: sandbox_convert(JSContext *cx, HandleObject obj, JSType type, MutableHandleValue vp) michael@0: { michael@0: if (type == JSTYPE_OBJECT) { michael@0: vp.set(OBJECT_TO_JSVAL(obj)); michael@0: return true; michael@0: } michael@0: michael@0: return JS_ConvertStub(cx, obj, type, vp); michael@0: } michael@0: michael@0: #define XPCONNECT_SANDBOX_CLASS_METADATA_SLOT (XPCONNECT_GLOBAL_EXTRA_SLOT_OFFSET) michael@0: michael@0: static const JSClass SandboxClass = { michael@0: "Sandbox", michael@0: XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1), michael@0: JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, michael@0: sandbox_enumerate, sandbox_resolve, sandbox_convert, sandbox_finalize, michael@0: nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook michael@0: }; michael@0: michael@0: static const JSFunctionSpec SandboxFunctions[] = { michael@0: JS_FS("dump", SandboxDump, 1,0), michael@0: JS_FS("debug", SandboxDebug, 1,0), michael@0: JS_FS("importFunction", SandboxImport, 1,0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: bool michael@0: xpc::IsSandbox(JSObject *obj) michael@0: { michael@0: return GetObjectJSClass(obj) == &SandboxClass; michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: nsXPCComponents_utils_Sandbox::nsXPCComponents_utils_Sandbox() michael@0: { michael@0: } michael@0: michael@0: nsXPCComponents_utils_Sandbox::~nsXPCComponents_utils_Sandbox() michael@0: { michael@0: } michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsXPCComponents_utils_Sandbox) michael@0: NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_utils_Sandbox) michael@0: NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_utils_Sandbox) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_ADDREF(nsXPCComponents_utils_Sandbox) michael@0: NS_IMPL_RELEASE(nsXPCComponents_utils_Sandbox) michael@0: michael@0: // We use the nsIXPScriptable macros to generate lots of stuff for us. michael@0: #define XPC_MAP_CLASSNAME nsXPCComponents_utils_Sandbox michael@0: #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_utils_Sandbox" michael@0: #define XPC_MAP_WANT_CALL michael@0: #define XPC_MAP_WANT_CONSTRUCT michael@0: #define XPC_MAP_FLAGS 0 michael@0: #include "xpc_map_end.h" /* This #undef's the above. */ michael@0: michael@0: xpc::SandboxProxyHandler xpc::sandboxProxyHandler; michael@0: michael@0: bool michael@0: xpc::IsSandboxPrototypeProxy(JSObject *obj) michael@0: { michael@0: return js::IsProxy(obj) && michael@0: js::GetProxyHandler(obj) == &xpc::sandboxProxyHandler; michael@0: } michael@0: michael@0: bool michael@0: xpc::SandboxCallableProxyHandler::call(JSContext *cx, JS::Handle proxy, michael@0: const JS::CallArgs &args) michael@0: { michael@0: // We forward the call to our underlying callable. michael@0: michael@0: // The parent of our proxy is the SandboxProxyHandler proxy michael@0: RootedObject sandboxProxy(cx, JS_GetParent(proxy)); michael@0: MOZ_ASSERT(js::IsProxy(sandboxProxy) && michael@0: js::GetProxyHandler(sandboxProxy) == &xpc::sandboxProxyHandler); michael@0: michael@0: // The parent of the sandboxProxy is the sandbox global, and the michael@0: // target object is the original proto. michael@0: RootedObject sandboxGlobal(cx, JS_GetParent(sandboxProxy)); michael@0: MOZ_ASSERT(js::GetObjectJSClass(sandboxGlobal) == &SandboxClass); michael@0: michael@0: // If our this object is the sandbox global, we call with this set to the michael@0: // original proto instead. michael@0: // michael@0: // There are two different ways we can compute |this|. If we use michael@0: // JS_THIS_VALUE, we'll get the bonafide |this| value as passed by the michael@0: // caller, which may be undefined if a global function was invoked without michael@0: // an explicit invocant. If we use JS_THIS or JS_THIS_OBJECT, the |this| michael@0: // in |vp| will be coerced to the global, which is not the correct michael@0: // behavior in ES5 strict mode. And we have no way to compute strictness michael@0: // here. michael@0: // michael@0: // The naive approach is simply to use JS_THIS_VALUE here. If |this| was michael@0: // explicit, we can remap it appropriately. If it was implicit, then we michael@0: // leave it as undefined, and let the callee sort it out. Since the callee michael@0: // is generally in the same compartment as its global (eg the Window's michael@0: // compartment, not the Sandbox's), the callee will generally compute the michael@0: // correct |this|. michael@0: // michael@0: // However, this breaks down in the Xray case. If the sandboxPrototype michael@0: // is an Xray wrapper, then we'll end up reifying the native methods in michael@0: // the Sandbox's scope, which means that they'll compute |this| to be the michael@0: // Sandbox, breaking old-style XPC_WN_CallMethod methods. michael@0: // michael@0: // Luckily, the intent of Xrays is to provide a vanilla view of a foreign michael@0: // DOM interface, which means that we don't care about script-enacted michael@0: // strictness in the prototype's home compartment. Indeed, since DOM michael@0: // methods are always non-strict, we can just assume non-strict semantics michael@0: // if the sandboxPrototype is an Xray Wrapper, which lets us appropriately michael@0: // remap |this|. michael@0: bool isXray = WrapperFactory::IsXrayWrapper(sandboxProxy); michael@0: RootedValue thisVal(cx, isXray ? args.computeThis(cx) : args.thisv()); michael@0: if (thisVal == ObjectValue(*sandboxGlobal)) { michael@0: thisVal = ObjectValue(*js::GetProxyTargetObject(sandboxProxy)); michael@0: } michael@0: michael@0: RootedValue func(cx, js::GetProxyPrivate(proxy)); michael@0: return JS::Call(cx, thisVal, func, args, args.rval()); michael@0: } michael@0: michael@0: xpc::SandboxCallableProxyHandler xpc::sandboxCallableProxyHandler; michael@0: michael@0: /* michael@0: * Wrap a callable such that if we're called with oldThisObj as the michael@0: * "this" we will instead call it with newThisObj as the this. michael@0: */ michael@0: static JSObject* michael@0: WrapCallable(JSContext *cx, JSObject *callable, JSObject *sandboxProtoProxy) michael@0: { michael@0: MOZ_ASSERT(JS_ObjectIsCallable(cx, callable)); michael@0: // Our proxy is wrapping the callable. So we need to use the michael@0: // callable as the private. We use the given sandboxProtoProxy as michael@0: // the parent, and our call() hook depends on that. michael@0: MOZ_ASSERT(js::IsProxy(sandboxProtoProxy) && michael@0: js::GetProxyHandler(sandboxProtoProxy) == michael@0: &xpc::sandboxProxyHandler); michael@0: michael@0: RootedValue priv(cx, ObjectValue(*callable)); michael@0: js::ProxyOptions options; michael@0: options.selectDefaultClass(true); michael@0: return js::NewProxyObject(cx, &xpc::sandboxCallableProxyHandler, michael@0: priv, nullptr, michael@0: sandboxProtoProxy, options); michael@0: } michael@0: michael@0: template michael@0: bool BindPropertyOp(JSContext *cx, Op &op, JSPropertyDescriptor *desc, HandleId id, michael@0: unsigned attrFlag, HandleObject sandboxProtoProxy) michael@0: { michael@0: if (!op) { michael@0: return true; michael@0: } michael@0: michael@0: RootedObject func(cx); michael@0: if (desc->attrs & attrFlag) { michael@0: // Already an object michael@0: func = JS_FUNC_TO_DATA_PTR(JSObject *, op); michael@0: } else { michael@0: // We have an actual property op. For getters, we use 0 michael@0: // args, for setters we use 1 arg. michael@0: uint32_t args = (attrFlag == JSPROP_GETTER) ? 0 : 1; michael@0: RootedObject obj(cx, desc->obj); michael@0: func = GeneratePropertyOp(cx, obj, id, args, op); michael@0: if (!func) michael@0: return false; michael@0: } michael@0: func = WrapCallable(cx, func, sandboxProtoProxy); michael@0: if (!func) michael@0: return false; michael@0: op = JS_DATA_TO_FUNC_PTR(Op, func.get()); michael@0: desc->attrs |= attrFlag; michael@0: return true; michael@0: } michael@0: michael@0: extern bool michael@0: XPC_WN_Helper_GetProperty(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp); michael@0: extern bool michael@0: XPC_WN_Helper_SetProperty(JSContext *cx, HandleObject obj, HandleId id, bool strict, MutableHandleValue vp); michael@0: michael@0: bool michael@0: xpc::SandboxProxyHandler::getPropertyDescriptor(JSContext *cx, michael@0: JS::Handle proxy, michael@0: JS::Handle id, michael@0: JS::MutableHandle desc) michael@0: { michael@0: JS::RootedObject obj(cx, wrappedObject(proxy)); michael@0: michael@0: MOZ_ASSERT(js::GetObjectCompartment(obj) == js::GetObjectCompartment(proxy)); michael@0: if (!JS_GetPropertyDescriptorById(cx, obj, id, desc)) michael@0: return false; michael@0: michael@0: if (!desc.object()) michael@0: return true; // No property, nothing to do michael@0: michael@0: // Now fix up the getter/setter/value as needed to be bound to desc->obj michael@0: // Don't mess with holder_get and holder_set, though, because those rely on michael@0: // the "vp is prefilled with the value in the slot" behavior that property michael@0: // ops can in theory rely on, but our property op forwarder doesn't know how michael@0: // to make that happen. Since we really only need to rebind the DOM methods michael@0: // here, not rebindings holder_get and holder_set is OK. michael@0: // michael@0: // Similarly, don't mess with XPC_WN_Helper_GetProperty and michael@0: // XPC_WN_Helper_SetProperty, for the same reasons: that could confuse our michael@0: // access to expandos when we're not doing Xrays. michael@0: if (desc.getter() != xpc::holder_get && michael@0: desc.getter() != XPC_WN_Helper_GetProperty && michael@0: !BindPropertyOp(cx, desc.getter(), desc.address(), id, JSPROP_GETTER, proxy)) michael@0: return false; michael@0: if (desc.setter() != xpc::holder_set && michael@0: desc.setter() != XPC_WN_Helper_SetProperty && michael@0: !BindPropertyOp(cx, desc.setter(), desc.address(), id, JSPROP_SETTER, proxy)) michael@0: return false; michael@0: if (desc.value().isObject()) { michael@0: JSObject* val = &desc.value().toObject(); michael@0: if (JS_ObjectIsCallable(cx, val)) { michael@0: val = WrapCallable(cx, val, proxy); michael@0: if (!val) michael@0: return false; michael@0: desc.value().setObject(*val); michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: xpc::SandboxProxyHandler::getOwnPropertyDescriptor(JSContext *cx, michael@0: JS::Handle proxy, michael@0: JS::Handle id, michael@0: JS::MutableHandle desc) michael@0: { michael@0: if (!getPropertyDescriptor(cx, proxy, id, desc)) michael@0: return false; michael@0: michael@0: if (desc.object() != wrappedObject(proxy)) michael@0: desc.object().set(nullptr); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Reuse the BaseProxyHandler versions of the derived traps that are implemented michael@0: * in terms of the fundamental traps. michael@0: */ michael@0: michael@0: bool michael@0: xpc::SandboxProxyHandler::has(JSContext *cx, JS::Handle proxy, michael@0: JS::Handle id, bool *bp) michael@0: { michael@0: return BaseProxyHandler::has(cx, proxy, id, bp); michael@0: } michael@0: bool michael@0: xpc::SandboxProxyHandler::hasOwn(JSContext *cx, JS::Handle proxy, michael@0: JS::Handle id, bool *bp) michael@0: { michael@0: return BaseProxyHandler::hasOwn(cx, proxy, id, bp); michael@0: } michael@0: michael@0: bool michael@0: xpc::SandboxProxyHandler::get(JSContext *cx, JS::Handle proxy, michael@0: JS::Handle receiver, michael@0: JS::Handle id, michael@0: JS::MutableHandle vp) michael@0: { michael@0: return BaseProxyHandler::get(cx, proxy, receiver, id, vp); michael@0: } michael@0: michael@0: bool michael@0: xpc::SandboxProxyHandler::set(JSContext *cx, JS::Handle proxy, michael@0: JS::Handle receiver, michael@0: JS::Handle id, michael@0: bool strict, michael@0: JS::MutableHandle vp) michael@0: { michael@0: return BaseProxyHandler::set(cx, proxy, receiver, id, strict, vp); michael@0: } michael@0: michael@0: bool michael@0: xpc::SandboxProxyHandler::keys(JSContext *cx, JS::Handle proxy, michael@0: AutoIdVector &props) michael@0: { michael@0: return BaseProxyHandler::keys(cx, proxy, props); michael@0: } michael@0: michael@0: bool michael@0: xpc::SandboxProxyHandler::iterate(JSContext *cx, JS::Handle proxy, michael@0: unsigned flags, JS::MutableHandle vp) michael@0: { michael@0: return BaseProxyHandler::iterate(cx, proxy, flags, vp); michael@0: } michael@0: michael@0: bool michael@0: xpc::GlobalProperties::Parse(JSContext *cx, JS::HandleObject obj) michael@0: { michael@0: MOZ_ASSERT(JS_IsArrayObject(cx, obj)); michael@0: michael@0: uint32_t length; michael@0: bool ok = JS_GetArrayLength(cx, obj, &length); michael@0: NS_ENSURE_TRUE(ok, false); michael@0: bool promise = Promise; michael@0: for (uint32_t i = 0; i < length; i++) { michael@0: RootedValue nameValue(cx); michael@0: ok = JS_GetElement(cx, obj, i, &nameValue); michael@0: NS_ENSURE_TRUE(ok, false); michael@0: if (!nameValue.isString()) { michael@0: JS_ReportError(cx, "Property names must be strings"); michael@0: return false; michael@0: } michael@0: JSAutoByteString name(cx, nameValue.toString()); michael@0: NS_ENSURE_TRUE(name, false); michael@0: if (promise && !strcmp(name.ptr(), "-Promise")) { michael@0: Promise = false; michael@0: } else if (!strcmp(name.ptr(), "indexedDB")) { michael@0: indexedDB = true; michael@0: } else if (!strcmp(name.ptr(), "XMLHttpRequest")) { michael@0: XMLHttpRequest = true; michael@0: } else if (!strcmp(name.ptr(), "TextEncoder")) { michael@0: TextEncoder = true; michael@0: } else if (!strcmp(name.ptr(), "TextDecoder")) { michael@0: TextDecoder = true; michael@0: } else if (!strcmp(name.ptr(), "URL")) { michael@0: URL = true; michael@0: } else if (!strcmp(name.ptr(), "atob")) { michael@0: atob = true; michael@0: } else if (!strcmp(name.ptr(), "btoa")) { michael@0: btoa = true; michael@0: } else { michael@0: JS_ReportError(cx, "Unknown property name: %s", name.ptr()); michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: xpc::GlobalProperties::Define(JSContext *cx, JS::HandleObject obj) michael@0: { michael@0: if (Promise && !dom::PromiseBinding::GetConstructorObject(cx, obj)) michael@0: return false; michael@0: michael@0: if (indexedDB && AccessCheck::isChrome(obj) && michael@0: !IndexedDatabaseManager::DefineIndexedDB(cx, obj)) michael@0: return false; michael@0: michael@0: if (XMLHttpRequest && michael@0: !JS_DefineFunction(cx, obj, "XMLHttpRequest", CreateXMLHttpRequest, 0, JSFUN_CONSTRUCTOR)) michael@0: return false; michael@0: michael@0: if (TextEncoder && michael@0: !dom::TextEncoderBinding::GetConstructorObject(cx, obj)) michael@0: return false; michael@0: michael@0: if (TextDecoder && michael@0: !dom::TextDecoderBinding::GetConstructorObject(cx, obj)) michael@0: return false; michael@0: michael@0: if (URL && michael@0: !dom::URLBinding::GetConstructorObject(cx, obj)) michael@0: return false; michael@0: michael@0: if (atob && michael@0: !JS_DefineFunction(cx, obj, "atob", Atob, 1, 0)) michael@0: return false; michael@0: michael@0: if (btoa && michael@0: !JS_DefineFunction(cx, obj, "btoa", Btoa, 1, 0)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: xpc::CreateSandboxObject(JSContext *cx, MutableHandleValue vp, nsISupports *prinOrSop, michael@0: SandboxOptions& options) michael@0: { michael@0: // Create the sandbox global object michael@0: nsresult rv; michael@0: nsCOMPtr xpc(do_GetService(nsIXPConnect::GetCID(), &rv)); michael@0: if (NS_FAILED(rv)) michael@0: return NS_ERROR_XPC_UNEXPECTED; michael@0: michael@0: nsCOMPtr principal = do_QueryInterface(prinOrSop); michael@0: if (!principal) { michael@0: nsCOMPtr sop = do_QueryInterface(prinOrSop); michael@0: if (sop) { michael@0: principal = sop->GetPrincipal(); michael@0: } else { michael@0: principal = do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); michael@0: MOZ_ASSERT(NS_FAILED(rv) || principal, "Bad return from do_CreateInstance"); michael@0: michael@0: if (!principal || NS_FAILED(rv)) { michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: } michael@0: MOZ_ASSERT(principal); michael@0: } michael@0: michael@0: JS::CompartmentOptions compartmentOptions; michael@0: if (options.sameZoneAs) michael@0: compartmentOptions.setSameZoneAs(js::UncheckedUnwrap(options.sameZoneAs)); michael@0: else michael@0: compartmentOptions.setZone(JS::SystemZone); michael@0: michael@0: compartmentOptions.setInvisibleToDebugger(options.invisibleToDebugger) michael@0: .setDiscardSource(options.discardSource) michael@0: .setTrace(TraceXPCGlobal); michael@0: michael@0: RootedObject sandbox(cx, xpc::CreateGlobalObject(cx, &SandboxClass, michael@0: principal, compartmentOptions)); michael@0: if (!sandbox) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Set up the wantXrays flag, which indicates whether xrays are desired even michael@0: // for same-origin access. michael@0: // michael@0: // This flag has historically been ignored for chrome sandboxes due to michael@0: // quirks in the wrapping implementation that have now been removed. Indeed, michael@0: // same-origin Xrays for chrome->chrome access seems a bit superfluous. michael@0: // Arguably we should just flip the default for chrome and still honor the michael@0: // flag, but such a change would break code in subtle ways for minimal michael@0: // benefit. So we just switch it off here. michael@0: xpc::GetCompartmentPrivate(sandbox)->wantXrays = michael@0: AccessCheck::isChrome(sandbox) ? false : options.wantXrays; michael@0: michael@0: { michael@0: JSAutoCompartment ac(cx, sandbox); michael@0: michael@0: if (options.proto) { michael@0: bool ok = JS_WrapObject(cx, &options.proto); michael@0: if (!ok) michael@0: return NS_ERROR_XPC_UNEXPECTED; michael@0: michael@0: if (xpc::WrapperFactory::IsXrayWrapper(options.proto) && !options.wantXrays) { michael@0: RootedValue v(cx, ObjectValue(*options.proto)); michael@0: if (!xpc::WrapperFactory::WaiveXrayAndWrap(cx, &v)) michael@0: return NS_ERROR_FAILURE; michael@0: options.proto = &v.toObject(); michael@0: } michael@0: michael@0: // Now check what sort of thing we've got in |proto| michael@0: JSObject *unwrappedProto = js::UncheckedUnwrap(options.proto, false); michael@0: const js::Class *unwrappedClass = js::GetObjectClass(unwrappedProto); michael@0: if (IS_WN_CLASS(unwrappedClass) || michael@0: mozilla::dom::IsDOMClass(Jsvalify(unwrappedClass))) { michael@0: // Wrap it up in a proxy that will do the right thing in terms michael@0: // of this-binding for methods. michael@0: RootedValue priv(cx, ObjectValue(*options.proto)); michael@0: options.proto = js::NewProxyObject(cx, &xpc::sandboxProxyHandler, michael@0: priv, nullptr, sandbox); michael@0: if (!options.proto) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: ok = JS_SetPrototype(cx, sandbox, options.proto); michael@0: if (!ok) michael@0: return NS_ERROR_XPC_UNEXPECTED; michael@0: } michael@0: michael@0: nsCOMPtr sbp = michael@0: new SandboxPrivate(principal, sandbox); michael@0: michael@0: // Pass on ownership of sbp to |sandbox|. michael@0: JS_SetPrivate(sandbox, sbp.forget().take()); michael@0: michael@0: bool allowComponents = nsContentUtils::IsSystemPrincipal(principal) || michael@0: nsContentUtils::IsExpandedPrincipal(principal); michael@0: if (options.wantComponents && allowComponents && michael@0: !GetObjectScope(sandbox)->AttachComponentsObject(cx)) michael@0: return NS_ERROR_XPC_UNEXPECTED; michael@0: michael@0: if (!XPCNativeWrapper::AttachNewConstructorObject(cx, sandbox)) michael@0: return NS_ERROR_XPC_UNEXPECTED; michael@0: michael@0: if (!JS_DefineFunctions(cx, sandbox, SandboxFunctions)) michael@0: return NS_ERROR_XPC_UNEXPECTED; michael@0: michael@0: if (options.wantExportHelpers && michael@0: (!JS_DefineFunction(cx, sandbox, "exportFunction", ExportFunction, 3, 0) || michael@0: !JS_DefineFunction(cx, sandbox, "evalInWindow", EvalInWindow, 2, 0) || michael@0: !JS_DefineFunction(cx, sandbox, "createObjectIn", CreateObjectIn, 2, 0) || michael@0: !JS_DefineFunction(cx, sandbox, "cloneInto", CloneInto, 3, 0) || michael@0: !JS_DefineFunction(cx, sandbox, "isProxy", IsProxy, 1, 0))) michael@0: return NS_ERROR_XPC_UNEXPECTED; michael@0: michael@0: if (!options.globalProperties.Define(cx, sandbox)) michael@0: return NS_ERROR_XPC_UNEXPECTED; michael@0: } michael@0: michael@0: michael@0: // We have this crazy behavior where wantXrays=false also implies that the michael@0: // returned sandbox is implicitly waived. We've stopped advertising it, but michael@0: // keep supporting it for now. michael@0: vp.setObject(*sandbox); michael@0: if (options.wantXrays && !JS_WrapValue(cx, vp)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: if (!options.wantXrays && !xpc::WrapperFactory::WaiveXrayAndWrap(cx, vp)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: // Set the location information for the new global, so that tools like michael@0: // about:memory may use that information michael@0: xpc::SetLocationForGlobal(sandbox, options.sandboxName); michael@0: michael@0: xpc::SetSandboxMetadata(cx, sandbox, options.metadata); michael@0: michael@0: JS_FireOnNewGlobalObject(cx, sandbox); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* bool call(in nsIXPConnectWrappedNative wrapper, michael@0: * in JSContextPtr cx, michael@0: * in JSObjectPtr obj, michael@0: * in uint32_t argc, michael@0: * in JSValPtr argv, michael@0: * in JSValPtr vp); michael@0: */ michael@0: NS_IMETHODIMP michael@0: nsXPCComponents_utils_Sandbox::Call(nsIXPConnectWrappedNative *wrapper, JSContext *cx, michael@0: JSObject *objArg, const CallArgs &args, bool *_retval) michael@0: { michael@0: RootedObject obj(cx, objArg); michael@0: return CallOrConstruct(wrapper, cx, obj, args, _retval); michael@0: } michael@0: michael@0: /* bool construct(in nsIXPConnectWrappedNative wrapper, michael@0: * in JSContextPtr cx, michael@0: * in JSObjectPtr obj, michael@0: * in uint32_t argc, michael@0: * in JSValPtr argv, michael@0: * in JSValPtr vp); michael@0: */ michael@0: NS_IMETHODIMP michael@0: nsXPCComponents_utils_Sandbox::Construct(nsIXPConnectWrappedNative *wrapper, JSContext *cx, michael@0: JSObject *objArg, const CallArgs &args, bool *_retval) michael@0: { michael@0: RootedObject obj(cx, objArg); michael@0: return CallOrConstruct(wrapper, cx, obj, args, _retval); michael@0: } michael@0: michael@0: /* michael@0: * For sandbox constructor the first argument can be a URI string in which case michael@0: * we use the related Codebase Principal for the sandbox. michael@0: */ michael@0: bool michael@0: ParsePrincipal(JSContext *cx, HandleString codebase, nsIPrincipal **principal) michael@0: { michael@0: MOZ_ASSERT(principal); michael@0: MOZ_ASSERT(codebase); michael@0: nsCOMPtr uri; michael@0: nsDependentJSString codebaseStr; michael@0: NS_ENSURE_TRUE(codebaseStr.init(cx, codebase), false); michael@0: nsresult rv = NS_NewURI(getter_AddRefs(uri), codebaseStr); michael@0: if (NS_FAILED(rv)) { michael@0: JS_ReportError(cx, "Creating URI from string failed"); michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr secman = michael@0: do_GetService(kScriptSecurityManagerContractID); michael@0: NS_ENSURE_TRUE(secman, false); michael@0: michael@0: // We could allow passing in the app-id and browser-element info to the michael@0: // sandbox constructor. But creating a sandbox based on a string is a michael@0: // deprecated API so no need to add features to it. michael@0: rv = secman->GetNoAppCodebasePrincipal(uri, principal); michael@0: if (NS_FAILED(rv) || !*principal) { michael@0: JS_ReportError(cx, "Creating Principal from URI failed"); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * For sandbox constructor the first argument can be a principal object or michael@0: * a script object principal (Document, Window). michael@0: */ michael@0: static bool michael@0: GetPrincipalOrSOP(JSContext *cx, HandleObject from, nsISupports **out) michael@0: { michael@0: MOZ_ASSERT(out); michael@0: *out = nullptr; michael@0: michael@0: nsXPConnect* xpc = nsXPConnect::XPConnect(); michael@0: nsISupports* native = xpc->GetNativeOfWrapper(cx, from); michael@0: michael@0: if (nsCOMPtr sop = do_QueryInterface(native)) { michael@0: sop.forget(out); michael@0: return true; michael@0: } michael@0: michael@0: nsCOMPtr principal = do_QueryInterface(native); michael@0: principal.forget(out); michael@0: NS_ENSURE_TRUE(*out, false); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * The first parameter of the sandbox constructor might be an array of principals, either in string michael@0: * format or actual objects (see GetPrincipalOrSOP) michael@0: */ michael@0: static bool michael@0: GetExpandedPrincipal(JSContext *cx, HandleObject arrayObj, nsIExpandedPrincipal **out) michael@0: { michael@0: MOZ_ASSERT(out); michael@0: uint32_t length; michael@0: michael@0: if (!JS_IsArrayObject(cx, arrayObj) || michael@0: !JS_GetArrayLength(cx, arrayObj, &length) || michael@0: !length) michael@0: { michael@0: // We need a whitelist of principals or uri strings to create an michael@0: // expanded principal, if we got an empty array or something else michael@0: // report error. michael@0: JS_ReportError(cx, "Expected an array of URI strings"); michael@0: return false; michael@0: } michael@0: michael@0: nsTArray< nsCOMPtr > allowedDomains(length); michael@0: allowedDomains.SetLength(length); michael@0: nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager(); michael@0: NS_ENSURE_TRUE(ssm, false); michael@0: michael@0: for (uint32_t i = 0; i < length; ++i) { michael@0: RootedValue allowed(cx); michael@0: if (!JS_GetElement(cx, arrayObj, i, &allowed)) michael@0: return false; michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr principal; michael@0: if (allowed.isString()) { michael@0: // In case of string let's try to fetch a codebase principal from it. michael@0: RootedString str(cx, allowed.toString()); michael@0: if (!ParsePrincipal(cx, str, getter_AddRefs(principal))) michael@0: return false; michael@0: michael@0: } else if (allowed.isObject()) { michael@0: // In case of object let's see if it's a Principal or a ScriptObjectPrincipal. michael@0: nsCOMPtr prinOrSop; michael@0: RootedObject obj(cx, &allowed.toObject()); michael@0: if (!GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop))) michael@0: return false; michael@0: michael@0: nsCOMPtr sop(do_QueryInterface(prinOrSop)); michael@0: principal = do_QueryInterface(prinOrSop); michael@0: if (sop) michael@0: principal = sop->GetPrincipal(); michael@0: } michael@0: NS_ENSURE_TRUE(principal, false); michael@0: michael@0: // We do not allow ExpandedPrincipals to contain any system principals. michael@0: bool isSystem; michael@0: rv = ssm->IsSystemPrincipal(principal, &isSystem); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: if (isSystem) { michael@0: JS_ReportError(cx, "System principal is not allowed in an expanded principal"); michael@0: return false; michael@0: } michael@0: allowedDomains[i] = principal; michael@0: } michael@0: michael@0: nsCOMPtr result = new nsExpandedPrincipal(allowedDomains); michael@0: result.forget(out); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Helper that tries to get a property from the options object. michael@0: */ michael@0: bool michael@0: OptionsBase::ParseValue(const char *name, MutableHandleValue prop, bool *aFound) michael@0: { michael@0: bool found; michael@0: bool ok = JS_HasProperty(mCx, mObject, name, &found); michael@0: NS_ENSURE_TRUE(ok, false); michael@0: michael@0: if (aFound) michael@0: *aFound = found; michael@0: michael@0: if (!found) michael@0: return true; michael@0: michael@0: return JS_GetProperty(mCx, mObject, name, prop); michael@0: } michael@0: michael@0: /* michael@0: * Helper that tries to get a boolean property from the options object. michael@0: */ michael@0: bool michael@0: OptionsBase::ParseBoolean(const char *name, bool *prop) michael@0: { michael@0: MOZ_ASSERT(prop); michael@0: RootedValue value(mCx); michael@0: bool found; michael@0: bool ok = ParseValue(name, &value, &found); michael@0: NS_ENSURE_TRUE(ok, false); michael@0: michael@0: if (!found) michael@0: return true; michael@0: michael@0: if (!value.isBoolean()) { michael@0: JS_ReportError(mCx, "Expected a boolean value for property %s", name); michael@0: return false; michael@0: } michael@0: michael@0: *prop = value.toBoolean(); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Helper that tries to get an object property from the options object. michael@0: */ michael@0: bool michael@0: OptionsBase::ParseObject(const char *name, MutableHandleObject prop) michael@0: { michael@0: RootedValue value(mCx); michael@0: bool found; michael@0: bool ok = ParseValue(name, &value, &found); michael@0: NS_ENSURE_TRUE(ok, false); michael@0: michael@0: if (!found) michael@0: return true; michael@0: michael@0: if (!value.isObject()) { michael@0: JS_ReportError(mCx, "Expected an object value for property %s", name); michael@0: return false; michael@0: } michael@0: prop.set(&value.toObject()); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Helper that tries to get a string property from the options object. michael@0: */ michael@0: bool michael@0: OptionsBase::ParseString(const char *name, nsCString &prop) michael@0: { michael@0: RootedValue value(mCx); michael@0: bool found; michael@0: bool ok = ParseValue(name, &value, &found); michael@0: NS_ENSURE_TRUE(ok, false); michael@0: michael@0: if (!found) michael@0: return true; michael@0: michael@0: if (!value.isString()) { michael@0: JS_ReportError(mCx, "Expected a string value for property %s", name); michael@0: return false; michael@0: } michael@0: michael@0: char *tmp = JS_EncodeString(mCx, value.toString()); michael@0: NS_ENSURE_TRUE(tmp, false); michael@0: prop.Adopt(tmp, strlen(tmp)); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Helper that tries to get a string property from the options object. michael@0: */ michael@0: bool michael@0: OptionsBase::ParseString(const char *name, nsString &prop) michael@0: { michael@0: RootedValue value(mCx); michael@0: bool found; michael@0: bool ok = ParseValue(name, &value, &found); michael@0: NS_ENSURE_TRUE(ok, false); michael@0: michael@0: if (!found) michael@0: return true; michael@0: michael@0: if (!value.isString()) { michael@0: JS_ReportError(mCx, "Expected a string value for property %s", name); michael@0: return false; michael@0: } michael@0: michael@0: nsDependentJSString strVal; michael@0: strVal.init(mCx, value.toString()); michael@0: prop = strVal; michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Helper that tries to get jsid property from the options object. michael@0: */ michael@0: bool michael@0: OptionsBase::ParseId(const char *name, MutableHandleId prop) michael@0: { michael@0: RootedValue value(mCx); michael@0: bool found; michael@0: bool ok = ParseValue(name, &value, &found); michael@0: NS_ENSURE_TRUE(ok, false); michael@0: michael@0: if (!found) michael@0: return true; michael@0: michael@0: return JS_ValueToId(mCx, value, prop); michael@0: } michael@0: michael@0: /* michael@0: * Helper that tries to get a list of DOM constructors and other helpers from the options object. michael@0: */ michael@0: bool michael@0: SandboxOptions::ParseGlobalProperties() michael@0: { michael@0: RootedValue value(mCx); michael@0: bool found; michael@0: bool ok = ParseValue("wantGlobalProperties", &value, &found); michael@0: NS_ENSURE_TRUE(ok, false); michael@0: if (!found) michael@0: return true; michael@0: michael@0: if (!value.isObject()) { michael@0: JS_ReportError(mCx, "Expected an array value for wantGlobalProperties"); michael@0: return false; michael@0: } michael@0: michael@0: RootedObject ctors(mCx, &value.toObject()); michael@0: if (!JS_IsArrayObject(mCx, ctors)) { michael@0: JS_ReportError(mCx, "Expected an array value for wantGlobalProperties"); michael@0: return false; michael@0: } michael@0: michael@0: return globalProperties.Parse(mCx, ctors); michael@0: } michael@0: michael@0: /* michael@0: * Helper that parsing the sandbox options object (from) and sets the fields of the incoming options struct (options). michael@0: */ michael@0: bool michael@0: SandboxOptions::Parse() michael@0: { michael@0: return ParseObject("sandboxPrototype", &proto) && michael@0: ParseBoolean("wantXrays", &wantXrays) && michael@0: ParseBoolean("wantComponents", &wantComponents) && michael@0: ParseBoolean("wantExportHelpers", &wantExportHelpers) && michael@0: ParseString("sandboxName", sandboxName) && michael@0: ParseObject("sameZoneAs", &sameZoneAs) && michael@0: ParseBoolean("invisibleToDebugger", &invisibleToDebugger) && michael@0: ParseBoolean("discardSource", &discardSource) && michael@0: ParseGlobalProperties() && michael@0: ParseValue("metadata", &metadata); michael@0: } michael@0: michael@0: static nsresult michael@0: AssembleSandboxMemoryReporterName(JSContext *cx, nsCString &sandboxName) michael@0: { michael@0: // Use a default name when the caller did not provide a sandboxName. michael@0: if (sandboxName.IsEmpty()) michael@0: sandboxName = NS_LITERAL_CSTRING("[anonymous sandbox]"); michael@0: michael@0: nsXPConnect* xpc = nsXPConnect::XPConnect(); michael@0: // Get the xpconnect native call context. michael@0: nsAXPCNativeCallContext *cc = nullptr; michael@0: xpc->GetCurrentNativeCallContext(&cc); michael@0: NS_ENSURE_TRUE(cc, NS_ERROR_INVALID_ARG); michael@0: michael@0: // Get the current source info from xpc. michael@0: nsCOMPtr frame; michael@0: xpc->GetCurrentJSStack(getter_AddRefs(frame)); michael@0: michael@0: // Append the caller's location information. michael@0: if (frame) { michael@0: nsString location; michael@0: int32_t lineNumber = 0; michael@0: frame->GetFilename(location); michael@0: frame->GetLineNumber(&lineNumber); michael@0: michael@0: sandboxName.AppendLiteral(" (from: "); michael@0: sandboxName.Append(NS_ConvertUTF16toUTF8(location)); michael@0: sandboxName.AppendLiteral(":"); michael@0: sandboxName.AppendInt(lineNumber); michael@0: sandboxName.AppendLiteral(")"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: nsXPCComponents_utils_Sandbox::CallOrConstruct(nsIXPConnectWrappedNative *wrapper, michael@0: JSContext *cx, HandleObject obj, michael@0: const CallArgs &args, bool *_retval) michael@0: { michael@0: if (args.length() < 1) michael@0: return ThrowAndFail(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx, _retval); michael@0: michael@0: nsresult rv; michael@0: bool ok = false; michael@0: michael@0: // Make sure to set up principals on the sandbox before initing classes. michael@0: nsCOMPtr principal; michael@0: nsCOMPtr expanded; michael@0: nsCOMPtr prinOrSop; michael@0: michael@0: if (args[0].isString()) { michael@0: RootedString str(cx, args[0].toString()); michael@0: ok = ParsePrincipal(cx, str, getter_AddRefs(principal)); michael@0: prinOrSop = principal; michael@0: } else if (args[0].isObject()) { michael@0: RootedObject obj(cx, &args[0].toObject()); michael@0: if (JS_IsArrayObject(cx, obj)) { michael@0: ok = GetExpandedPrincipal(cx, obj, getter_AddRefs(expanded)); michael@0: prinOrSop = expanded; michael@0: } else { michael@0: ok = GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop)); michael@0: } michael@0: } michael@0: michael@0: if (!ok) michael@0: return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); michael@0: michael@0: bool calledWithOptions = args.length() > 1; michael@0: if (calledWithOptions && !args[1].isObject()) michael@0: return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); michael@0: michael@0: RootedObject optionsObject(cx, calledWithOptions ? &args[1].toObject() michael@0: : nullptr); michael@0: michael@0: SandboxOptions options(cx, optionsObject); michael@0: if (calledWithOptions && !options.Parse()) michael@0: return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); michael@0: michael@0: if (NS_FAILED(AssembleSandboxMemoryReporterName(cx, options.sandboxName))) michael@0: return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); michael@0: michael@0: if (options.metadata.isNullOrUndefined()) { michael@0: // If the caller is running in a sandbox, inherit. michael@0: RootedObject callerGlobal(cx, CurrentGlobalOrNull(cx)); michael@0: if (IsSandbox(callerGlobal)) { michael@0: rv = GetSandboxMetadata(cx, callerGlobal, &options.metadata); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: rv = CreateSandboxObject(cx, args.rval(), prinOrSop, options); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return ThrowAndFail(rv, cx, _retval); michael@0: michael@0: *_retval = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: class ContextHolder : public nsIScriptObjectPrincipal michael@0: { michael@0: public: michael@0: ContextHolder(JSContext *aOuterCx, HandleObject aSandbox, nsIPrincipal *aPrincipal); michael@0: virtual ~ContextHolder(); michael@0: michael@0: JSContext * GetJSContext() michael@0: { michael@0: return mJSContext; michael@0: } michael@0: michael@0: nsIPrincipal * GetPrincipal() { return mPrincipal; } michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: private: michael@0: JSContext* mJSContext; michael@0: nsCOMPtr mPrincipal; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(ContextHolder, nsIScriptObjectPrincipal) michael@0: michael@0: ContextHolder::ContextHolder(JSContext *aOuterCx, michael@0: HandleObject aSandbox, michael@0: nsIPrincipal *aPrincipal) michael@0: : mJSContext(JS_NewContext(JS_GetRuntime(aOuterCx), 1024)), michael@0: mPrincipal(aPrincipal) michael@0: { michael@0: if (mJSContext) { michael@0: bool isChrome; michael@0: DebugOnly rv = XPCWrapper::GetSecurityManager()-> michael@0: IsSystemPrincipal(mPrincipal, &isChrome); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: michael@0: JS::ContextOptionsRef(mJSContext).setDontReportUncaught(true) michael@0: .setPrivateIsNSISupports(true); michael@0: js::SetDefaultObjectForContext(mJSContext, aSandbox); michael@0: JS_SetContextPrivate(mJSContext, this); michael@0: } michael@0: } michael@0: michael@0: ContextHolder::~ContextHolder() michael@0: { michael@0: if (mJSContext) michael@0: JS_DestroyContextNoGC(mJSContext); michael@0: } michael@0: michael@0: nsresult michael@0: xpc::EvalInSandbox(JSContext *cx, HandleObject sandboxArg, const nsAString& source, michael@0: const nsACString& filename, int32_t lineNo, michael@0: JSVersion jsVersion, bool returnStringOnly, MutableHandleValue rval) michael@0: { michael@0: JS_AbortIfWrongThread(JS_GetRuntime(cx)); michael@0: rval.set(UndefinedValue()); michael@0: michael@0: bool waiveXray = xpc::WrapperFactory::HasWaiveXrayFlag(sandboxArg); michael@0: RootedObject sandbox(cx, js::CheckedUnwrap(sandboxArg)); michael@0: if (!sandbox || js::GetObjectJSClass(sandbox) != &SandboxClass) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: nsIScriptObjectPrincipal *sop = michael@0: (nsIScriptObjectPrincipal*)xpc_GetJSPrivate(sandbox); michael@0: MOZ_ASSERT(sop, "Invalid sandbox passed"); michael@0: nsCOMPtr prin = sop->GetPrincipal(); michael@0: NS_ENSURE_TRUE(prin, NS_ERROR_FAILURE); michael@0: michael@0: nsAutoCString filenameBuf; michael@0: if (!filename.IsVoid()) { michael@0: filenameBuf.Assign(filename); michael@0: } else { michael@0: // Default to the spec of the principal. michael@0: nsJSPrincipals::get(prin)->GetScriptLocation(filenameBuf); michael@0: lineNo = 1; michael@0: } michael@0: michael@0: // We create a separate cx to do the sandbox evaluation. Scope it. michael@0: RootedValue v(cx, UndefinedValue()); michael@0: RootedValue exn(cx, UndefinedValue()); michael@0: bool ok = true; michael@0: { michael@0: // Make a special cx for the sandbox and push it. michael@0: // NB: As soon as the RefPtr goes away, the cx goes away. So declare michael@0: // it first so that it disappears last. michael@0: nsRefPtr sandcxHolder = new ContextHolder(cx, sandbox, prin); michael@0: JSContext *sandcx = sandcxHolder->GetJSContext(); michael@0: if (!sandcx) { michael@0: JS_ReportError(cx, "Can't prepare context for evalInSandbox"); michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: nsCxPusher pusher; michael@0: pusher.Push(sandcx); michael@0: JSAutoCompartment ac(sandcx, sandbox); michael@0: michael@0: JS::CompileOptions options(sandcx); michael@0: options.setFileAndLine(filenameBuf.get(), lineNo); michael@0: if (jsVersion != JSVERSION_DEFAULT) michael@0: options.setVersion(jsVersion); michael@0: JS::RootedObject rootedSandbox(sandcx, sandbox); michael@0: ok = JS::Evaluate(sandcx, rootedSandbox, options, michael@0: PromiseFlatString(source).get(), source.Length(), &v); michael@0: if (ok && returnStringOnly && !v.isUndefined()) { michael@0: JSString *str = ToString(sandcx, v); michael@0: ok = !!str; michael@0: v = ok ? JS::StringValue(str) : JS::UndefinedValue(); michael@0: } michael@0: michael@0: // If the sandbox threw an exception, grab it off the context. michael@0: if (JS_GetPendingException(sandcx, &exn)) { michael@0: MOZ_ASSERT(!ok); michael@0: JS_ClearPendingException(sandcx); michael@0: if (returnStringOnly) { michael@0: // The caller asked for strings only, convert the michael@0: // exception into a string. michael@0: JSString *str = ToString(sandcx, exn); michael@0: exn = str ? JS::StringValue(str) : JS::UndefinedValue(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // michael@0: // Alright, we're back on the caller's cx. If an error occured, try to michael@0: // wrap and set the exception. Otherwise, wrap the return value. michael@0: // michael@0: michael@0: if (!ok) { michael@0: // If we end up without an exception, it was probably due to OOM along michael@0: // the way, in which case we thow. Otherwise, wrap it. michael@0: if (exn.isUndefined() || !JS_WrapValue(cx, &exn)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // Set the exception on our caller's cx. michael@0: JS_SetPendingException(cx, exn); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Transitively apply Xray waivers if |sb| was waived. michael@0: if (waiveXray) { michael@0: ok = xpc::WrapperFactory::WaiveXrayAndWrap(cx, &v); michael@0: } else { michael@0: ok = JS_WrapValue(cx, &v); michael@0: } michael@0: NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); michael@0: michael@0: // Whew! michael@0: rval.set(v); michael@0: return NS_OK; michael@0: } michael@0: michael@0: static bool michael@0: NonCloningFunctionForwarder(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0)); michael@0: MOZ_ASSERT(v.isObject(), "weird function"); michael@0: michael@0: RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); michael@0: if (!obj) { michael@0: return false; michael@0: } michael@0: return JS_CallFunctionValue(cx, obj, v, args, args.rval()); michael@0: } michael@0: michael@0: /* michael@0: * Forwards the call to the exported function. Clones all the non reflectors, ignores michael@0: * the |this| argument. michael@0: */ michael@0: static bool michael@0: CloningFunctionForwarder(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0)); michael@0: NS_ASSERTION(v.isObject(), "weird function"); michael@0: RootedObject origFunObj(cx, UncheckedUnwrap(&v.toObject())); michael@0: { michael@0: JSAutoCompartment ac(cx, origFunObj); michael@0: // Note: only the arguments are cloned not the |this| or the |callee|. michael@0: // Function forwarder does not use those. michael@0: for (unsigned i = 0; i < args.length(); i++) { michael@0: if (!CloneNonReflectors(cx, args[i])) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // JS API does not support any JSObject to JSFunction conversion, michael@0: // so let's use JS_CallFunctionValue instead. michael@0: RootedValue functionVal(cx, ObjectValue(*origFunObj)); michael@0: michael@0: if (!JS_CallFunctionValue(cx, JS::NullPtr(), functionVal, args, args.rval())) michael@0: return false; michael@0: } michael@0: michael@0: // Return value must be wrapped. michael@0: return JS_WrapValue(cx, args.rval()); michael@0: } michael@0: michael@0: bool michael@0: xpc::NewFunctionForwarder(JSContext *cx, HandleId id, HandleObject callable, bool doclone, michael@0: MutableHandleValue vp) michael@0: { michael@0: JSFunction *fun = js::NewFunctionByIdWithReserved(cx, doclone ? CloningFunctionForwarder : michael@0: NonCloningFunctionForwarder, michael@0: 0,0, JS::CurrentGlobalOrNull(cx), id); michael@0: michael@0: if (!fun) michael@0: return false; michael@0: michael@0: JSObject *funobj = JS_GetFunctionObject(fun); michael@0: js::SetFunctionNativeReserved(funobj, 0, ObjectValue(*callable)); michael@0: vp.setObject(*funobj); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: xpc::NewFunctionForwarder(JSContext *cx, HandleObject callable, bool doclone, michael@0: MutableHandleValue vp) michael@0: { michael@0: RootedId emptyId(cx); michael@0: RootedValue emptyStringValue(cx, JS_GetEmptyStringValue(cx)); michael@0: if (!JS_ValueToId(cx, emptyStringValue, &emptyId)) michael@0: return false; michael@0: michael@0: return NewFunctionForwarder(cx, emptyId, callable, doclone, vp); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: xpc::GetSandboxMetadata(JSContext *cx, HandleObject sandbox, MutableHandleValue rval) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(IsSandbox(sandbox)); michael@0: michael@0: RootedValue metadata(cx); michael@0: { michael@0: JSAutoCompartment ac(cx, sandbox); michael@0: metadata = JS_GetReservedSlot(sandbox, XPCONNECT_SANDBOX_CLASS_METADATA_SLOT); michael@0: } michael@0: michael@0: if (!JS_WrapValue(cx, &metadata)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: rval.set(metadata); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: xpc::SetSandboxMetadata(JSContext *cx, HandleObject sandbox, HandleValue metadataArg) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(IsSandbox(sandbox)); michael@0: michael@0: RootedValue metadata(cx); michael@0: michael@0: JSAutoCompartment ac(cx, sandbox); michael@0: if (!JS_StructuredClone(cx, metadataArg, &metadata, nullptr, nullptr)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: JS_SetReservedSlot(sandbox, XPCONNECT_SANDBOX_CLASS_METADATA_SLOT, metadata); michael@0: michael@0: return NS_OK; michael@0: }