Bug 1155898 - Expose fetch on JS sandbox. r=gabor, r=peterv

This commit is contained in:
Martin Thomson 2015-05-20 14:26:32 -07:00
parent d5d271440b
commit e6709a0bb7
4 changed files with 145 additions and 0 deletions

View File

@ -32,12 +32,16 @@
#include "mozilla/dom/BlobBinding.h"
#include "mozilla/dom/CSSBinding.h"
#include "mozilla/dom/indexedDB/IndexedDatabaseManager.h"
#include "mozilla/dom/Fetch.h"
#include "mozilla/dom/FileBinding.h"
#include "mozilla/dom/PromiseBinding.h"
#include "mozilla/dom/RequestBinding.h"
#include "mozilla/dom/ResponseBinding.h"
#include "mozilla/dom/RTCIdentityProviderRegistrar.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/TextDecoderBinding.h"
#include "mozilla/dom/TextEncoderBinding.h"
#include "mozilla/dom/UnionConversions.h"
#include "mozilla/dom/URLBinding.h"
#include "mozilla/dom/URLSearchParamsBinding.h"
@ -236,6 +240,84 @@ SandboxCreateRTCIdentityProvider(JSContext* cx, JS::HandleObject obj)
return JS_DefineProperty(cx, obj, "rtcIdentityProvider", wrapped, JSPROP_ENUMERATE);
}
static bool
SetFetchRequestFromValue(JSContext *cx, RequestOrUSVString& request,
const MutableHandleValue& requestOrUrl)
{
RequestOrUSVStringArgument requestHolder(request);
bool noMatch = true;
if (requestOrUrl.isObject() &&
!requestHolder.TrySetToRequest(cx, requestOrUrl, noMatch, false)) {
return false;
}
if (noMatch &&
!requestHolder.TrySetToUSVString(cx, requestOrUrl, noMatch)) {
return false;
}
if (noMatch) {
return false;
}
return true;
}
static bool
SandboxFetch(JSContext* cx, JS::HandleObject scope, const CallArgs& args)
{
if (args.length() < 1) {
JS_ReportError(cx, "fetch requires at least 1 argument");
return false;
}
RequestOrUSVString request;
if (!SetFetchRequestFromValue(cx, request, args[0])) {
JS_ReportError(cx, "fetch requires a string or Request in argument 1");
return false;
}
RootedDictionary<dom::RequestInit> options(cx);
if (!options.Init(cx, args.hasDefined(1) ? args[1] : JS::NullHandleValue,
"Argument 2 of fetch", false)) {
return false;
}
nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(scope);
if (!global) {
return false;
}
ErrorResult rv;
nsRefPtr<dom::Promise> response =
FetchRequest(global, Constify(request), Constify(options), rv);
rv.WouldReportJSException();
if (rv.Failed()) {
return ThrowMethodFailedWithDetails(cx, rv, "Sandbox", "fetch");
}
if (!GetOrCreateDOMReflector(cx, scope, response, args.rval())) {
return false;
}
return true;
}
static bool SandboxFetchPromise(JSContext* cx, unsigned argc, jsval* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject callee(cx, &args.callee());
RootedObject scope(cx, JS::CurrentGlobalOrNull(cx));
if (SandboxFetch(cx, scope, args)) {
return true;
}
return ConvertExceptionToPromise(cx, scope, args.rval());
}
static bool
SandboxCreateFetch(JSContext* cx, HandleObject obj)
{
MOZ_ASSERT(JS_IsGlobalObject(obj));
return JS_DefineFunction(cx, obj, "fetch", SandboxFetchPromise, 2, 0) &&
dom::RequestBinding::GetConstructorObject(cx, obj) &&
dom::ResponseBinding::GetConstructorObject(cx, obj) &&
dom::HeadersBinding::GetConstructorObject(cx, obj);
}
static bool
SandboxIsProxy(JSContext* cx, unsigned argc, jsval* vp)
{
@ -818,6 +900,8 @@ xpc::GlobalProperties::Parse(JSContext* cx, JS::HandleObject obj)
crypto = true;
} else if (!strcmp(name.ptr(), "rtcIdentityProvider")) {
rtcIdentityProvider = true;
} else if (!strcmp(name.ptr(), "fetch")) {
fetch = true;
} else {
JS_ReportError(cx, "Unknown property name: %s", name.ptr());
return false;
@ -878,6 +962,9 @@ xpc::GlobalProperties::Define(JSContext* cx, JS::HandleObject obj)
if (rtcIdentityProvider && !SandboxCreateRTCIdentityProvider(cx, obj))
return false;
if (fetch && !SandboxCreateFetch(cx, obj))
return false;
return true;
}

View File

@ -3400,6 +3400,7 @@ struct GlobalProperties {
bool File : 1;
bool crypto : 1;
bool rtcIdentityProvider : 1;
bool fetch : 1;
};
// Infallible.

View File

@ -105,3 +105,6 @@ skip-if= buildapp == 'mulet'
skip-if = (debug == false || os == "android")
[test_nac.xhtml]
[test_sameOriginPolicy.html]
[test_sandbox_fetch.html]
support-files =
../../../../dom/tests/mochitest/fetch/test_fetch_basic.js

View File

@ -0,0 +1,54 @@
<!doctype html>
<html>
<head>
<title>Fetch in JS Sandbox</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"></link>
<script src="test_fetch_basic.js"></script>
</head>
<body>
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
function testHttpFetch(url) {
info('fetch: ' + url);
return fetch(new Request(url, { method: 'GET' }))
.then(response => {
is(response.status, 200, 'Response is 200');
is(response.url, url, 'Response URL matches');
});
}
function runSandboxTest(testFunc, argString) {
is(typeof testFunc, 'function');
var resolvePromise;
var testPromise = new Promise(r => resolvePromise = r);
var finishFuncName = 'finish_' + testFunc.name;
SpecialPowers.Cu.exportFunction(_ => resolvePromise(), sb,
{ defineAs: finishFuncName });
SpecialPowers.Cu.evalInSandbox('(' + testFunc.toSource() + ')' +
'(' + argString + ')' +
'.then(' + finishFuncName + ');', sb);
return testPromise;
}
var origin = 'https://example.com';
var properties = ['fetch', 'Blob', 'URL'];
var sb = new SpecialPowers.Cu.Sandbox(origin,
{ wantGlobalProperties: properties });
sb.ok = SpecialPowers.Cu.exportFunction(ok, sb);
sb.is = SpecialPowers.Cu.exportFunction(is, sb);
sb.info = SpecialPowers.Cu.exportFunction(info, sb);
Promise.resolve()
.then(_ => runSandboxTest(testHttpFetch, '"' + origin + window.location.pathname + '"'))
.then(_ => runSandboxTest(testAboutURL))
.then(_ => runSandboxTest(testDataURL))
.then(_ => runSandboxTest(testSameOriginBlobURL))
.then(_ => SimpleTest.finish());
</script>
</body>
</html>