mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-12 00:50:40 +00:00
Bug 806037 - use nsIPrincipal for origin checks. r=gavin
This commit is contained in:
parent
3f3cb0b9df
commit
20dbf380a1
@ -70,12 +70,11 @@ function injectController(doc, topic, data) {
|
||||
|
||||
// Loads mozSocial support functions associated with provider into targetWindow
|
||||
function attachToWindow(provider, targetWindow) {
|
||||
// If the loaded document isn't from the provider's origin, don't attach
|
||||
// the mozSocial API.
|
||||
let origin = provider.origin;
|
||||
// If the loaded document isn't from the provider's origin (or a protocol
|
||||
// that inherits the principal), don't attach the mozSocial API.
|
||||
let targetDocURI = targetWindow.document.documentURIObject;
|
||||
if (provider.origin != targetDocURI.prePath) {
|
||||
let msg = "MozSocialAPI: not attaching mozSocial API for " + origin +
|
||||
if (!provider.isSameOrigin(targetDocURI)) {
|
||||
let msg = "MozSocialAPI: not attaching mozSocial API for " + provider.origin +
|
||||
" to " + targetDocURI.spec + " since origins differ."
|
||||
Services.console.logStringMessage(msg);
|
||||
return;
|
||||
@ -125,10 +124,9 @@ function attachToWindow(provider, targetWindow) {
|
||||
if (!chromeWindow.SocialFlyout)
|
||||
return;
|
||||
let url = targetWindow.document.documentURIObject.resolve(toURL);
|
||||
let fullURL = ensureProviderOrigin(provider, url);
|
||||
if (!fullURL)
|
||||
if (!provider.isSameOrigin(url))
|
||||
return;
|
||||
chromeWindow.SocialFlyout.open(fullURL, offset, callback);
|
||||
chromeWindow.SocialFlyout.open(url, offset, callback);
|
||||
}
|
||||
},
|
||||
closePanel: {
|
||||
@ -229,26 +227,6 @@ function getChromeWindow(contentWin) {
|
||||
.getInterface(Ci.nsIDOMWindow);
|
||||
}
|
||||
|
||||
function ensureProviderOrigin(provider, url) {
|
||||
// resolve partial URLs and check prePath matches
|
||||
let uri;
|
||||
let fullURL;
|
||||
try {
|
||||
fullURL = Services.io.newURI(provider.origin, null, null).resolve(url);
|
||||
uri = Services.io.newURI(fullURL, null, null);
|
||||
} catch (ex) {
|
||||
Cu.reportError("mozSocial: failed to resolve window URL: " + url + "; " + ex);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (provider.origin != uri.prePath) {
|
||||
Cu.reportError("mozSocial: unable to load new location, " +
|
||||
provider.origin + " != " + uri.prePath);
|
||||
return null;
|
||||
}
|
||||
return fullURL;
|
||||
}
|
||||
|
||||
function isWindowGoodForChats(win) {
|
||||
return win.SocialChatBar && win.SocialChatBar.isAvailable;
|
||||
}
|
||||
@ -289,10 +267,10 @@ this.openChatWindow =
|
||||
chromeWindow = findChromeWindowForChats(chromeWindow);
|
||||
if (!chromeWindow)
|
||||
return;
|
||||
let fullURL = ensureProviderOrigin(provider, url);
|
||||
if (!fullURL)
|
||||
let fullURI = provider.resolveUri(url);
|
||||
if (!provider.isSameOrigin(fullURI))
|
||||
return;
|
||||
chromeWindow.SocialChatBar.openChat(provider, fullURL, callback, mode);
|
||||
chromeWindow.SocialChatBar.openChat(provider, fullURI.spec, callback, mode);
|
||||
// getAttention is ignored if the target window is already foreground, so
|
||||
// we can call it unconditionally.
|
||||
chromeWindow.getAttention();
|
||||
|
@ -246,6 +246,8 @@ function SocialProvider(input) {
|
||||
this.workerURL = input.workerURL;
|
||||
this.sidebarURL = input.sidebarURL;
|
||||
this.origin = input.origin;
|
||||
let originUri = Services.io.newURI(input.origin, null, null);
|
||||
this.principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(originUri);
|
||||
this.ambientNotificationIcons = {};
|
||||
this.errorState = null;
|
||||
this._active = ActiveProviders.has(this.origin);
|
||||
@ -336,14 +338,16 @@ SocialProvider.prototype = {
|
||||
reportError('images["' + sub + '"] is missing or not a non-empty string');
|
||||
return;
|
||||
}
|
||||
// resolve potentially relative URLs then check the scheme is acceptable.
|
||||
url = Services.io.newURI(this.origin, null, null).resolve(url);
|
||||
let uri = Services.io.newURI(url, null, null);
|
||||
if (!uri.schemeIs("http") && !uri.schemeIs("https") && !uri.schemeIs("data")) {
|
||||
reportError('images["' + sub + '"] does not have a valid scheme');
|
||||
// resolve potentially relative URLs but there is no same-origin check
|
||||
// for images to help providers utilize content delivery networks...
|
||||
// Also note no scheme checks are necessary - even a javascript: URL
|
||||
// is safe as gecko evaluates them in a sandbox.
|
||||
let imgUri = this.resolveUri(url);
|
||||
if (!imgUri) {
|
||||
reportError('images["' + sub + '"] is an invalid URL');
|
||||
return;
|
||||
}
|
||||
promptImages[sub] = url;
|
||||
promptImages[sub] = imgUri.spec;
|
||||
}
|
||||
for (let sub of ["shareTooltip", "unshareTooltip",
|
||||
"sharedLabel", "unsharedLabel", "unshareLabel",
|
||||
@ -451,5 +455,52 @@ SocialProvider.prototype = {
|
||||
return null;
|
||||
return getFrameWorkerHandle(this.workerURL, window,
|
||||
"SocialProvider:" + this.origin, this.origin).port;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if a given URI is of the same origin as the provider.
|
||||
*
|
||||
* Returns true or false.
|
||||
*
|
||||
* @param {URI or string} uri
|
||||
*/
|
||||
isSameOrigin: function isSameOrigin(uri, allowIfInheritsPrincipal) {
|
||||
if (!uri)
|
||||
return false;
|
||||
if (typeof uri == "string") {
|
||||
try {
|
||||
uri = Services.io.newURI(uri, null, null);
|
||||
} catch (ex) {
|
||||
// an invalid URL can't be loaded!
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.principal.checkMayLoad(
|
||||
uri, // the thing to check.
|
||||
false, // reportError - we do our own reporting when necessary.
|
||||
allowIfInheritsPrincipal
|
||||
);
|
||||
return true;
|
||||
} catch (ex) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolve partial URLs for a provider.
|
||||
*
|
||||
* Returns nsIURI object or null on failure
|
||||
*
|
||||
* @param {string} url
|
||||
*/
|
||||
resolveUri: function resolveUri(url) {
|
||||
try {
|
||||
let fullURL = this.principal.URI.resolve(url);
|
||||
return Services.io.newURI(fullURL, null, null);
|
||||
} catch (ex) {
|
||||
Cu.reportError("mozSocial: failed to resolve window URL: " + url + "; " + ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,19 +102,18 @@ WorkerAPI.prototype = {
|
||||
case "link":
|
||||
// if there is a url, make it open a tab
|
||||
if (actionArgs.toURL) {
|
||||
try {
|
||||
let pUri = Services.io.newURI(provider.origin, null, null);
|
||||
let nUri = Services.io.newURI(pUri.resolve(actionArgs.toURL),
|
||||
null, null);
|
||||
// fixup
|
||||
if (nUri.scheme != pUri.scheme)
|
||||
nUri.scheme = pUri.scheme;
|
||||
if (nUri.prePath == provider.origin) {
|
||||
let xulWindow = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
xulWindow.openUILinkIn(nUri.spec, "tab");
|
||||
}
|
||||
} catch(e) {
|
||||
Cu.reportError("social.notification-create error: "+e);
|
||||
let uriToOpen = provider.resolveUri(actionArgs.toURL);
|
||||
// Bug 815970 - facebook gives us http:// links even though
|
||||
// the origin is https:// - so we perform a fixup here.
|
||||
let pUri = Services.io.newURI(provider.origin, null, null);
|
||||
if (uriToOpen.scheme != pUri.scheme)
|
||||
uriToOpen.scheme = pUri.scheme;
|
||||
if (provider.isSameOrigin(uriToOpen)) {
|
||||
let xulWindow = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
xulWindow.openUILinkIn(uriToOpen.spec, "tab");
|
||||
} else {
|
||||
Cu.reportError("Not opening notification link " + actionArgs.toURL
|
||||
+ " as not in provider origin");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -6,70 +6,6 @@ const TEST_PROVIDER_ORIGIN = 'http://example.com';
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// A mock notifications server. Based on:
|
||||
// dom/tests/mochitest/notification/notification_common.js
|
||||
const FAKE_CID = Cc["@mozilla.org/uuid-generator;1"].
|
||||
getService(Ci.nsIUUIDGenerator).generateUUID();
|
||||
|
||||
const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1";
|
||||
const ALERTS_SERVICE_CID = Components.ID(Cc[ALERTS_SERVICE_CONTRACT_ID].number);
|
||||
|
||||
function MockAlertsService() {}
|
||||
|
||||
MockAlertsService.prototype = {
|
||||
|
||||
showAlertNotification: function(imageUrl, title, text, textClickable,
|
||||
cookie, alertListener, name) {
|
||||
let obData = JSON.stringify({
|
||||
imageUrl: imageUrl,
|
||||
title: title,
|
||||
text:text,
|
||||
textClickable: textClickable,
|
||||
cookie: cookie,
|
||||
name: name
|
||||
});
|
||||
Services.obs.notifyObservers(null, "social-test:notification-alert", obData);
|
||||
|
||||
if (textClickable) {
|
||||
// probably should do this async....
|
||||
alertListener.observe(null, "alertclickcallback", cookie);
|
||||
}
|
||||
|
||||
alertListener.observe(null, "alertfinished", cookie);
|
||||
},
|
||||
|
||||
QueryInterface: function(aIID) {
|
||||
if (aIID.equals(Ci.nsISupports) ||
|
||||
aIID.equals(Ci.nsIAlertsService))
|
||||
return this;
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
};
|
||||
|
||||
var factory = {
|
||||
createInstance: function(aOuter, aIID) {
|
||||
if (aOuter != null)
|
||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
||||
return new MockAlertsService().QueryInterface(aIID);
|
||||
}
|
||||
};
|
||||
|
||||
function replacePromptService() {
|
||||
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
|
||||
.registerFactory(FAKE_CID, "",
|
||||
ALERTS_SERVICE_CONTRACT_ID,
|
||||
factory)
|
||||
}
|
||||
|
||||
function restorePromptService() {
|
||||
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
|
||||
.registerFactory(ALERTS_SERVICE_CID, "",
|
||||
ALERTS_SERVICE_CONTRACT_ID,
|
||||
null);
|
||||
}
|
||||
// end of alerts service mock.
|
||||
|
||||
|
||||
function ensureProvider(workerFunction, cb) {
|
||||
let manifest = {
|
||||
origin: TEST_PROVIDER_ORIGIN,
|
||||
@ -90,8 +26,8 @@ function test() {
|
||||
let cbPostTest = function(cb) {
|
||||
SocialService.removeProvider(TEST_PROVIDER_ORIGIN, function() {cb()});
|
||||
};
|
||||
replacePromptService();
|
||||
registerCleanupFunction(restorePromptService);
|
||||
replaceAlertsService();
|
||||
registerCleanupFunction(restoreAlertsService);
|
||||
runTests(tests, undefined, cbPostTest);
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,9 @@ let provider;
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
replaceAlertsService();
|
||||
registerCleanupFunction(restoreAlertsService);
|
||||
|
||||
let manifest = {
|
||||
origin: 'http://example.com',
|
||||
name: "Example Provider",
|
||||
@ -144,5 +147,62 @@ let tests = {
|
||||
}
|
||||
}
|
||||
port.postMessage({topic: "test-initialization"});
|
||||
}
|
||||
},
|
||||
|
||||
testNotificationLinks: function(next) {
|
||||
let port = provider.getWorkerPort();
|
||||
let data = {
|
||||
id: 'an id',
|
||||
body: 'the text',
|
||||
action: 'link',
|
||||
actionArgs: {} // will get a toURL elt during the tests...
|
||||
}
|
||||
let testArgs = [
|
||||
// toURL, expectedLocation, expectedWhere]
|
||||
["http://example.com", "http://example.com/", "tab"],
|
||||
// bug 815970 - test that a mis-matched scheme gets patched up.
|
||||
["https://example.com", "http://example.com/", "tab"],
|
||||
// check an off-origin link is not opened.
|
||||
["https://mochitest:8888/", null, null]
|
||||
];
|
||||
|
||||
// we monkey-patch openUILinkIn
|
||||
let oldopenUILinkIn = window.openUILinkIn;
|
||||
registerCleanupFunction(function () {
|
||||
// restore the monkey-patch
|
||||
window.openUILinkIn = oldopenUILinkIn;
|
||||
});
|
||||
let openLocation;
|
||||
let openWhere;
|
||||
window.openUILinkIn = function(location, where) {
|
||||
openLocation = location;
|
||||
openWhere = where;
|
||||
}
|
||||
|
||||
// the testing framework.
|
||||
let toURL, expectedLocation, expectedWhere;
|
||||
function nextTest() {
|
||||
if (testArgs.length == 0) {
|
||||
port.close();
|
||||
next(); // all out of tests!
|
||||
return;
|
||||
}
|
||||
openLocation = openWhere = null;
|
||||
[toURL, expectedLocation, expectedWhere] = testArgs.shift();
|
||||
data.actionArgs.toURL = toURL;
|
||||
port.postMessage({topic: 'test-notification-create', data: data});
|
||||
};
|
||||
|
||||
port.onmessage = function(evt) {
|
||||
if (evt.data.topic == "did-notification-create") {
|
||||
is(openLocation, expectedLocation, "url actually opened was " + openLocation);
|
||||
is(openWhere, expectedWhere, "the url was opened in a " + expectedWhere);
|
||||
nextTest();
|
||||
}
|
||||
}
|
||||
// and kick off the tests.
|
||||
port.postMessage({topic: "test-initialization"});
|
||||
nextTest();
|
||||
},
|
||||
|
||||
};
|
||||
|
@ -59,3 +59,66 @@ function runTests(tests, cbPreTest, cbPostTest, cbFinish) {
|
||||
}
|
||||
runNextTest();
|
||||
}
|
||||
|
||||
// A mock notifications server. Based on:
|
||||
// dom/tests/mochitest/notification/notification_common.js
|
||||
const FAKE_CID = Cc["@mozilla.org/uuid-generator;1"].
|
||||
getService(Ci.nsIUUIDGenerator).generateUUID();
|
||||
|
||||
const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1";
|
||||
const ALERTS_SERVICE_CID = Components.ID(Cc[ALERTS_SERVICE_CONTRACT_ID].number);
|
||||
|
||||
function MockAlertsService() {}
|
||||
|
||||
MockAlertsService.prototype = {
|
||||
|
||||
showAlertNotification: function(imageUrl, title, text, textClickable,
|
||||
cookie, alertListener, name) {
|
||||
let obData = JSON.stringify({
|
||||
imageUrl: imageUrl,
|
||||
title: title,
|
||||
text:text,
|
||||
textClickable: textClickable,
|
||||
cookie: cookie,
|
||||
name: name
|
||||
});
|
||||
Services.obs.notifyObservers(null, "social-test:notification-alert", obData);
|
||||
|
||||
if (textClickable) {
|
||||
// probably should do this async....
|
||||
alertListener.observe(null, "alertclickcallback", cookie);
|
||||
}
|
||||
|
||||
alertListener.observe(null, "alertfinished", cookie);
|
||||
},
|
||||
|
||||
QueryInterface: function(aIID) {
|
||||
if (aIID.equals(Ci.nsISupports) ||
|
||||
aIID.equals(Ci.nsIAlertsService))
|
||||
return this;
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
};
|
||||
|
||||
var factory = {
|
||||
createInstance: function(aOuter, aIID) {
|
||||
if (aOuter != null)
|
||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
||||
return new MockAlertsService().QueryInterface(aIID);
|
||||
}
|
||||
};
|
||||
|
||||
function replaceAlertsService() {
|
||||
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
|
||||
.registerFactory(FAKE_CID, "",
|
||||
ALERTS_SERVICE_CONTRACT_ID,
|
||||
factory)
|
||||
}
|
||||
|
||||
function restoreAlertsService() {
|
||||
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
|
||||
.registerFactory(ALERTS_SERVICE_CID, "",
|
||||
ALERTS_SERVICE_CONTRACT_ID,
|
||||
null);
|
||||
}
|
||||
// end of alerts service mock.
|
||||
|
@ -42,6 +42,11 @@ onconnect = function(e) {
|
||||
// start
|
||||
apiPort.postMessage({topic: 'social.reload-worker'});
|
||||
break;
|
||||
case "test-notification-create":
|
||||
apiPort.postMessage({topic: 'social.notification-create',
|
||||
data: data});
|
||||
testerPort.postMessage({topic: 'did-notification-create'});
|
||||
break;
|
||||
}
|
||||
}
|
||||
// used for "test-reload-worker"
|
||||
|
@ -32,6 +32,8 @@ function run_test() {
|
||||
runner.appendIterator(testGetProviderList(manifests, next));
|
||||
runner.appendIterator(testEnabled(manifests, next));
|
||||
runner.appendIterator(testAddRemoveProvider(manifests, next));
|
||||
runner.appendIterator(testIsSameOrigin(manifests, next));
|
||||
runner.appendIterator(testResolveUri (manifests, next));
|
||||
runner.next();
|
||||
}
|
||||
|
||||
@ -136,3 +138,31 @@ function testAddRemoveProvider(manifests, next) {
|
||||
newProvider = yield SocialService.getProvider(newProvider.origin, next);
|
||||
do_check_true(!newProvider);
|
||||
}
|
||||
|
||||
function testIsSameOrigin(manifests, next) {
|
||||
let providers = yield SocialService.getProviderList(next);
|
||||
let provider = providers[0];
|
||||
// provider.origin is a string.
|
||||
do_check_true(provider.isSameOrigin(provider.origin));
|
||||
do_check_true(provider.isSameOrigin(Services.io.newURI(provider.origin, null, null)));
|
||||
do_check_true(provider.isSameOrigin(provider.origin + "/some-sub-page"));
|
||||
do_check_true(provider.isSameOrigin(Services.io.newURI(provider.origin + "/some-sub-page", null, null)));
|
||||
do_check_false(provider.isSameOrigin("http://something.com"));
|
||||
do_check_false(provider.isSameOrigin(Services.io.newURI("http://something.com", null, null)));
|
||||
do_check_false(provider.isSameOrigin("data:text/html,<p>hi"));
|
||||
do_check_true(provider.isSameOrigin("data:text/html,<p>hi", true));
|
||||
do_check_false(provider.isSameOrigin(Services.io.newURI("data:text/html,<p>hi", null, null)));
|
||||
do_check_true(provider.isSameOrigin(Services.io.newURI("data:text/html,<p>hi", null, null), true));
|
||||
// we explicitly handle null and return false
|
||||
do_check_false(provider.isSameOrigin(null));
|
||||
}
|
||||
|
||||
function testResolveUri(manifests, next) {
|
||||
let providers = yield SocialService.getProviderList(next);
|
||||
let provider = providers[0];
|
||||
do_check_eq(provider.resolveUri(provider.origin).spec, provider.origin + "/");
|
||||
do_check_eq(provider.resolveUri("foo.html").spec, provider.origin + "/foo.html");
|
||||
do_check_eq(provider.resolveUri("/foo.html").spec, provider.origin + "/foo.html");
|
||||
do_check_eq(provider.resolveUri("http://somewhereelse.com/foo.html").spec, "http://somewhereelse.com/foo.html");
|
||||
do_check_eq(provider.resolveUri("data:text/html,<p>hi").spec, "data:text/html,<p>hi");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user