Bug 397878: Send Referer-Root header when doing cross-site access requests. Also update domain pattern matching to spec. Patch by <suryaismail@gmail.com>. r=bent sr=sicking b3a=beltzner

This commit is contained in:
jonas@sicking.cc 2008-01-31 00:16:54 -08:00
parent b7a52d2699
commit ba446696ec
22 changed files with 516 additions and 101 deletions

View File

@ -358,17 +358,8 @@ nsScriptSecurityManager::SecurityCompareURIs(nsIURI* aSourceURI,
{
NS_ENSURE_TRUE(sIOService, PR_FALSE);
PRInt32 defaultPort;
nsCOMPtr<nsIProtocolHandler> protocolHandler;
rv = sIOService->GetProtocolHandler(targetScheme.get(),
getter_AddRefs(protocolHandler));
if (NS_FAILED(rv))
{
return PR_FALSE;
}
rv = protocolHandler->GetDefaultPort(&defaultPort);
if (NS_FAILED(rv) || defaultPort == -1)
PRInt32 defaultPort = NS_GetDefaultPort(targetScheme.get());
if (defaultPort == -1)
return PR_FALSE; // No default port for this scheme
if (sourcePort == -1)

View File

@ -697,6 +697,49 @@ EatChar(nsACString::const_iterator& aIter, nsACString::const_iterator& aEnd,
return PR_FALSE;
}
// Moves aIter forward until it hits a subdomain terminator (* : or whitespace)
// or reaches the end
// access-item ::= (scheme "://")? domain-pattern (":" port)? | "*"
// domain-pattern ::= subdomain | "*." subdomain
// Returns PR_TRUE and updates aIter if a terminator is found.
// PR_FALSE otherwise.
static void
EatSubdomainChars(nsACString::const_iterator& aIter,
nsACString::const_iterator& aEnd)
{
NS_ASSERTION(aIter.get() <= aEnd.get(), "EatSubdomainChars failed");
// Make sure to not allow initial hyphens
if (*aIter == '-') {
return;
}
while (aIter != aEnd) {
unsigned char c = *aIter;
if (c <= 0x2c ||
0x2e <= c && c <= 0x2f ||
0x3a <= c && c <= 0x40 ||
0x5b <= c && c <= 0x60 ||
0x7b <= c && c <= 0x7f) {
return;
}
++aIter;
}
}
static PRBool
ACEEquals(const nsACString &aPattern, const nsCString &domain)
{
if (aPattern.LowerCaseEqualsASCII(domain.get(), domain.Length()))
return PR_TRUE;
// Convert subdomain patern to ACE
nsCString acePattern;
if (!NS_StringToACE(aPattern, acePattern))
return PR_FALSE;
return acePattern.LowerCaseEqualsASCII(domain.get(), domain.Length());
}
PRBool
nsCrossSiteListenerProxy::VerifyAndMatchDomainPattern(const nsACString& aPattern)
{
@ -753,13 +796,8 @@ nsCrossSiteListenerProxy::VerifyAndMatchDomainPattern(const nsACString& aPattern
// subdomain ::= label | subdomain "." label
do {
iter = start;
if (!EatAlpha(iter, end)) {
DENY_AND_RETURN PR_FALSE;
}
while (EatAlpha(iter, end) ||
EatDigit(iter, end) ||
EatChar(iter, end, '-')) {}
EatSubdomainChars(iter, end);
const nsDependentCSubstring& label = Substring(start, iter);
if (label.Last() == '-') {
@ -767,7 +805,7 @@ nsCrossSiteListenerProxy::VerifyAndMatchDomainPattern(const nsACString& aPattern
}
start = iter;
// Save the label
patternSubdomains.AppendElement(label);
} while (EatChar(start, end, '.'));
@ -804,8 +842,9 @@ nsCrossSiteListenerProxy::VerifyAndMatchDomainPattern(const nsACString& aPattern
}
// Check port
if (patternPort != -1 &&
patternPort != NS_GetRealPort(mRequestingURI)) {
if (patternPort == -1 && !patternScheme.IsEmpty())
patternPort = NS_GetDefaultPort(patternScheme.get());
if (patternPort != -1 && patternPort != NS_GetRealPort(mRequestingURI)) {
return PR_FALSE;
}
@ -815,17 +854,57 @@ nsCrossSiteListenerProxy::VerifyAndMatchDomainPattern(const nsACString& aPattern
do {
--patternPos;
--reqPos;
if (!patternSubdomains[patternPos].LowerCaseEqualsASCII(
mReqSubdomains[reqPos].get(), mReqSubdomains[reqPos].Length())) {
if (!ACEEquals(patternSubdomains[patternPos], mReqSubdomains[reqPos])) {
return PR_FALSE;
}
} while (patternPos > 0 && reqPos > 0);
// Only matches if we've matched all of pattern and either matched all of
// mRequestingURI and there's no wildcard, or there's a wildcard and there
// are still elements of mRequestingURI left.
// Only matches if we've matched all of pattern and, if there is a wildcard, there
// is at least one more entry in mReqSubdomains.
return patternPos == 0 &&
((reqPos == 0 && !patternHasWild) ||
(reqPos != 0 && patternHasWild));
(!patternHasWild || reqPos >= 1);
}
/* static */
nsresult
nsCrossSiteListenerProxy::AddRequestHeaders(nsIChannel* aChannel,
nsIPrincipal* aRequestingPrincipal)
{
// Once bug 386823 is fixed this could just be an assertion.
NS_ENSURE_TRUE(aRequestingPrincipal, NS_ERROR_FAILURE);
// Work out the requesting URI
nsCOMPtr<nsIURI> uri;
nsresult rv = aRequestingPrincipal->GetURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
nsCString scheme, host;
rv = uri->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
rv = uri->GetAsciiHost(host);
NS_ENSURE_SUCCESS(rv, rv);
nsCString root = scheme + NS_LITERAL_CSTRING("://") + host +
NS_LITERAL_CSTRING(":");
// If needed, append the default port
PRInt32 port;
uri->GetPort(&port);
if (port == -1) {
port = NS_GetDefaultPort(scheme.get());
if (port == -1) {
return NS_ERROR_DOM_BAD_URI;
}
}
root.AppendInt(port);
// Now add the referer-root header
nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
NS_ENSURE_TRUE(http, NS_ERROR_FAILURE);
return http->SetRequestHeader(NS_LITERAL_CSTRING("Referer-Root"),
root, PR_FALSE);
}

View File

@ -62,6 +62,9 @@ public:
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIEXPATSINK
static nsresult AddRequestHeaders(nsIChannel* aChannel,
nsIPrincipal* aRequestingPrincipal);
// nsIContentSink
NS_IMETHOD WillTokenize(void) { return NS_OK; }
NS_IMETHOD WillBuildModel(void);

View File

@ -1413,7 +1413,9 @@ nsXMLHttpRequest::CheckChannelForCrossSiteRequest()
nsCString userpass;
channelURI->GetUserPass(userpass);
return userpass.IsEmpty() ? NS_OK : NS_ERROR_DOM_BAD_URI;
NS_ENSURE_TRUE(userpass.IsEmpty(), NS_ERROR_DOM_BAD_URI);
return nsCrossSiteListenerProxy::AddRequestHeaders(mChannel, mPrincipal);
}
/* noscript void openRequest (in AUTF8String method, in AUTF8String url, in boolean async, in AString user, in AString password); */
@ -1563,6 +1565,8 @@ nsXMLHttpRequest::OpenRequest(const nsACString& method,
nsnull, loadFlags);
NS_ENSURE_SUCCESS(rv, rv);
rv = nsCrossSiteListenerProxy::AddRequestHeaders(mACGetChannel, mPrincipal);
nsCOMPtr<nsIHttpChannel> acHttp = do_QueryInterface(mACGetChannel);
NS_ASSERTION(acHttp, "Failed to QI to nsIHttpChannel!");

View File

@ -109,6 +109,21 @@ _TEST_FILES = test_bug5141.html \
file_CrossSiteXHR_pass1.xml^headers^ \
file_CrossSiteXHR_pass2.xml \
file_CrossSiteXHR_pass3.xml \
test_CrossSiteXHR2.html \
file_CrossSiteXHR2_inner.html \
file_CrossSiteXHR2_fail1.xml \
file_CrossSiteXHR2_fail1.xml^headers^ \
file_CrossSiteXHR2_fail2.xml \
file_CrossSiteXHR2_pass1.xml \
file_CrossSiteXHR2_pass1.xml^headers^ \
file_CrossSiteXHR2_pass2.xml \
file_CrossSiteXHR2_pass3.xml \
file_CrossSiteXHR2_pass3.xml^headers^ \
file_CrossSiteXHR2_pass3_redirect.xml \
test_CrossSiteXHR3.html \
file_CrossSiteXHR3_inner.html \
file_CrossSiteXHR3_pass1.xml^headers^ \
file_CrossSiteXHR3_pass1.xml \
test_bug326337.html \
file_bug326337_inner.html \
file_bug326337_outer.html \

View File

@ -0,0 +1 @@
<res>hello</res>

View File

@ -0,0 +1,2 @@
HTTP 200 OK
Access-Control: allow <http://sub1.xn--lt-uia.example.org.example.org>

View File

@ -0,0 +1,2 @@
<?access-control allow="http://ält.example.org"?>
<res>hello</res>

View File

@ -0,0 +1,104 @@
<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
<script class="testbody" type="text/javascript">
function doFail(msg)
{
document.write(msg + "<p>");
parent.location.hash = "#fail";
throw 1;
}
var local = "http://example.com/tests/content/base/test/";
var passFiles = [[local + 'file_CrossSiteXHR2_pass1.xml', 'GET'],
[local + 'file_CrossSiteXHR2_pass2.xml', 'GET'],
[local + 'file_CrossSiteXHR2_pass3.xml', 'GET']
];
var failFiles = [[local + 'file_CrossSiteXHR2_fail1.xml', 'GET'],
[local + 'file_CrossSiteXHR2_fail2.xml', 'GET']
];
netscape.security.PrivilegeManager.enablePrivilege(
"UniversalXPConnect UniversalBrowserWrite");
for (i = 0; i < passFiles.length; ++i)
{
xhr = new XMLHttpRequest();
if (!xhr)
doFail("Couldn't open request : " + passFiles[i][0]);
try {
xhr.open(passFiles[i][1], passFiles[i][0], false);
}
catch(e) {
doFail("Open failed : " + passFiles[i][0]);
}
// Do the send
try {
xhr.send(null);
}
catch(e) {
doFail("Sending failed : " + passFiles[i][0]);
}
// Check the Referer-Root
var channel =
xhr.channel.QueryInterface(Components.interfaces.nsIHttpChannel);
var value = null;
try {
value = channel.getRequestHeader("Referer-Root");
}
catch(e) {
doFail("Getting request header failed : " + passFiles[i][0]);
}
if (value != "http://sub1.xn--lt-uia.example.org:8000")
doFail("Referer-root incorrect : " + value);
// Check the response
if (xhr.status != "200") {
doFail("Status incorrect : " + passFiles[i][0]);
}
if (xhr.responseXML) {
responseString = (new XMLSerializer()).serializeToString(
xhr.responseXML.documentElement);
if (responseString != "<res>hello</res>") {
doFail("Response incorrect : " + passFiles[i][0]);
}
}
else {
doFail("No response : " + passFiles[i][0]);
}
}
for (i = 0; i < failFiles.length; ++i)
{
xhr = new XMLHttpRequest();
if (!xhr)
doFail("Couldn't open request : " + passFiles[i][0]);
success = false;
try {
xhr.open(failFiles[i][1], failFiles[i][0], false);
xhr.send(null);
}
catch (e) {
success = true;
}
if (!success) {
doFail("Test did not fail : " + failFiles[i][0]);
}
}
document.write("Success!");
parent.location.hash = "#done";
</script>
</body>
</html>

View File

@ -0,0 +1 @@
<res>hello</res>

View File

@ -0,0 +1,2 @@
HTTP 200 OK
Access-Control: allow <http://sub1.xn--lt-uia.example.org:8000>

View File

@ -0,0 +1,2 @@
<?access-control allow="http://sub1.ält.example.org:8000"?>
<res>hello</res>

View File

@ -0,0 +1 @@
<res>wrong, should redirect</res>

View File

@ -0,0 +1,2 @@
HTTP 301 Moved Permanently
Location: http://test1.example.org/tests/content/base/test/file_CrossSiteXHR2_pass3_redirect.xml

View File

@ -0,0 +1,2 @@
<?access-control allow="http://sub1.ält.example.org:8000"?>
<res>hello</res>

View File

@ -0,0 +1,98 @@
<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
<script class="testbody" type="text/javascript">
function doFail(msg)
{
document.write(msg + "<p>");
parent.location.hash = "#fail";
throw 1;
}
var local = "http://example.com/tests/content/base/test/";
var passFiles = [[local + 'file_CrossSiteXHR3_pass1.xml', 'GET']];
var failFiles = [];
netscape.security.PrivilegeManager.enablePrivilege(
"UniversalXPConnect UniversalBrowserWrite");
for (i = 0; i < passFiles.length; ++i)
{
xhr = new XMLHttpRequest();
if (!xhr)
doFail("Couldn't open request : " + passFiles[i][0]);
try {
xhr.open(passFiles[i][1], passFiles[i][0], false);
}
catch(e) {
doFail("Open failed : " + passFiles[i][0]);
}
// Do the send
try {
xhr.send(null);
}
catch(e) {
doFail("Sending failed : " + passFiles[i][0]);
}
// Check the Referer-Root
var channel =
xhr.channel.QueryInterface(Components.interfaces.nsIHttpChannel);
var value = null;
try {
value = channel.getRequestHeader("Referer-Root");
}
catch(e) {
doFail("Getting request header failed : " + passFiles[i][0]);
}
if (value != "http://sub2.xn--lt-uia.example.org:80")
doFail("Referer-root incorrect : " + value);
// Check the response
if (xhr.status != "200") {
doFail("Status incorrect : " + passFiles[i][0]);
}
if (xhr.responseXML) {
responseString = (new XMLSerializer()).serializeToString(xhr.responseXML.documentElement);
if (responseString != "<res>hello</res>") {
doFail("Response incorrect : " + passFiles[i][0]);
}
}
else {
doFail("No response : " + passFiles[i][0]);
}
}
for (i = 0; i < failFiles.length; ++i)
{
xhr = new XMLHttpRequest();
if (!xhr)
doFail("Couldn't open request : " + passFiles[i][0]);
success = false;
try {
xhr.open(failFiles[i][1], failFiles[i][0], false);
xhr.send(null);
}
catch (e) {
success = true;
}
if (!success) {
doFail("Test did not fail : " + failFiles[i][0]);
}
}
document.write("Success!");
parent.location.hash = "#done";
</script>
</body>
</html>

View File

@ -0,0 +1 @@
<res>hello</res>

View File

@ -0,0 +1,2 @@
HTTP 200 OK
Access-Control: allow <http://sub2.xn--lt-uia.example.org>

View File

@ -1,60 +1,60 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Cross Site XMLHttpRequest</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
var local = "http://example.com/tests/content/base/test/";
var passFiles = [[local + 'file_CrossSiteXHR_pass1.xml', 'GET'],
[local + 'file_CrossSiteXHR_pass2.xml', 'GET'],
[local + 'file_CrossSiteXHR_pass3.xml', 'GET'],
];
var failFiles = [[local + 'file_CrossSiteXHR_fail1.xml', 'GET'],
[local + 'file_CrossSiteXHR_fail2.xml', 'GET'],
[local + 'file_CrossSiteXHR_fail3.xml', 'GET'],
[local + 'file_CrossSiteXHR_fail4.xml', 'GET'],
];
for (i = 0; i < passFiles.length; ++i) {
xhr = new XMLHttpRequest();
xhr.open(passFiles[i][1], passFiles[i][0], false);
xhr.send(null);
is(xhr.status, 200, "wrong status");
if (xhr.responseXML) {
is((new XMLSerializer()).serializeToString(xhr.responseXML.documentElement),
"<res>hello</res>",
"wrong response");
}
else {
is(xhr.responseText, "hello pass\n", "wrong response");
}
}
for (i = 0; i < failFiles.length; ++i) {
xhr = new XMLHttpRequest();
success = false;
try {
xhr.open(failFiles[i][1], failFiles[i][0], false);
xhr.send(null);
}
catch (e) {
success = true;
}
ok(success, "should have thrown");
}
</script>
</pre>
</body>
</html>
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Cross Site XMLHttpRequest</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
var local = "http://example.com/tests/content/base/test/";
var passFiles = [[local + 'file_CrossSiteXHR_pass1.xml', 'GET'],
[local + 'file_CrossSiteXHR_pass2.xml', 'GET'],
[local + 'file_CrossSiteXHR_pass3.xml', 'GET'],
];
var failFiles = [[local + 'file_CrossSiteXHR_fail1.xml', 'GET'],
[local + 'file_CrossSiteXHR_fail2.xml', 'GET'],
[local + 'file_CrossSiteXHR_fail3.xml', 'GET'],
[local + 'file_CrossSiteXHR_fail4.xml', 'GET'],
];
for (i = 0; i < passFiles.length; ++i) {
xhr = new XMLHttpRequest();
xhr.open(passFiles[i][1], passFiles[i][0], false);
xhr.send(null);
is(xhr.status, 200, "wrong status");
if (xhr.responseXML) {
is((new XMLSerializer()).serializeToString(xhr.responseXML.documentElement),
"<res>hello</res>",
"wrong response");
}
else {
is(xhr.responseText, "hello pass\n", "wrong response");
}
}
for (i = 0; i < failFiles.length; ++i) {
xhr = new XMLHttpRequest();
success = false;
try {
xhr.open(failFiles[i][1], failFiles[i][0], false);
xhr.send(null);
}
catch (e) {
success = true;
}
ok(success, "should have thrown");
}
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,37 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Cross Site XMLHttpRequest with IDN</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript"
src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<iframe id="inner" src="http://sub1.xn--lt-uia.example.org:8000/tests/content/base/test/file_CrossSiteXHR2_inner.html">
</iframe>
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
var t = setInterval(doCheck, 300);
function doCheck()
{
if (location.hash) {
clearInterval(t);
is(location.hash, "#done", "failed to run");
SimpleTest.finish();
}
}
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,37 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Cross Site XMLHttpRequest with Referer-Root</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript"
src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<iframe id="inner" src="http://sub2.xn--lt-uia.example.org/tests/content/base/test/file_CrossSiteXHR3_inner.html">
</iframe>
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
var t = setInterval(doCheck, 300);
function doCheck()
{
if (location.hash) {
clearInterval(t);
is(location.hash, "#done", "failed to run");
SimpleTest.finish();
}
}
</script>
</pre>
</body>
</html>

View File

@ -93,6 +93,7 @@
#include "nsINestedURI.h"
#include "nsIMutable.h"
#include "nsIPropertyBag2.h"
#include "nsIIDNService.h"
// Helper, to simplify getting the I/O service.
inline const nsGetServiceByContractIDWithError
@ -299,6 +300,46 @@ NS_MakeAbsoluteURI(nsAString &result,
return rv;
}
/**
* This function is a helper function to get a scheme's default port.
*/
inline PRInt32
NS_GetDefaultPort(const char *scheme,
nsIIOService* ioService = nsnull)
{
nsresult rv;
nsCOMPtr<nsIIOService> grip;
net_EnsureIOService(&ioService, grip);
if (!ioService)
return -1;
nsCOMPtr<nsIProtocolHandler> handler;
rv = ioService->GetProtocolHandler(scheme, getter_AddRefs(handler));
if (NS_FAILED(rv))
return -1;
PRInt32 port;
rv = handler->GetDefaultPort(&port);
return NS_SUCCEEDED(rv) ? port : -1;
}
/**
* This function is a helper function to apply the ToAscii conversion
* to a string
*/
inline PRBool
NS_StringToACE(const nsACString &idn, nsACString &result)
{
nsCOMPtr<nsIIDNService> idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID);
if (!idnSrv)
return PR_FALSE;
nsresult rv = idnSrv->ConvertUTF8toACE(idn, result);
if (NS_FAILED(rv))
return PR_FALSE;
return PR_TRUE;
}
/**
* This function is a helper function to get a protocol's default port if the
* URI does not specify a port explicitly. Returns -1 if this protocol has no
@ -324,19 +365,7 @@ NS_GetRealPort(nsIURI* aURI,
if (NS_FAILED(rv))
return -1;
nsCOMPtr<nsIIOService> grip;
rv = net_EnsureIOService(&ioService, grip);
if (!ioService)
return -1;
nsCOMPtr<nsIProtocolHandler> handler;
rv = ioService->GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
if (NS_FAILED(rv))
return -1;
NS_ASSERTION(handler, "IO Service lied");
rv = handler->GetDefaultPort(&port);
return NS_SUCCEEDED(rv) ? port : -1;
return NS_GetDefaultPort(scheme.get());
}
inline nsresult