Bug 692677 - Relax same-origin XHR restrictions for privileged applications. r=sicking

--HG--
extra : rebase_source : 9e8e8cf2e03b6f3d148503d92630ee898bf835bb
This commit is contained in:
Philipp von Weitershausen 2012-06-07 11:28:33 -07:00
parent e02689f0c9
commit 9d771da697
13 changed files with 414 additions and 14 deletions

View File

@ -21,6 +21,27 @@ interface nsIDOMBlob;
#include "jsapi.h"
%}
/**
* Parameters for instantiating an XMLHttpRequest. They are passed as an
* optional argument to the constructor:
*
* new XMLHttpRequest({anon: true, system: true});
*
*/
dictionary XMLHttpRequestParameters
{
/**
* If true, the request will be sent without cookie and authentication
* headers.
*/
boolean mozAnon;
/**
* If true, the same origin policy will not be enforced on the request.
*/
boolean mozSystem;
};
[scriptable, builtinclass, uuid(5e346bf8-7083-4ef8-b9b9-736a1b5aa7ab)]
interface nsIXMLHttpRequestEventTarget : nsIDOMEventTarget {
// event handler attributes
@ -79,7 +100,7 @@ interface nsIXMLHttpRequestUpload : nsIXMLHttpRequestEventTarget {
* you're aware of all the security implications. And then think twice about
* it.
*/
[scriptable, uuid(8681ffbc-4755-45de-9fc1-b63e6930e76a)]
[scriptable, uuid(2ed23d20-9d6d-47fd-b60f-2416dbd57005)]
interface nsIXMLHttpRequest : nsISupports
{
/**
@ -351,6 +372,17 @@ interface nsIXMLHttpRequest : nsISupports
* Call open() before setting an onreadystatechange listener.
*/
attribute nsIDOMEventListener onreadystatechange;
/**
* If true, the request will be sent without cookie and authentication
* headers.
*/
readonly attribute boolean mozAnon;
/**
* If true, the same origin policy will not be enforced on the request.
*/
readonly attribute boolean mozSystem;
};
[scriptable, uuid(840d0d00-e83e-4a29-b3c7-67e96e90a499)]

View File

@ -76,6 +76,7 @@
#include "sampler.h"
#include "mozilla/dom/XMLHttpRequestBinding.h"
#include "nsIDOMFormData.h"
#include "DictionaryHelpers.h"
#include "nsWrapperCacheInlines.h"
#include "nsStreamListenerWrapper.h"
@ -451,7 +452,9 @@ nsXMLHttpRequest::nsXMLHttpRequest()
mFirstStartRequestSeen(false),
mInLoadProgressEvent(false),
mResultJSON(JSVAL_VOID),
mResultArrayBuffer(nsnull)
mResultArrayBuffer(nsnull),
mIsAnon(false),
mIsSystem(false)
{
nsLayoutStatics::AddRef();
@ -561,6 +564,41 @@ nsXMLHttpRequest::Initialize(nsISupports* aOwner, JSContext* cx, JSObject* obj,
NS_ENSURE_STATE(scriptPrincipal);
Construct(scriptPrincipal->GetPrincipal(), owner);
if (argc) {
nsresult rv = InitParameters(cx, argv);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
nsXMLHttpRequest::InitParameters(JSContext* aCx, const jsval* aParams)
{
XMLHttpRequestParameters* params = new XMLHttpRequestParameters();
nsresult rv = params->Init(aCx, aParams);
NS_ENSURE_SUCCESS(rv, rv);
// Check for permissions.
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(GetOwner());
NS_ENSURE_TRUE(window && window->GetDocShell(), NS_OK);
// Chrome is always allowed access, so do the permission check only
// for non-chrome pages.
if (!nsContentUtils::IsCallerChrome()) {
nsCOMPtr<nsIDocument> doc = do_QueryInterface(window->GetExtantDocument());
NS_ENSURE_TRUE(doc, NS_OK);
nsCOMPtr<nsIURI> uri;
doc->NodePrincipal()->GetURI(getter_AddRefs(uri));
if (!nsContentUtils::URIIsChromeOrInPref(uri, "dom.systemXHR.whitelist")) {
return NS_OK;
}
}
mIsAnon = params->mozAnon;
mIsSystem = params->mozSystem;
return NS_OK;
}
@ -1711,7 +1749,7 @@ nsXMLHttpRequest::GetCurrentHttpChannel()
bool
nsXMLHttpRequest::IsSystemXHR()
{
return !!nsContentUtils::IsSystemPrincipal(mPrincipal);
return mIsSystem || nsContentUtils::IsSystemPrincipal(mPrincipal);
}
nsresult
@ -2284,7 +2322,7 @@ nsXMLHttpRequest::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
mResponseXML = do_QueryInterface(responseDoc);
mResponseXML->SetPrincipal(documentPrincipal);
if (IsSystemXHR()) {
if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
mResponseXML->ForceEnableXULXBL();
}
@ -3049,6 +3087,10 @@ nsXMLHttpRequest::Send(JSContext *aCx, nsIVariant* aVariant, const Nullable<Requ
listener = new nsStreamListenerWrapper(listener);
}
if (mIsAnon) {
AddLoadFlags(mChannel, nsIRequest::LOAD_ANONYMOUS);
}
NS_ASSERTION(listener != this,
"Using an object as a listener that can't be exposed to JS");
@ -3891,6 +3933,32 @@ nsXMLHttpRequest::GetUpload(nsIXMLHttpRequestUpload** aUpload)
return NS_OK;
}
bool
nsXMLHttpRequest::GetMozAnon()
{
return mIsAnon;
}
NS_IMETHODIMP
nsXMLHttpRequest::GetMozAnon(bool* aAnon)
{
*aAnon = GetMozAnon();
return NS_OK;
}
bool
nsXMLHttpRequest::GetMozSystem()
{
return IsSystemXHR();
}
NS_IMETHODIMP
nsXMLHttpRequest::GetMozSystem(bool* aSystem)
{
*aSystem = GetMozSystem();
return NS_OK;
}
void
nsXMLHttpRequest::HandleTimeoutCallback()
{

View File

@ -37,6 +37,7 @@
#include "nsDOMBlobBuilder.h"
#include "nsIPrincipal.h"
#include "nsIScriptObjectPrincipal.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/XMLHttpRequestBinding.h"
#include "mozilla/dom/XMLHttpRequestUploadBinding.h"
@ -180,7 +181,10 @@ public:
// The WebIDL constructor.
static already_AddRefed<nsXMLHttpRequest>
Constructor(nsISupports* aGlobal, ErrorResult& aRv)
Constructor(JSContext* aCx,
nsISupports* aGlobal,
const mozilla::dom::Optional<jsval>& aParams,
ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal);
nsCOMPtr<nsIScriptObjectPrincipal> principal = do_QueryInterface(aGlobal);
@ -191,6 +195,13 @@ public:
nsRefPtr<nsXMLHttpRequest> req = new nsXMLHttpRequest();
req->Construct(principal->GetPrincipal(), window);
if (aParams.WasPassed()) {
nsresult rv = req->InitParameters(aCx, &aParams.Value());
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return req.forget();
}
}
return req.forget();
}
@ -205,6 +216,9 @@ public:
mBaseURI = aBaseURI;
}
// Initialize XMLHttpRequestParameter object.
nsresult InitParameters(JSContext* aCx, const jsval* aParams);
NS_DECL_ISUPPORTS_INHERITED
// nsIXMLHttpRequest
@ -460,6 +474,9 @@ public:
bool GetMultipart();
void SetMultipart(bool aMultipart, nsresult& aRv);
bool GetMozAnon();
bool GetMozSystem();
nsIChannel* GetChannel()
{
return mChannel;
@ -689,6 +706,9 @@ protected:
nsCOMPtr<nsITimer> mProgressNotifier;
void HandleProgressTimerCallback();
bool mIsSystem;
bool mIsAnon;
/**
* Close the XMLHttpRequest's channels and dispatch appropriate progress
* events.

View File

@ -551,6 +551,10 @@ _TEST_FILES2 = \
test_bug753278.html \
test_bug761120.html \
test_XHR_onuploadprogress.html \
test_XHR_anon.html \
file_XHR_anon.sjs \
test_XHR_system.html \
test_XHR_parameters.html \
$(NULL)
_CHROME_FILES = \

View File

@ -0,0 +1,23 @@
function handleRequest(request, response) {
let invalidHeaders = ["Cookie"];
let headers = {};
if (request.queryString == "expectAuth=true") {
if (request.hasHeader("Authorization")) {
headers["authorization"] = request.getHeader("Authorization");
} else {
response.setStatusLine(null, 500, "Server Error");
}
} else {
invalidHeaders.push("Authorization");
}
for each (let header in invalidHeaders) {
if (request.hasHeader(header)) {
response.setStatusLine(null, 500, "Server Error");
headers[header.toLowerCase()] = request.getHeader(header);
}
}
response.write(JSON.stringify(headers));
}

View File

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test for XMLHttpRequest with system privileges</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body onload="runTests();">
<p id="display">
<iframe id="loader"></iframe>
</p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.8">
function runTests() {
let tearDown = (function setUp() {
SimpleTest.waitForExplicitFinish();
const {classes: Cc, interfaces: Ci} = SpecialPowers.wrap(Components);
let authMgr = Cc["@mozilla.org/network/http-auth-manager;1"]
.getService(Components.interfaces.nsIHttpAuthManager)
authMgr.setAuthIdentity("http", "example.com", 80, "basic", "testrealm",
"", "example.com", "user1", "password1");
SpecialPowers.setCharPref("dom.systemXHR.whitelist",
"http://mochi.test:8888");
return function tearDown() {
authMgr.clearAll();
SpecialPowers.clearUserPref("dom.systemXHR.whitelist");
SimpleTest.finish();
}
}());
// An XHR with the anon flag set will not send cookie and auth information.
const TEST_URL = "http://example.com/tests/content/base/test/file_XHR_anon.sjs";
document.cookie = "foo=bar";
function withoutCredentials() {
let xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
is(xhr.mozAnon, true, "withoutCredentials: .mozAnon == true");
xhr.open("GET", TEST_URL);
xhr.onload = function onload() {
is(xhr.status, 200, "withoutCredentials: " + xhr.responseText);
withCredentials();
};
xhr.onerror = function onerror() {
ok(false, "Got an error event!");
tearDown();
}
xhr.send();
}
function withCredentials() {
// TODO: this currently does not work as expected, see bug 761479
let xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
is(xhr.mozAnon, true, "withCredentials: .mozAnon == true");
xhr.open("GET", TEST_URL + "?expectAuth=true", true,
"user2name", "pass2word");
xhr.onload = function onload() {
todo_is(xhr.status, 200, "withCredentials: " + xhr.responseText);
let response = JSON.parse(xhr.responseText);
todo_is(response.authorization, "Basic dXNlcjJuYW1lOnBhc3Myd29yZA==");
tearDown();
};
xhr.onerror = function onerror() {
ok(false, "Got an error event!");
tearDown();
}
xhr.send();
}
withoutCredentials();
}
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,93 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test for XMLHttpRequest with system privileges</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body onload="runTests();">
<p id="display">
</p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.8">
function runTests() {
SimpleTest.waitForExplicitFinish();
let validParameters = [
{},
{mozSystem: ""},
{mozSystem: 0},
{mozAnon: 1},
{mozAnon: []},
{get mozAnon() { return true; }},
];
let invalidParameters = [
undefined,
null,
{get mozSystem() { throw "Bla"; } },
];
let havePrivileges = false;
function testValidParameter(value) {
let xhr;
try {
xhr = new XMLHttpRequest(value);
} catch (ex) {
ok(false, "Got unexpected exception: " + ex);
return;
}
ok(xhr instanceof XMLHttpRequest, "passed " + JSON.stringify(value));
// If the page doesnt have privileges to create a system or anon XHR,
// these flags will always be false no matter what is passed.
let expectedAnon = false;
let expectedSystem = false;
if (havePrivileges) {
expectedAnon = Boolean(value && value.mozAnon);
expectedSystem = Boolean(value && value.mozSystem);
}
is(xhr.mozAnon, expectedAnon, "testing mozAnon");
is(xhr.mozSystem, expectedSystem, "testing mozSystem");
}
function testInvalidParameter(value) {
let expectedError;
try {
new XMLHttpRequest(value);
ok(false, "invalid parameter did not cause exception: " +
JSON.stringify(value));
} catch (ex) {
expectedError = ex;
}
ok(expectedError, "invalid parameter raised exception as expected: " +
JSON.stringify(expectedError))
}
// Run the tests once without API privileges...
validParameters.forEach(testValidParameter);
invalidParameters.forEach(testInvalidParameter);
// ...and once with privileges.
havePrivileges = true;
SpecialPowers.setCharPref("dom.systemXHR.whitelist",
"http://mochi.test:8888");
validParameters.forEach(testValidParameter);
invalidParameters.forEach(testInvalidParameter);
SpecialPowers.clearUserPref("dom.systemXHR.whitelist");
SimpleTest.finish();
}
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test for XMLHttpRequest with system privileges</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body onload="runTests();">
<p id="display">
</p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.8">
function runTests() {
SimpleTest.waitForExplicitFinish();
SpecialPowers.setCharPref("dom.systemXHR.whitelist",
"http://mochi.test:8888");
function tearDown() {
SpecialPowers.clearUserPref("dom.systemXHR.whitelist");
SimpleTest.finish();
}
// An XHR with system privileges will be able to do cross-site calls.
const TEST_URL = "http://example.com/tests/content/base/test/test_XHR_system.html";
is(window.location.hostname, "mochi.test");
let xhr = new XMLHttpRequest({mozSystem: true});
is(xhr.mozSystem, true, ".mozSystem == true");
xhr.open("GET", TEST_URL);
xhr.onload = function onload() {
is(xhr.status, 200);
ok(xhr.responseText != null);
ok(xhr.responseText.length);
tearDown();
};
xhr.onerror = function onerror() {
ok(false, "Got an error event!");
tearDown();
}
xhr.send();
}
</script>
</pre>
</body>
</html>

View File

@ -229,7 +229,7 @@ DOMInterfaces = {
'all': [
'readyState', 'withCredentials', 'abort', 'statusText',
'getAllResponseHeaders', 'overrideMimeType', 'mozBackgroundRequest',
'multipart', 'channel', 'upload', 'status'
'multipart', 'channel', 'upload', 'status', 'mozAnon', 'mozSystem'
],
'getterOnly': [
'responseType', 'timeout', 'onreadystatechange', 'onuploadprogress'
@ -237,8 +237,9 @@ DOMInterfaces = {
},
# XXXbz need a JSContext for send() and sendAsBinary because of
# the old nsIVariant-based signatures which actually use it for
# typed arrays. Once those go away, we can nuke this line.
'implicitJSContext': [ 'send', 'sendAsBinary' ],
# typed arrays. Once those go away, we can nuke them from this
# list.
'implicitJSContext': [ 'constructor', 'send', 'sendAsBinary' ],
'resultNotAddRefed': [ 'upload', 'responseXML' ]
},
{
@ -246,7 +247,7 @@ DOMInterfaces = {
'nativeType': 'mozilla::dom::workers::XMLHttpRequest',
'headerFile': 'mozilla/dom/workers/bindings/XMLHttpRequest.h',
'infallible': {
'all': ['readyState', 'statusText' ],
'all': ['readyState', 'statusText', 'mozAnon', 'mozSystem' ],
'getterOnly': [ 'timeout', 'withCredentials', 'mozBackgroundRequest',
'responseType', 'responseXML', 'channel', 'multipart' ]
}

View File

@ -31,7 +31,7 @@ enum XMLHttpRequestResponseType {
"moz-blob"
};
[Constructor]
[Constructor(optional any params)]
interface XMLHttpRequest : XMLHttpRequestEventTarget {
// event handler
[TreatNonCallableAsNull] attribute Function? onreadystatechange;
@ -80,4 +80,6 @@ interface XMLHttpRequest : XMLHttpRequestEventTarget {
void sendAsBinary(DOMString body);
any getInterface(IID iid);
[TreatNonCallableAsNull] attribute Function? onuploadprogress;
readonly attribute boolean mozAnon;
readonly attribute boolean mozSystem;
};

View File

@ -1463,7 +1463,10 @@ XMLHttpRequest::_finalize(JSFreeOp* aFop)
// static
XMLHttpRequest*
XMLHttpRequest::Constructor(JSContext* aCx, JSObject* aGlobal, ErrorResult& aRv)
XMLHttpRequest::Constructor(JSContext* aCx,
JSObject* aGlobal,
const Optional<jsval>& aParams,
ErrorResult& aRv)
{
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
MOZ_ASSERT(workerPrivate);
@ -1475,6 +1478,8 @@ XMLHttpRequest::Constructor(JSContext* aCx, JSObject* aGlobal, ErrorResult& aRv)
return NULL;
}
// TODO: process aParams. See bug 761227
xhr->mJSObject = xhr->GetJSObject();
return xhr;
}

View File

@ -12,6 +12,7 @@
// Need this for XMLHttpRequestResponseType.
#include "mozilla/dom/XMLHttpRequestBinding.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/TypedArray.h"
BEGIN_WORKERS_NAMESPACE
@ -70,8 +71,8 @@ public:
_finalize(JSFreeOp* aFop) MOZ_OVERRIDE;
static XMLHttpRequest*
Constructor(JSContext* aCx, JSObject* aGlobal, ErrorResult& aRv);
Constructor(JSContext* aCx, JSObject* aGlobal,
const Optional<jsval>& aParams, ErrorResult& aRv);
void
Unpin();
@ -257,6 +258,16 @@ public:
mStateData.mResponse = JSVAL_NULL;
}
bool GetMozAnon() {
// TODO: bug 761227
return false;
}
bool GetMozSystem() {
// TODO: bug 761227
return false;
}
private:
enum ReleaseType { Default, XHRIsGoingAway, WorkerIsGoingAway };

View File

@ -22,7 +22,8 @@ dictionaries = [
[ 'DeviceProximityEventInit', 'nsIDOMDeviceProximityEvent.idl' ],
[ 'UserProximityEventInit', 'nsIDOMUserProximityEvent.idl' ],
[ 'DeviceLightEventInit', 'nsIDOMDeviceLightEvent.idl' ],
[ 'DOMFileMetadataParameters', 'nsIDOMLockedFile.idl' ]
[ 'DOMFileMetadataParameters', 'nsIDOMLockedFile.idl' ],
[ 'XMLHttpRequestParameters', 'nsIXMLHttpRequest.idl' ],
]
# include file names