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 08:17:47 +00:00
Родитель 7fe6f87087
Коммит 98d1136fea
22 изменённых файлов: 401 добавлений и 101 удалений

Просмотреть файл

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

Просмотреть файл

@ -697,6 +697,49 @@ EatChar(nsACString::const_iterator& aIter, nsACString::const_iterator& aEnd,
return PR_FALSE; 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 PRBool
nsCrossSiteListenerProxy::VerifyAndMatchDomainPattern(const nsACString& aPattern) nsCrossSiteListenerProxy::VerifyAndMatchDomainPattern(const nsACString& aPattern)
{ {
@ -753,13 +796,8 @@ nsCrossSiteListenerProxy::VerifyAndMatchDomainPattern(const nsACString& aPattern
// subdomain ::= label | subdomain "." label // subdomain ::= label | subdomain "." label
do { do {
iter = start; iter = start;
if (!EatAlpha(iter, end)) {
DENY_AND_RETURN PR_FALSE;
}
while (EatAlpha(iter, end) || EatSubdomainChars(iter, end);
EatDigit(iter, end) ||
EatChar(iter, end, '-')) {}
const nsDependentCSubstring& label = Substring(start, iter); const nsDependentCSubstring& label = Substring(start, iter);
if (label.Last() == '-') { if (label.Last() == '-') {
@ -767,7 +805,7 @@ nsCrossSiteListenerProxy::VerifyAndMatchDomainPattern(const nsACString& aPattern
} }
start = iter; start = iter;
// Save the label // Save the label
patternSubdomains.AppendElement(label); patternSubdomains.AppendElement(label);
} while (EatChar(start, end, '.')); } while (EatChar(start, end, '.'));
@ -804,8 +842,9 @@ nsCrossSiteListenerProxy::VerifyAndMatchDomainPattern(const nsACString& aPattern
} }
// Check port // Check port
if (patternPort != -1 && if (patternPort == -1 && !patternScheme.IsEmpty())
patternPort != NS_GetRealPort(mRequestingURI)) { patternPort = NS_GetDefaultPort(patternScheme.get());
if (patternPort != -1 && patternPort != NS_GetRealPort(mRequestingURI)) {
return PR_FALSE; return PR_FALSE;
} }
@ -815,17 +854,57 @@ nsCrossSiteListenerProxy::VerifyAndMatchDomainPattern(const nsACString& aPattern
do { do {
--patternPos; --patternPos;
--reqPos; --reqPos;
if (!patternSubdomains[patternPos].LowerCaseEqualsASCII( if (!ACEEquals(patternSubdomains[patternPos], mReqSubdomains[reqPos])) {
mReqSubdomains[reqPos].get(), mReqSubdomains[reqPos].Length())) {
return PR_FALSE; return PR_FALSE;
} }
} while (patternPos > 0 && reqPos > 0); } while (patternPos > 0 && reqPos > 0);
// Only matches if we've matched all of pattern and either matched all of // Only matches if we've matched all of pattern and, if there is a wildcard, there
// mRequestingURI and there's no wildcard, or there's a wildcard and there // is at least one more entry in mReqSubdomains.
// are still elements of mRequestingURI left.
return patternPos == 0 && return patternPos == 0 &&
((reqPos == 0 && !patternHasWild) || (!patternHasWild || reqPos >= 1);
(reqPos != 0 && patternHasWild)); }
/* 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);
} }

Просмотреть файл

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

Просмотреть файл

@ -1413,7 +1413,9 @@ nsXMLHttpRequest::CheckChannelForCrossSiteRequest()
nsCString userpass; nsCString userpass;
channelURI->GetUserPass(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); */ /* 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); nsnull, loadFlags);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
rv = nsCrossSiteListenerProxy::AddRequestHeaders(mACGetChannel, mPrincipal);
nsCOMPtr<nsIHttpChannel> acHttp = do_QueryInterface(mACGetChannel); nsCOMPtr<nsIHttpChannel> acHttp = do_QueryInterface(mACGetChannel);
NS_ASSERTION(acHttp, "Failed to QI to nsIHttpChannel!"); NS_ASSERTION(acHttp, "Failed to QI to nsIHttpChannel!");

Просмотреть файл

@ -109,6 +109,21 @@ _TEST_FILES = test_bug5141.html \
file_CrossSiteXHR_pass1.xml^headers^ \ file_CrossSiteXHR_pass1.xml^headers^ \
file_CrossSiteXHR_pass2.xml \ file_CrossSiteXHR_pass2.xml \
file_CrossSiteXHR_pass3.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 \ test_bug326337.html \
file_bug326337_inner.html \ file_bug326337_inner.html \
file_bug326337_outer.html \ file_bug326337_outer.html \

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -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>

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -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>

Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -1,60 +0,0 @@
<!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>

Просмотреть файл

Просмотреть файл

@ -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>

Просмотреть файл

@ -93,6 +93,7 @@
#include "nsINestedURI.h" #include "nsINestedURI.h"
#include "nsIMutable.h" #include "nsIMutable.h"
#include "nsIPropertyBag2.h" #include "nsIPropertyBag2.h"
#include "nsIIDNService.h"
// Helper, to simplify getting the I/O service. // Helper, to simplify getting the I/O service.
inline const nsGetServiceByContractIDWithError inline const nsGetServiceByContractIDWithError
@ -299,6 +300,46 @@ NS_MakeAbsoluteURI(nsAString &result,
return rv; 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 * 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 * 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)) if (NS_FAILED(rv))
return -1; return -1;
nsCOMPtr<nsIIOService> grip; return NS_GetDefaultPort(scheme.get());
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;
} }
inline nsresult inline nsresult