Reland old Access-Control implementation. b=389508 r/sr=jst

This commit is contained in:
Jonas Sicking 2008-09-30 17:49:30 -07:00
Родитель 32d3012ef7
Коммит f9b484d02c
10 изменённых файлов: 1709 добавлений и 96 удалений

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

@ -120,6 +120,7 @@ CPPSRCS = \
nsContentSink.cpp \
nsContentUtils.cpp \
nsCopySupport.cpp \
nsCrossSiteListenerProxy.cpp \
nsDataDocumentContentPolicy.cpp \
nsDOMAttribute.cpp \
nsDOMAttributeMap.cpp \

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

@ -0,0 +1,969 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Jonas Sicking <jonas@sicking.cc> (Original Author)
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsCrossSiteListenerProxy.h"
#include "nsIChannel.h"
#include "nsIHttpChannel.h"
#include "nsDOMError.h"
#include "nsContentUtils.h"
#include "nsIScriptSecurityManager.h"
#include "nsNetUtil.h"
#include "nsIParser.h"
#include "nsParserCIID.h"
#include "nsICharsetAlias.h"
#include "nsMimeTypes.h"
#include "nsIStreamConverterService.h"
#include "nsStringStream.h"
#include "nsParserUtils.h"
#include "nsGkAtoms.h"
#include "nsWhitespaceTokenizer.h"
#include "nsIChannelEventSink.h"
static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID);
NS_IMPL_ISUPPORTS7(nsCrossSiteListenerProxy, nsIStreamListener,
nsIRequestObserver, nsIContentSink, nsIXMLContentSink,
nsIExpatSink, nsIChannelEventSink, nsIInterfaceRequestor)
nsCrossSiteListenerProxy::nsCrossSiteListenerProxy(nsIStreamListener* aOuter,
nsIPrincipal* aRequestingPrincipal,
nsIChannel* aChannel,
nsresult* aResult)
: mOuterListener(aOuter),
mRequestingPrincipal(aRequestingPrincipal),
mAcceptState(eNotSet),
mHasForwardedRequest(PR_FALSE),
mHasBeenCrossSite(PR_FALSE)
{
aRequestingPrincipal->GetURI(getter_AddRefs(mRequestingURI));
aChannel->GetNotificationCallbacks(getter_AddRefs(mOuterNotificationCallbacks));
aChannel->SetNotificationCallbacks(this);
*aResult = UpdateChannel(aChannel);
}
nsresult
nsCrossSiteListenerProxy::ForwardRequest(PRBool aFromStop)
{
if (mHasForwardedRequest) {
return NS_OK;
}
mHasForwardedRequest = PR_TRUE;
if (mParser) {
mParser->Terminate();
mParser = nsnull;
mParserListener = nsnull;
}
if (mAcceptState != eAccept) {
mAcceptState = eDeny;
mOuterRequest->Cancel(NS_ERROR_DOM_BAD_URI);
mOuterListener->OnStartRequest(mOuterRequest, mOuterContext);
// Only call OnStopRequest here if we were called from OnStopRequest.
// Otherwise the call to Cancel will make us get an OnStopRequest later
// so we'll forward OnStopRequest then.
if (aFromStop) {
mOuterListener->OnStopRequest(mOuterRequest, mOuterContext,
NS_ERROR_DOM_BAD_URI);
}
// Clear this data just in case since it should never be forwarded.
mStoredData.Truncate();
return NS_ERROR_DOM_BAD_URI;
}
nsresult rv = mOuterListener->OnStartRequest(mOuterRequest, mOuterContext);
NS_ENSURE_SUCCESS(rv, rv);
if (!mStoredData.IsEmpty()) {
nsCOMPtr<nsIInputStream> stream;
rv = NS_NewCStringInputStream(getter_AddRefs(stream), mStoredData);
NS_ENSURE_SUCCESS(rv, rv);
rv = mOuterListener->OnDataAvailable(mOuterRequest, mOuterContext, stream,
0, mStoredData.Length());
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
NS_IMETHODIMP
nsCrossSiteListenerProxy::OnStartRequest(nsIRequest* aRequest,
nsISupports* aContext)
{
mOuterRequest = aRequest;
mOuterContext = aContext;
// Check if the request failed
nsresult status;
nsresult rv = aRequest->GetStatus(&status);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_FAILED(status)) {
mAcceptState = eDeny;
return ForwardRequest(PR_FALSE);
}
// Check if this was actually a cross domain request
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
if (!channel) {
return NS_ERROR_DOM_BAD_URI;
}
nsCOMPtr<nsIURI> finalURI;
channel->GetURI(getter_AddRefs(finalURI));
if (!mHasBeenCrossSite) {
mAcceptState = eAccept;
return ForwardRequest(PR_FALSE);
}
nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(channel);
if (http) {
PRBool succeeded;
rv = http->GetRequestSucceeded(&succeeded);
NS_ENSURE_SUCCESS(rv, rv);
if (!succeeded) {
mAcceptState = eDeny;
return ForwardRequest(PR_FALSE);
}
}
// Get the list of subdomains out of mRequestingURI
nsCString host;
rv = mRequestingURI->GetAsciiHost(host);
NS_ENSURE_SUCCESS(rv, rv);
PRInt32 nextDot, currDot = 0;
while ((nextDot = host.FindChar('.', currDot)) != -1) {
mReqSubdomains.AppendElement(Substring(host, currDot, nextDot - currDot));
currDot = nextDot + 1;
}
mReqSubdomains.AppendElement(Substring(host, currDot));
// Check the Access-Control header
if (http) {
nsCAutoString ac;
rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control"), ac);
if (NS_SUCCEEDED(rv)) {
CheckHeader(ac);
}
}
if (mAcceptState == eDeny) {
return ForwardRequest(PR_FALSE);
}
// Set up a parser with us as a sink to look for PIs
mParser = do_CreateInstance(kCParserCID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
mParserListener = do_QueryInterface(mParser);
mParser->SetCommand(kLoadAsData);
mParser->SetContentSink(this);
mParser->Parse(finalURI);
// check channel's charset...
nsCAutoString charset(NS_LITERAL_CSTRING("UTF-8"));
PRInt32 charsetSource = kCharsetFromDocTypeDefault;
nsCAutoString charsetVal;
rv = channel->GetContentCharset(charsetVal);
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsICharsetAlias> calias =
do_GetService(NS_CHARSETALIAS_CONTRACTID);
if (calias) {
nsCAutoString preferred;
rv = calias->GetPreferred(charsetVal, preferred);
if (NS_SUCCEEDED(rv)) {
charset = preferred;
charsetSource = kCharsetFromChannel;
}
}
}
mParser->SetDocumentCharset(charset, charsetSource);
nsCAutoString contentType;
channel->GetContentType(contentType);
// Time to sniff! Note: this should go away once file channels do
// sniffing themselves.
PRBool sniff;
if (NS_SUCCEEDED(finalURI->SchemeIs("file", &sniff)) && sniff &&
contentType.Equals(UNKNOWN_CONTENT_TYPE)) {
nsCOMPtr<nsIStreamConverterService> serv =
do_GetService("@mozilla.org/streamConverters;1", &rv);
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIStreamListener> converter;
rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE,
"*/*",
mParserListener,
aContext,
getter_AddRefs(converter));
if (NS_SUCCEEDED(rv)) {
mParserListener = converter;
}
}
}
// Hold a local reference to make sure the parser doesn't go away
nsCOMPtr<nsIStreamListener> stackedListener = mParserListener;
return stackedListener->OnStartRequest(aRequest, aContext);
}
NS_IMETHODIMP
nsCrossSiteListenerProxy::OnStopRequest(nsIRequest* aRequest,
nsISupports* aContext,
nsresult aStatusCode)
{
if (mHasForwardedRequest) {
return mOuterListener->OnStopRequest(aRequest, aContext, aStatusCode);
}
mAcceptState = eDeny;
return ForwardRequest(PR_TRUE);
}
NS_METHOD
StringSegmentWriter(nsIInputStream *aInStream,
void *aClosure,
const char *aFromSegment,
PRUint32 aToOffset,
PRUint32 aCount,
PRUint32 *aWriteCount)
{
nsCString* dest = static_cast<nsCString*>(aClosure);
dest->Append(aFromSegment, aCount);
*aWriteCount = aCount;
return NS_OK;
}
NS_IMETHODIMP
nsCrossSiteListenerProxy::OnDataAvailable(nsIRequest* aRequest,
nsISupports* aContext,
nsIInputStream* aInputStream,
PRUint32 aOffset,
PRUint32 aCount)
{
if (mHasForwardedRequest) {
if (mAcceptState != eAccept) {
return NS_ERROR_DOM_BAD_URI;
}
return mOuterListener->OnDataAvailable(aRequest, aContext, aInputStream,
aOffset, aCount);
}
NS_ASSERTION(mStoredData.Length() == aOffset,
"Stored wrong amount of data");
PRUint32 read;
nsresult rv = aInputStream->ReadSegments(StringSegmentWriter, &mStoredData,
aCount, &read);
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(read == aCount, "didn't store all of the stream");
nsCOMPtr<nsIInputStream> stream;
rv = NS_NewCStringInputStream(getter_AddRefs(stream),
Substring(mStoredData, aOffset));
NS_ENSURE_SUCCESS(rv, rv);
// Hold a local reference to make sure the parser doesn't go away
nsCOMPtr<nsIStreamListener> stackedListener = mParserListener;
rv = stackedListener->OnDataAvailable(aRequest, aContext, stream, aOffset,
aCount);
// When we forward the request we also terminate the parsing which will
// result in an error bubbling up to here. We want to ignore the error
// in that case.
if (mHasForwardedRequest) {
rv = mAcceptState == eAccept ? NS_OK : NS_ERROR_DOM_BAD_URI;
}
return rv;
}
NS_IMETHODIMP
nsCrossSiteListenerProxy::HandleStartElement(const PRUnichar *aName,
const PRUnichar **aAtts,
PRUint32 aAttsCount,
PRInt32 aIndex,
PRUint32 aLineNumber)
{
// We're done processing the prolog.
ForwardRequest(PR_FALSE);
// Stop the parser since we don't want to spend more cycles on parsing
// stuff.
return NS_ERROR_HTMLPARSER_STOPPARSING;
}
NS_IMETHODIMP
nsCrossSiteListenerProxy::HandleEndElement(const PRUnichar *aName)
{
NS_ASSERTION(mHasForwardedRequest, "Should have forwarded request");
return NS_OK;
}
NS_IMETHODIMP
nsCrossSiteListenerProxy::HandleComment(const PRUnichar *aName)
{
return NS_OK;
}
NS_IMETHODIMP
nsCrossSiteListenerProxy::HandleCDataSection(const PRUnichar *aData,
PRUint32 aLength)
{
NS_ASSERTION(mHasForwardedRequest, "Should have forwarded request");
return NS_OK;
}
NS_IMETHODIMP
nsCrossSiteListenerProxy::HandleDoctypeDecl(const nsAString & aSubset,
const nsAString & aName,
const nsAString & aSystemId,
const nsAString & aPublicId,
nsISupports *aCatalogData)
{
return NS_OK;
}
NS_IMETHODIMP
nsCrossSiteListenerProxy::HandleCharacterData(const PRUnichar *aData,
PRUint32 aLength)
{
return NS_OK;
}
NS_IMETHODIMP
nsCrossSiteListenerProxy::HandleProcessingInstruction(const PRUnichar *aTarget,
const PRUnichar *aData)
{
if (mHasForwardedRequest ||
!NS_LITERAL_STRING("access-control").Equals(aTarget)) {
return NS_OK;
}
nsDependentString data(aData);
PRBool seenType = PR_FALSE, seenExclude = PR_FALSE;
PRBool ruleIsAllow = PR_FALSE;
nsAutoString itemList, excludeList;
PRUint32 i;
for (i = 0;; ++i) {
nsAutoString attrName;
if (nsParserUtils::GetQuotedAttrNameAt(data, i, attrName) &&
attrName.IsEmpty()) {
break;
}
nsCOMPtr<nsIAtom> attr = do_GetAtom(attrName);
PRBool res;
if (!seenType && attrName.EqualsLiteral("allow")) {
seenType = PR_TRUE;
ruleIsAllow = PR_TRUE;
res = nsParserUtils::GetQuotedAttributeValue(data, attr, itemList);
}
else if (!seenType && attrName.EqualsLiteral("deny")) {
seenType = PR_TRUE;
ruleIsAllow = PR_FALSE;
res = nsParserUtils::GetQuotedAttributeValue(data, attr, itemList);
}
else if (!seenExclude && attrName.EqualsLiteral("exclude")) {
seenExclude = PR_TRUE;
res = nsParserUtils::GetQuotedAttributeValue(data, attr, excludeList);
}
else {
res = PR_FALSE;
}
if (!res) {
// parsing attribute value failed or unknown/duplicated attribute
mAcceptState = eDeny;
return ForwardRequest(PR_FALSE);
}
}
PRBool matchesRule = PR_FALSE;
nsWhitespaceTokenizer itemTok(itemList);
if (!itemTok.hasMoreTokens()) {
mAcceptState = eDeny;
return ForwardRequest(PR_FALSE);
}
while (itemTok.hasMoreTokens()) {
// Order is important here since we always want to call the function
matchesRule = VerifyAndMatchDomainPattern(
NS_ConvertUTF16toUTF8(itemTok.nextToken())) || matchesRule;
}
nsWhitespaceTokenizer excludeTok(excludeList);
while (excludeTok.hasMoreTokens()) {
// Order is important here since we always want to call the function
matchesRule = !VerifyAndMatchDomainPattern(
NS_ConvertUTF16toUTF8(excludeTok.nextToken())) && matchesRule;
}
if (matchesRule && mAcceptState != eDeny) {
mAcceptState = ruleIsAllow ? eAccept : eDeny;
}
if (mAcceptState == eDeny) {
return ForwardRequest(PR_FALSE);
}
return NS_OK;
}
NS_IMETHODIMP
nsCrossSiteListenerProxy::HandleXMLDeclaration(const PRUnichar *aVersion,
const PRUnichar *aEncoding,
PRInt32 aStandalone)
{
return NS_OK;
}
NS_IMETHODIMP
nsCrossSiteListenerProxy::ReportError(const PRUnichar *aErrorText,
const PRUnichar *aSourceText,
nsIScriptError *aError,
PRBool *_retval)
{
if (!mHasForwardedRequest) {
mAcceptState = eDeny;
return ForwardRequest(PR_FALSE);
}
return NS_OK;
}
NS_IMETHODIMP
nsCrossSiteListenerProxy::WillBuildModel()
{
nsCOMPtr<nsIDTD> dtd;
mParser->GetDTD(getter_AddRefs(dtd));
NS_ASSERTION(dtd, "missing dtd in WillBuildModel");
if (dtd && !(dtd->GetType() & NS_IPARSER_FLAG_XML)) {
ForwardRequest(PR_FALSE);
// Stop the parser since we don't want to spend more cycles on parsing
// stuff.
return NS_ERROR_HTMLPARSER_STOPPARSING;
}
return NS_OK;
}
// Moves aIter past the LWS (RFC2616) directly following it.
// Returns PR_TRUE and updates aIter if there was an LWS there,
// PR_FALSE otherwise
static PRBool
EatLWS(const char*& aIter, const char* aEnd)
{
if (aIter + 1 < aEnd && *aIter == '\r' && *(aIter + 1) == '\n') {
aIter += 2;
}
PRBool res = PR_FALSE;
while (aIter < aEnd && (*aIter == '\t' || *aIter == ' ')) {
++aIter;
res = PR_TRUE;
}
return res;
}
// Moves aIter past the string, given by aString, directly following it.
// Returns PR_TRUE and updates aIter if the string was there,
// PR_FALSE otherwise
static PRBool
EatString(const char*& aIter, const char* aEnd, const char* aString)
{
const char* local = aIter;
while (*aString && local < aEnd && *local == *aString) {
++local;
++aString;
}
if (*aString) {
return PR_FALSE;
}
aIter = local;
return PR_TRUE;
}
// Moves aIter to the first aChar following it.
// Returns The string between the aIters initial position and the
// found character if one was found.
// Returns an empty string otherwise.
static nsDependentCSubstring
EatToChar(const char*& aIter, const char* aEnd, char aChar)
{
const char* start = aIter;
while (aIter < aEnd) {
if (*aIter == aChar) {
return Substring(start, aIter);
}
++aIter;
}
static char emptyStatic[] = { '\0' };
aIter = start;
return Substring(emptyStatic, emptyStatic);
}
PRBool
nsCrossSiteListenerProxy::MatchPatternList(const char*& aIter, const char* aEnd)
{
PRBool matchesList = PR_FALSE;
PRBool hasItems = PR_FALSE;
for (;;) {
const char* start = aIter;
if (!EatLWS(aIter, aEnd)) {
break;
}
if (!EatString(aIter, aEnd, "<")) {
// restore iterator to before LWS since it wasn't part of the list
aIter = start;
break;
}
const nsACString& accessItem = EatToChar(aIter, aEnd, '>');
if (!EatString(aIter, aEnd, ">")) {
mAcceptState = eDeny;
break;
}
hasItems = PR_TRUE;
// Order is important here since we always want to call the function
matchesList = VerifyAndMatchDomainPattern(accessItem) || matchesList;
}
if (!hasItems) {
mAcceptState = eDeny;
}
return matchesList;
}
#define DENY_AND_RETURN \
mAcceptState = eDeny; \
return
void
nsCrossSiteListenerProxy::CheckHeader(const nsCString& aHeader)
{
const char* iter = aHeader.BeginReading();
const char* end = aHeader.EndReading();
// ruleset ::= LWS? rule LWS? ("," LWS? rule LWS?)*
while (iter < end) {
// eat LWS?
EatLWS(iter, end);
// rule ::= rule-type (LWS pattern)+ (LWS "exclude" (LWS pattern)+)?
// eat rule-type
PRBool ruleIsAllow;
if (EatString(iter, end, "deny")) {
ruleIsAllow = PR_FALSE;
}
else if (EatString(iter, end, "allow")) {
ruleIsAllow = PR_TRUE;
}
else {
DENY_AND_RETURN;
}
// eat (LWS pattern)+
PRBool matchesRule = MatchPatternList(iter, end);
PRBool ateLWS = EatLWS(iter, end);
// eat (LWS "exclude" (LWS pattern)+)?
if (ateLWS && EatString(iter, end, "exclude")) {
ateLWS = PR_FALSE;
// Order is important here since we always want to call the function
matchesRule = !MatchPatternList(iter, end) && matchesRule;
}
if (matchesRule && mAcceptState != eDeny) {
mAcceptState = ruleIsAllow ? eAccept : eDeny;
}
// eat LWS?
if (!ateLWS) {
EatLWS(iter, end);
}
if (iter != end) {
if (!EatString(iter, end, ",")) {
DENY_AND_RETURN;
}
}
}
}
// Moves aIter forward one character if the character at aIter is in [a-zA-Z]
// Returns PR_TRUE and updates aIter if such a character was found.
// PR_FALSE otherwise.
static PRBool
EatAlpha(nsACString::const_iterator& aIter, nsACString::const_iterator& aEnd)
{
if (aIter != aEnd && ((*aIter >= 'A' && *aIter <= 'Z') ||
(*aIter >= 'a' && *aIter <= 'z'))) {
++aIter;
return PR_TRUE;
}
return PR_FALSE;
}
// Moves aIter forward one character if the character at aIter is in [0-9]
// Returns PR_TRUE and updates aIter if such a character was found.
// PR_FALSE otherwise.
static PRBool
EatDigit(nsACString::const_iterator& aIter, nsACString::const_iterator& aEnd)
{
if (aIter != aEnd && *aIter >= '0' && *aIter <= '9') {
++aIter;
return PR_TRUE;
}
return PR_FALSE;
}
// Moves aIter forward one character if the character at aIter is aChar
// Returns PR_TRUE and updates aIter if aChar was found.
// PR_FALSE otherwise.
static PRBool
EatChar(nsACString::const_iterator& aIter, nsACString::const_iterator& aEnd,
char aChar)
{
if (aIter != aEnd && *aIter == aChar) {
++aIter;
return PR_TRUE;
}
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)
{
if (aPattern.EqualsLiteral("*")) {
return PR_TRUE;
}
// access-item ::= (scheme "://")? domain-pattern (":" port)? | "*"
nsACString::const_iterator start, iter, end;
aPattern.BeginReading(start);
aPattern.EndReading(end);
// (scheme "://")?
nsCString patternScheme;
nsACString::const_iterator schemeStart = start, schemeEnd = end;
if (FindInReadable(NS_LITERAL_CSTRING("://"), schemeStart, schemeEnd)) {
// There is a '://' in the string which means that it must start with
// a scheme.
iter = start;
if (!EatAlpha(iter, end)) {
DENY_AND_RETURN PR_FALSE;
}
while(EatAlpha(iter, end) ||
EatDigit(iter, end) ||
EatChar(iter, end, '+') ||
EatChar(iter, end, '-') ||
EatChar(iter, end, '.')) {}
if (iter != schemeStart) {
DENY_AND_RETURN PR_FALSE;
}
// Set the scheme
patternScheme = Substring(start, iter);
start = iter.advance(3);
}
// domain-pattern ::= subdomain | "*." subdomain
PRBool patternHasWild = PR_FALSE;
if (EatChar(start, end, '*')) {
if (!EatChar(start, end, '.')) {
DENY_AND_RETURN PR_FALSE;
}
patternHasWild = PR_TRUE;
}
nsTArray<nsCString> patternSubdomains;
// subdomain ::= label | subdomain "." label
do {
iter = start;
EatSubdomainChars(iter, end);
const nsDependentCSubstring& label = Substring(start, iter);
if (label.Last() == '-') {
DENY_AND_RETURN PR_FALSE;
}
start = iter;
// Save the label
patternSubdomains.AppendElement(label);
} while (EatChar(start, end, '.'));
// (":" port)?
PRInt32 patternPort = -1;
if (EatChar(start, end, ':')) {
iter = start;
while (EatDigit(iter, end)) {}
if (iter != start) {
PRInt32 ec;
patternPort = PromiseFlatCString(Substring(start, iter)).ToInteger(&ec);
NS_ASSERTION(NS_SUCCEEDED(ec), "ToInteger failed");
}
start = iter;
}
// Did we consume the whole pattern?
if (start != end) {
DENY_AND_RETURN PR_FALSE;
}
// Do checks at the end so that we make sure that the whole pattern is
// checked for syntax correctness first.
// Check scheme
PRBool res;
if (!patternScheme.IsEmpty() &&
(NS_FAILED(mRequestingURI->SchemeIs(patternScheme.get(), &res)) ||
!res)) {
return PR_FALSE;
}
// Check port
if (patternPort == -1 && !patternScheme.IsEmpty())
patternPort = NS_GetDefaultPort(patternScheme.get());
if (patternPort != -1 && patternPort != NS_GetRealPort(mRequestingURI)) {
return PR_FALSE;
}
// Check subdomain
PRUint32 patternPos = patternSubdomains.Length();
PRUint32 reqPos = mReqSubdomains.Length();
do {
--patternPos;
--reqPos;
if (!ACEEquals(patternSubdomains[patternPos], mReqSubdomains[reqPos])) {
return PR_FALSE;
}
} while (patternPos > 0 && reqPos > 0);
// 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 &&
(!patternHasWild || reqPos >= 1);
}
NS_IMETHODIMP
nsCrossSiteListenerProxy::GetInterface(const nsIID & aIID, void **aResult)
{
if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
*aResult = static_cast<nsIChannelEventSink*>(this);
NS_ADDREF_THIS();
return NS_OK;
}
return mOuterNotificationCallbacks ?
mOuterNotificationCallbacks->GetInterface(aIID, aResult) :
NS_ERROR_NO_INTERFACE;
}
NS_IMETHODIMP
nsCrossSiteListenerProxy::OnChannelRedirect(nsIChannel *aOldChannel,
nsIChannel *aNewChannel,
PRUint32 aFlags)
{
nsresult rv;
nsCOMPtr<nsIChannelEventSink> outer =
do_GetInterface(mOuterNotificationCallbacks);
if (outer) {
rv = outer->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
NS_ENSURE_SUCCESS(rv, rv);
}
return UpdateChannel(aNewChannel);
}
nsresult
nsCrossSiteListenerProxy::UpdateChannel(nsIChannel* aChannel)
{
nsCOMPtr<nsIURI> uri;
nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
// Check that the uri is ok to load
rv = nsContentUtils::GetSecurityManager()->
CheckLoadURIWithPrincipal(mRequestingPrincipal, uri,
nsIScriptSecurityManager::STANDARD);
NS_ENSURE_SUCCESS(rv, rv);
if (!mHasBeenCrossSite &&
NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(uri, PR_FALSE))) {
return NS_OK;
}
nsCString userpass;
uri->GetUserPass(userpass);
NS_ENSURE_TRUE(userpass.IsEmpty(), NS_ERROR_DOM_BAD_URI);
// It's a cross site load
mHasBeenCrossSite = PR_TRUE;
// Work out the Referer-Root header
nsCString root, host;
rv = mRequestingURI->GetAsciiHost(host);
NS_ENSURE_SUCCESS(rv, rv);
if (!host.IsEmpty()) {
nsCString scheme;
rv = mRequestingURI->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
root = scheme + NS_LITERAL_CSTRING("://") + host;
// If needed, append the port
PRInt32 port;
mRequestingURI->GetPort(&port);
if (port != -1) {
PRInt32 defaultPort = NS_GetDefaultPort(scheme.get());
if (port != defaultPort) {
root.Append(":");
root.AppendInt(port);
}
}
}
else {
root.AssignLiteral("null");
}
// Now add the access-control-origin header
nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
NS_ENSURE_TRUE(http, NS_ERROR_FAILURE);
return http->SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Origin"),
root, PR_FALSE);
}

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

@ -0,0 +1,109 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Jonas Sicking <jonas@sicking.cc> (Original Author)
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsIStreamListener.h"
#include "nsIInterfaceRequestor.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsIURI.h"
#include "nsTArray.h"
#include "nsIContentSink.h"
#include "nsIXMLContentSink.h"
#include "nsIExpatSink.h"
#include "nsIInterfaceRequestor.h"
#include "nsIChannelEventSink.h"
class nsIURI;
class nsIParser;
class nsIPrincipal;
class nsCrossSiteListenerProxy : public nsIStreamListener,
public nsIXMLContentSink,
public nsIExpatSink,
public nsIInterfaceRequestor,
public nsIChannelEventSink
{
public:
nsCrossSiteListenerProxy(nsIStreamListener* aOuter,
nsIPrincipal* aRequestingPrincipal,
nsIChannel* aChannel,
nsresult* aResult);
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIEXPATSINK
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSICHANNELEVENTSINK
// nsIContentSink
NS_IMETHOD WillTokenize(void) { return NS_OK; }
NS_IMETHOD WillBuildModel(void);
NS_IMETHOD DidBuildModel() { return NS_OK; }
NS_IMETHOD WillInterrupt(void) { return NS_OK; }
NS_IMETHOD WillResume(void) { return NS_OK; }
NS_IMETHOD SetParser(nsIParser* aParser) { return NS_OK; }
virtual void FlushPendingNotifications(mozFlushType aType) { }
NS_IMETHOD SetDocumentCharset(nsACString& aCharset) { return NS_OK; }
virtual nsISupports *GetTarget() { return nsnull; }
private:
nsresult UpdateChannel(nsIChannel* aChannel);
nsresult ForwardRequest(PRBool aCallStop);
PRBool MatchPatternList(const char*& aIter, const char* aEnd);
void CheckHeader(const nsCString& aHeader);
PRBool VerifyAndMatchDomainPattern(const nsACString& aDomainPattern);
nsCOMPtr<nsIStreamListener> mOuterListener;
nsCOMPtr<nsIRequest> mOuterRequest;
nsCOMPtr<nsISupports> mOuterContext;
nsCOMPtr<nsIStreamListener> mParserListener;
nsCOMPtr<nsIParser> mParser;
nsCOMPtr<nsIURI> mRequestingURI;
nsCOMPtr<nsIPrincipal> mRequestingPrincipal;
nsCOMPtr<nsIInterfaceRequestor> mOuterNotificationCallbacks;
nsTArray<nsCString> mReqSubdomains;
nsCString mStoredData;
enum {
eAccept,
eDeny,
eNotSet
} mAcceptState;
PRBool mHasForwardedRequest;
PRBool mHasBeenCrossSite;
};

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

@ -59,6 +59,7 @@
#include "nsAutoPtr.h"
#include "nsLoadListenerProxy.h"
#include "nsStreamUtils.h"
#include "nsCrossSiteListenerProxy.h"
/**
* This class manages loading a single XML document
@ -219,12 +220,10 @@ nsSyncLoader::LoadDocument(nsIChannel* aChannel,
}
if (aLoaderPrincipal) {
nsCOMPtr<nsIURI> docURI;
rv = aChannel->GetOriginalURI(getter_AddRefs(docURI));
NS_ENSURE_SUCCESS(rv, rv);
rv = aLoaderPrincipal->CheckMayLoad(docURI, PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv);
listener = new nsCrossSiteListenerProxy(listener, aLoaderPrincipal,
mChannel, &rv);
NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_SUCCESS(rv, rv);
}
// Register as a load listener on the document
@ -370,18 +369,6 @@ nsSyncLoader::OnChannelRedirect(nsIChannel *aOldChannel,
mChannel = aNewChannel;
nsCOMPtr<nsIURI> oldURI;
nsresult rv = aOldChannel->GetURI(getter_AddRefs(oldURI));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> newURI;
rv = aNewChannel->GetURI(getter_AddRefs(newURI));
NS_ENSURE_SUCCESS(rv, rv);
rv = nsContentUtils::GetSecurityManager()->
CheckSameOriginURI(oldURI, newURI, PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}

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

@ -81,6 +81,7 @@
#include "nsContentPolicyUtils.h"
#include "nsContentErrors.h"
#include "nsLayoutStatics.h"
#include "nsCrossSiteListenerProxy.h"
#include "nsDOMError.h"
#include "nsIHTMLDocument.h"
#include "nsIDOM3Document.h"
@ -124,6 +125,8 @@
// This is set when we've got the headers for a multipart XMLHttpRequest,
// but haven't yet started to process the first part.
#define XML_HTTP_REQUEST_MPART_HEADERS (1 << 15) // Internal
#define XML_HTTP_REQUEST_USE_XSITE_AC (1 << 16) // Internal
#define XML_HTTP_REQUEST_NON_GET (1 << 17) // Internal
#define XML_HTTP_REQUEST_LOADSTATES \
(XML_HTTP_REQUEST_UNINITIALIZED | \
@ -134,6 +137,8 @@
XML_HTTP_REQUEST_SENT | \
XML_HTTP_REQUEST_STOPPED)
#define ACCESS_CONTROL_CACHE_SIZE 100
#define NS_BADCERTHANDLER_CONTRACTID \
"@mozilla.org/content/xmlhttprequest-bad-cert-handler;1"
@ -277,6 +282,134 @@ nsMultipartProxyListener::OnDataAvailable(nsIRequest *aRequest,
count);
}
// Class used as streamlistener and notification callback when
// doing the initial GET request for an access-control check
class nsACProxyListener : public nsIStreamListener,
public nsIInterfaceRequestor,
public nsIChannelEventSink
{
public:
nsACProxyListener(nsIChannel* aOuterChannel,
nsIStreamListener* aOuterListener,
nsISupports* aOuterContext,
nsIPrincipal* aReferrerPrincipal,
const nsACString& aRequestMethod)
: mOuterChannel(aOuterChannel), mOuterListener(aOuterListener),
mOuterContext(aOuterContext), mReferrerPrincipal(aReferrerPrincipal),
mRequestMethod(aRequestMethod)
{ }
NS_DECL_ISUPPORTS
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSICHANNELEVENTSINK
private:
nsCOMPtr<nsIChannel> mOuterChannel;
nsCOMPtr<nsIStreamListener> mOuterListener;
nsCOMPtr<nsISupports> mOuterContext;
nsCOMPtr<nsIPrincipal> mReferrerPrincipal;
nsCString mRequestMethod;
};
NS_IMPL_ISUPPORTS4(nsACProxyListener, nsIStreamListener, nsIRequestObserver,
nsIInterfaceRequestor, nsIChannelEventSink)
NS_IMETHODIMP
nsACProxyListener::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
{
nsresult status;
nsresult rv = aRequest->GetStatus(&status);
if (NS_SUCCEEDED(rv)) {
rv = status;
}
if (NS_SUCCEEDED(rv)) {
// Everything worked, check to see if there is an expiration time set on
// this access control list. If so go ahead and cache it.
nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest, &rv);
// The "Access-Control-Max-Age" header should return an age in seconds.
nsCAutoString ageString;
http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Max-Age"),
ageString);
// Sanitize the string. We only allow 'delta-seconds' as specified by
// http://dev.w3.org/2006/waf/access-control (digits 0-9 with no leading or
// trailing non-whitespace characters). We don't allow a + or - character
// but PR_sscanf does so we ensure that the first character is actually a
// digit.
ageString.StripWhitespace();
if (ageString.CharAt(0) >= '0' || ageString.CharAt(0) <= '9') {
PRUint64 age;
PRInt32 convertedChars = PR_sscanf(ageString.get(), "%llu", &age);
if ((PRInt32)ageString.Length() == convertedChars &&
nsXMLHttpRequest::EnsureACCache()) {
// String seems fine, go ahead and cache.
nsCOMPtr<nsIURI> uri;
http->GetURI(getter_AddRefs(uri));
// PR_Now gives microseconds
PRTime expirationTime = PR_Now() + age * PR_USEC_PER_SEC;
nsXMLHttpRequest::sAccessControlCache->PutEntry(uri, mReferrerPrincipal,
expirationTime);
}
}
}
if (NS_SUCCEEDED(rv)) {
rv = mOuterChannel->AsyncOpen(mOuterListener, mOuterContext);
}
if (NS_FAILED(rv)) {
mOuterChannel->Cancel(rv);
mOuterListener->OnStartRequest(mOuterChannel, mOuterContext);
mOuterListener->OnStopRequest(mOuterChannel, mOuterContext, rv);
return rv;
}
return NS_OK;
}
NS_IMETHODIMP
nsACProxyListener::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext,
nsresult aStatus)
{
return NS_OK;
}
/** nsIStreamListener methods **/
NS_IMETHODIMP
nsACProxyListener::OnDataAvailable(nsIRequest *aRequest,
nsISupports *ctxt,
nsIInputStream *inStr,
PRUint32 sourceOffset,
PRUint32 count)
{
return NS_OK;
}
NS_IMETHODIMP
nsACProxyListener::OnChannelRedirect(nsIChannel *aOldChannel,
nsIChannel *aNewChannel,
PRUint32 aFlags)
{
// No redirects allowed for now.
return NS_ERROR_DOM_BAD_URI;
}
NS_IMETHODIMP
nsACProxyListener::GetInterface(const nsIID & aIID, void **aResult)
{
return QueryInterface(aIID, aResult);
}
/**
* Gets the nsIDocument given the script context. Will return nsnull on failure.
*
@ -617,11 +750,155 @@ NS_INTERFACE_MAP_END_INHERITING(nsXHREventTarget)
NS_IMPL_ADDREF_INHERITED(nsXMLHttpRequestUpload, nsXHREventTarget)
NS_IMPL_RELEASE_INHERITED(nsXMLHttpRequestUpload, nsXHREventTarget)
void
nsAccessControlLRUCache::GetEntry(nsIURI* aURI,
nsIPrincipal* aPrincipal,
PRTime* _retval)
{
nsCAutoString key;
if (GetCacheKey(aURI, aPrincipal, key)) {
CacheEntry* entry;
if (GetEntryInternal(key, &entry)) {
*_retval = entry->value;
return;
}
}
*_retval = 0;
}
void
nsAccessControlLRUCache::PutEntry(nsIURI* aURI,
nsIPrincipal* aPrincipal,
PRTime aValue)
{
nsCString key;
if (!GetCacheKey(aURI, aPrincipal, key)) {
NS_WARNING("Invalid cache key!");
return;
}
CacheEntry* entry;
if (GetEntryInternal(key, &entry)) {
// Entry already existed, just update the expiration time and bail. The LRU
// list is updated as a result of the call to GetEntryInternal.
entry->value = aValue;
return;
}
// This is a new entry, allocate and insert into the table now so that any
// failures don't cause items to be removed from a full cache.
entry = new CacheEntry(key, aValue);
if (!entry) {
NS_WARNING("Failed to allocate new cache entry!");
return;
}
if (!mTable.Put(key, entry)) {
// Failed, clean up the new entry.
delete entry;
NS_WARNING("Failed to add entry to the access control cache!");
return;
}
PR_INSERT_LINK(entry, &mList);
NS_ASSERTION(mTable.Count() <= ACCESS_CONTROL_CACHE_SIZE + 1,
"Something is borked, too many entries in the cache!");
// Now enforce the max count.
if (mTable.Count() > ACCESS_CONTROL_CACHE_SIZE) {
// Try to kick out all the expired entries.
PRTime now = PR_Now();
mTable.Enumerate(RemoveExpiredEntries, &now);
// If that didn't remove anything then kick out the least recently used
// entry.
if (mTable.Count() > ACCESS_CONTROL_CACHE_SIZE) {
CacheEntry* lruEntry = static_cast<CacheEntry*>(PR_LIST_TAIL(&mList));
PR_REMOVE_LINK(lruEntry);
// This will delete 'lruEntry'.
mTable.Remove(lruEntry->key);
NS_ASSERTION(mTable.Count() >= ACCESS_CONTROL_CACHE_SIZE,
"Somehow tried to remove an entry that was never added!");
}
}
}
void
nsAccessControlLRUCache::Clear()
{
PR_INIT_CLIST(&mList);
mTable.Clear();
}
PRBool
nsAccessControlLRUCache::GetEntryInternal(const nsACString& aKey,
CacheEntry** _retval)
{
if (!mTable.Get(aKey, _retval))
return PR_FALSE;
// Move to the head of the list.
PR_REMOVE_LINK(*_retval);
PR_INSERT_LINK(*_retval, &mList);
return PR_TRUE;
}
/* static */ PR_CALLBACK PLDHashOperator
nsAccessControlLRUCache::RemoveExpiredEntries(const nsACString& aKey,
nsAutoPtr<CacheEntry>& aValue,
void* aUserData)
{
PRTime* now = static_cast<PRTime*>(aUserData);
if (*now >= aValue->value) {
// Expired, remove from the list as well as the hash table.
PR_REMOVE_LINK(aValue);
return PL_DHASH_REMOVE;
}
// Keep going.
return PL_DHASH_NEXT;
}
/* static */ PRBool
nsAccessControlLRUCache::GetCacheKey(nsIURI* aURI,
nsIPrincipal* aPrincipal,
nsACString& _retval)
{
NS_ASSERTION(aURI, "Null uri!");
NS_ASSERTION(aPrincipal, "Null principal!");
NS_NAMED_LITERAL_CSTRING(space, " ");
nsCOMPtr<nsIURI> uri;
nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, PR_FALSE);
nsCAutoString host;
if (uri) {
uri->GetHost(host);
}
nsCAutoString spec;
rv = aURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, PR_FALSE);
_retval.Assign(host + space + spec);
return PR_TRUE;
}
/////////////////////////////////////////////
//
//
/////////////////////////////////////////////
// Will be initialized in nsXMLHttpRequest::EnsureACCache.
nsAccessControlLRUCache* nsXMLHttpRequest::sAccessControlCache = nsnull;
nsXMLHttpRequest::nsXMLHttpRequest()
: mRequestObserver(nsnull), mState(XML_HTTP_REQUEST_UNINITIALIZED),
mUploadTransferred(0), mUploadTotal(0), mUploadComplete(PR_TRUE),
@ -1053,6 +1330,9 @@ nsXMLHttpRequest::Abort()
if (mChannel) {
mChannel->Cancel(NS_BINDING_ABORTED);
}
if (mACGetChannel) {
mACGetChannel->Cancel(NS_BINDING_ABORTED);
}
mDocument = nsnull;
mResponseBody.Truncate();
mState |= XML_HTTP_REQUEST_ABORTED;
@ -1092,6 +1372,10 @@ nsXMLHttpRequest::GetAllResponseHeaders(char **_retval)
NS_ENSURE_ARG_POINTER(_retval);
*_retval = nsnull;
if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) {
return NS_OK;
}
nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
if (httpChannel) {
@ -1120,6 +1404,37 @@ nsXMLHttpRequest::GetResponseHeader(const nsACString& header,
nsresult rv = NS_OK;
_retval.Truncate();
// Check for dangerous headers
if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) {
// Make sure we don't leak header information from denied cross-site
// requests.
if (mChannel) {
nsresult status;
mChannel->GetStatus(&status);
if (NS_FAILED(status)) {
return NS_OK;
}
}
const char *kCrossOriginSafeHeaders[] = {
"cache-control", "content-language", "content-type", "expires",
"last-modified", "pragma"
};
PRBool safeHeader = PR_FALSE;
PRUint32 i;
for (i = 0; i < NS_ARRAY_LENGTH(kCrossOriginSafeHeaders); ++i) {
if (header.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
safeHeader = PR_TRUE;
break;
}
}
if (!safeHeader) {
return NS_OK;
}
}
nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
if (httpChannel) {
@ -1264,25 +1579,46 @@ IsSystemPrincipal(nsIPrincipal* aPrincipal)
}
static PRBool
IsSameOrigin(nsIPrincipal* aPrincipal, nsIChannel* aChannel)
CheckMayLoad(nsIPrincipal* aPrincipal, nsIChannel* aChannel)
{
NS_ASSERTION(!IsSystemPrincipal(aPrincipal), "Shouldn't get here!");
nsCOMPtr<nsIURI> codebase;
nsresult rv = aPrincipal->GetURI(getter_AddRefs(codebase));
NS_ENSURE_SUCCESS(rv, PR_FALSE);
NS_ASSERTION(codebase, "Principal must have a URI!");
nsCOMPtr<nsIURI> channelURI;
rv = aChannel->GetURI(getter_AddRefs(channelURI));
nsresult rv = aChannel->GetURI(getter_AddRefs(channelURI));
NS_ENSURE_SUCCESS(rv, PR_FALSE);
rv = nsContentUtils::GetSecurityManager()->
CheckSameOriginURI(codebase, channelURI, PR_FALSE);
rv = aPrincipal->CheckMayLoad(channelURI, PR_FALSE);
return NS_SUCCEEDED(rv);
}
nsresult
nsXMLHttpRequest::CheckChannelForCrossSiteRequest(nsIChannel* aChannel)
{
// First check if this is a same-origin request, or if cross-site requests
// are enabled.
if ((mState & XML_HTTP_REQUEST_XSITEENABLED) ||
CheckMayLoad(mPrincipal, aChannel)) {
return NS_OK;
}
// This is a cross-site request
// The request is now cross-site, so update flag.
mState |= XML_HTTP_REQUEST_USE_XSITE_AC;
// Remove dangerous headers
nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
if (http) {
PRUint32 i;
for (i = 0; i < mExtraRequestHeaders.Length(); ++i) {
http->SetRequestHeader(mExtraRequestHeaders[i], EmptyCString(), PR_FALSE);
}
mExtraRequestHeaders.Clear();
}
return NS_OK;
}
/* noscript void openRequest (in AUTF8String method, in AUTF8String url, in boolean async, in AString user, in AString password); */
NS_IMETHODIMP
nsXMLHttpRequest::OpenRequest(const nsACString& method,
@ -1401,11 +1737,46 @@ nsXMLHttpRequest::OpenRequest(const nsACString& method,
// Chrome callers are always allowed to read from different origins.
mState |= XML_HTTP_REQUEST_XSITEENABLED;
}
else if (!(mState & XML_HTTP_REQUEST_XSITEENABLED) &&
!CheckMayLoad(mPrincipal, mChannel)) {
mState |= XML_HTTP_REQUEST_USE_XSITE_AC;
}
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
if (httpChannel) {
rv = httpChannel->SetRequestMethod(method);
NS_ENSURE_SUCCESS(rv, rv);
if (!method.LowerCaseEqualsLiteral("get")) {
mState |= XML_HTTP_REQUEST_NON_GET;
}
}
// Do we need to set up an initial OPTIONS request to make sure that it is
// safe to make the request?
if ((mState & XML_HTTP_REQUEST_USE_XSITE_AC) &&
(mState & XML_HTTP_REQUEST_NON_GET)) {
// Check to see if this initial OPTIONS request has already been cached in
// our special Access Control Cache.
PRTime expiration = 0;
if (sAccessControlCache) {
sAccessControlCache->GetEntry(uri, mPrincipal, &expiration);
}
if (expiration <= PR_Now()) {
// Either it wasn't cached or the cached result has expired. Build a
// channel for the OPTIONS request.
rv = NS_NewChannel(getter_AddRefs(mACGetChannel), uri, nsnull, loadGroup,
nsnull, loadFlags);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIHttpChannel> acHttp = do_QueryInterface(mACGetChannel);
NS_ASSERTION(acHttp, "Failed to QI to nsIHttpChannel!");
rv = acHttp->SetRequestMethod(NS_LITERAL_CSTRING("OPTIONS"));
NS_ENSURE_SUCCESS(rv, rv);
}
}
ChangeState(XML_HTTP_REQUEST_OPENED);
@ -1454,8 +1825,6 @@ nsXMLHttpRequest::Open(const nsACString& method, const nsACString& url)
mState |= XML_HTTP_REQUEST_XSITEENABLED;
} else {
mState &= ~XML_HTTP_REQUEST_XSITEENABLED;
rv = mPrincipal->CheckMayLoad(targetURI, PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv);
}
if (argc > 2) {
@ -1635,6 +2004,13 @@ nsXMLHttpRequest::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
doc->SetPrincipal(documentPrincipal);
}
if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) {
nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(mDocument);
if (htmlDoc) {
htmlDoc->DisableCookieAccess();
}
}
// Reset responseBody
mResponseBody.Truncate();
@ -2109,6 +2485,9 @@ nsXMLHttpRequest::Send(nsIVariant *aBody)
mState |= XML_HTTP_REQUEST_SYNCLOOPING;
}
rv = CheckChannelForCrossSiteRequest(mChannel);
NS_ENSURE_SUCCESS(rv, rv);
// Hook us up to listen to redirects and the like
mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
mChannel->SetNotificationCallbacks(this);
@ -2122,6 +2501,15 @@ nsXMLHttpRequest::Send(nsIVariant *aBody)
}
}
if (!(mState & XML_HTTP_REQUEST_XSITEENABLED)) {
// Always create a nsCrossSiteListenerProxy here even if it's
// a same-origin request right now, since it could be redirected.
listener = new nsCrossSiteListenerProxy(listener, mPrincipal, mChannel,
&rv);
NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_SUCCESS(rv, rv);
}
// Bypass the network cache in cases where it makes no sense:
// 1) Multipart responses are very large and would likely be doomed by the
// cache once they grow too large, so they are not worth caching.
@ -2138,6 +2526,10 @@ nsXMLHttpRequest::Send(nsIVariant *aBody)
else if (mState & XML_HTTP_REQUEST_SYNCLOOPING) {
AddLoadFlags(mChannel,
nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
if (mACGetChannel) {
AddLoadFlags(mACGetChannel,
nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
}
}
// Since we expect XML data, set the type hint accordingly
@ -2145,8 +2537,24 @@ nsXMLHttpRequest::Send(nsIVariant *aBody)
// ignoring return value, as this is not critical
mChannel->SetContentType(NS_LITERAL_CSTRING("application/xml"));
// Start reading from the channel
rv = mChannel->AsyncOpen(listener, nsnull);
// If we're doing a cross-site non-GET request we need to first do
// a GET request to the same URI. Set that up if needed
if (mACGetChannel) {
nsCOMPtr<nsIStreamListener> acListener =
new nsACProxyListener(mChannel, listener, nsnull, mPrincipal, method);
NS_ENSURE_TRUE(acListener, NS_ERROR_OUT_OF_MEMORY);
listener = new nsCrossSiteListenerProxy(acListener, mPrincipal,
mACGetChannel, &rv);
NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_SUCCESS(rv, rv);
rv = mACGetChannel->AsyncOpen(listener, nsnull);
}
else {
// Start reading from the channel
rv = mChannel->AsyncOpen(listener, nsnull);
}
if (NS_FAILED(rv)) {
// Drop our ref to the channel to avoid cycles
@ -2192,6 +2600,19 @@ nsXMLHttpRequest::SetRequestHeader(const nsACString& header,
{
nsresult rv;
// Check that we haven't already opened the channel. We can't rely on
// the channel throwing from mChannel->SetRequestHeader since we might
// still be waiting for mACGetChannel to actually open mChannel
if (mACGetChannel) {
PRBool pending;
rv = mACGetChannel->IsPending(&pending);
NS_ENSURE_SUCCESS(rv, rv);
if (pending) {
return NS_ERROR_IN_PROGRESS;
}
}
if (!mChannel) // open() initializes mChannel, and open()
return NS_ERROR_FAILURE; // must be called before first setRequestHeader()
@ -2230,6 +2651,32 @@ nsXMLHttpRequest::SetRequestHeader(const nsACString& header,
NS_WARNING("refusing to set request header");
return NS_OK;
}
// Check for dangerous cross-site headers
PRBool safeHeader = !!(mState & XML_HTTP_REQUEST_XSITEENABLED);
if (!safeHeader) {
const char *kCrossOriginSafeHeaders[] = {
"accept", "accept-language"
};
for (i = 0; i < NS_ARRAY_LENGTH(kCrossOriginSafeHeaders); ++i) {
if (header.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
safeHeader = PR_TRUE;
break;
}
}
}
if (!safeHeader) {
// The header is unsafe for cross-site requests. If this is a cross-site
// request throw an exception...
if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) {
return NS_ERROR_FAILURE;
}
// ...otherwise just add it to mExtraRequestHeaders so that we can
// remove it in case we're redirected to another site
mExtraRequestHeaders.AppendElement(header);
}
}
// We need to set, not add to, the header.
@ -2439,21 +2886,15 @@ nsXMLHttpRequest::OnChannelRedirect(nsIChannel *aOldChannel,
nsresult rv;
if (!(mState & XML_HTTP_REQUEST_XSITEENABLED)) {
nsCOMPtr<nsIURI> oldURI;
rv = aOldChannel->GetURI(getter_AddRefs(oldURI));
NS_ENSURE_SUCCESS(rv, rv);
rv = CheckChannelForCrossSiteRequest(aNewChannel);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> newURI;
rv = aNewChannel->GetURI(getter_AddRefs(newURI));
NS_ENSURE_SUCCESS(rv, rv);
rv = nsContentUtils::GetSecurityManager()->
CheckSameOriginURI(oldURI, newURI, PR_TRUE);
if (NS_FAILED(rv)) {
mErrorLoad = PR_TRUE;
return rv;
}
// Disable redirects for non-get cross-site requests entirely for now
// Note, do this after the call to CheckChannelForCrossSiteRequest
// to make sure that XML_HTTP_REQUEST_USE_XSITE_AC is up-to-date
if ((mState & XML_HTTP_REQUEST_NON_GET) &&
(mState & XML_HTTP_REQUEST_USE_XSITE_AC)) {
return NS_ERROR_DOM_BAD_URI;
}
if (mChannelEventSink) {

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

@ -75,6 +75,65 @@
class nsILoadGroup;
class nsAccessControlLRUCache
{
struct CacheEntry : public PRCList
{
CacheEntry(const nsACString& aKey, PRTime aValue)
: key(aKey), value(aValue)
{
MOZ_COUNT_CTOR(nsAccessControlLRUCache::CacheEntry);
}
~CacheEntry()
{
MOZ_COUNT_DTOR(nsAccessControlLRUCache::CacheEntry);
}
nsCString key;
PRTime value;
};
public:
nsAccessControlLRUCache()
{
MOZ_COUNT_CTOR(nsAccessControlLRUCache);
PR_INIT_CLIST(&mList);
}
~nsAccessControlLRUCache()
{
Clear();
MOZ_COUNT_DTOR(nsAccessControlLRUCache);
}
PRBool Initialize()
{
return mTable.Init();
}
void GetEntry(nsIURI* aURI, nsIPrincipal* aPrincipal,
PRTime* _retval);
void PutEntry(nsIURI* aURI, nsIPrincipal* aPrincipal,
PRTime aValue);
void Clear();
private:
PRBool GetEntryInternal(const nsACString& aKey, CacheEntry** _retval);
PR_STATIC_CALLBACK(PLDHashOperator)
RemoveExpiredEntries(const nsACString& aKey, nsAutoPtr<CacheEntry>& aValue,
void* aUserData);
static PRBool GetCacheKey(nsIURI* aURI, nsIPrincipal* aPrincipal,
nsACString& _retval);
nsClassHashtable<nsCStringHashKey, CacheEntry> mTable;
PRCList mList;
};
class nsDOMEventListenerWrapper : public nsIDOMEventListener
{
public:
@ -262,6 +321,32 @@ public:
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsXMLHttpRequest,
nsXHREventTarget)
static PRBool EnsureACCache()
{
if (sAccessControlCache)
return PR_TRUE;
nsAutoPtr<nsAccessControlLRUCache> newCache(new nsAccessControlLRUCache());
NS_ENSURE_TRUE(newCache, PR_FALSE);
if (newCache->Initialize()) {
sAccessControlCache = newCache.forget();
return PR_TRUE;
}
return PR_FALSE;
}
static void ShutdownACCache()
{
if (sAccessControlCache) {
delete sAccessControlCache;
sAccessControlCache = nsnull;
}
}
static nsAccessControlLRUCache* sAccessControlCache;
protected:
friend class nsMultipartProxyListener;
@ -290,12 +375,12 @@ protected:
already_AddRefed<nsIHttpChannel> GetCurrentHttpChannel();
/**
* Check if mChannel is ok for a cross-site request by making sure no
* Check if aChannel is ok for a cross-site request by making sure no
* inappropriate headers are set, and no username/password is set.
*
* Also updates the XML_HTTP_REQUEST_USE_XSITE_AC bit.
*/
nsresult CheckChannelForCrossSiteRequest();
nsresult CheckChannelForCrossSiteRequest(nsIChannel* aChannel);
nsCOMPtr<nsISupports> mContext;
nsCOMPtr<nsIPrincipal> mPrincipal;
@ -303,6 +388,7 @@ protected:
// mReadRequest is different from mChannel for multipart requests
nsCOMPtr<nsIRequest> mReadRequest;
nsCOMPtr<nsIDOMDocument> mDocument;
nsCOMPtr<nsIChannel> mACGetChannel;
nsRefPtr<nsDOMEventListenerWrapper> mOnUploadProgressListener;
nsRefPtr<nsDOMEventListenerWrapper> mOnReadystatechangeListener;

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

@ -1713,6 +1713,10 @@ nsHTMLDocument::GetCookie(nsAString& aCookie)
aCookie.Truncate(); // clear current cookie in case service fails;
// no cookie isn't an error condition.
if (mDisableCookieAccess) {
return NS_OK;
}
// not having a cookie service isn't an error
nsCOMPtr<nsICookieService> service = do_GetService(NS_COOKIESERVICE_CONTRACTID);
if (service) {
@ -1739,6 +1743,10 @@ nsHTMLDocument::GetCookie(nsAString& aCookie)
NS_IMETHODIMP
nsHTMLDocument::SetCookie(const nsAString& aCookie)
{
if (mDisableCookieAccess) {
return NS_OK;
}
// not having a cookie service isn't an error
nsCOMPtr<nsICookieService> service = do_GetService(NS_COOKIESERVICE_CONTRACTID);
if (service && mDocumentURI) {

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

@ -197,6 +197,11 @@ public:
return mEditingState;
}
virtual void DisableCookieAccess()
{
mDisableCookieAccess = PR_TRUE;
}
virtual nsIContent* GetBodyContentExternal();
class nsAutoEditingState {
@ -356,6 +361,9 @@ protected:
static jsval sCutCopyInternal_id;
static jsval sPasteInternal_id;
// When false, the .cookies property is completely disabled
PRBool mDisableCookieAccess;
// Parser used for constructing document fragments.
nsCOMPtr<nsIParser> mFragmentParser;
};

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

@ -180,6 +180,11 @@ public:
virtual nsresult GetDocumentAllResult(const nsAString& aID,
nsISupports** aResult) = 0;
/**
* Disables getting and setting cookies
*/
virtual void DisableCookieAccess() = 0;
/**
* Get the first <body> child of the root <html>, but don't do
* anything <frameset>-related (like nsIDOMHTMLDocument::GetBody).

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

@ -70,6 +70,7 @@
#include "nsAttrName.h"
#include "nsIScriptError.h"
#include "nsIURL.h"
#include "nsCrossSiteListenerProxy.h"
#include "nsDOMError.h"
static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID);
@ -95,7 +96,6 @@ getSpec(nsIChannel* aChannel, nsAString& aSpec)
class txStylesheetSink : public nsIXMLContentSink,
public nsIExpatSink,
public nsIStreamListener,
public nsIChannelEventSink,
public nsIInterfaceRequestor
{
public:
@ -105,7 +105,6 @@ public:
NS_DECL_NSIEXPATSINK
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSICHANNELEVENTSINK
NS_DECL_NSIINTERFACEREQUESTOR
// nsIContentSink
@ -137,13 +136,12 @@ txStylesheetSink::txStylesheetSink(txStylesheetCompiler* aCompiler,
mListener = do_QueryInterface(aParser);
}
NS_IMPL_ISUPPORTS7(txStylesheetSink,
NS_IMPL_ISUPPORTS6(txStylesheetSink,
nsIXMLContentSink,
nsIContentSink,
nsIExpatSink,
nsIStreamListener,
nsIRequestObserver,
nsIChannelEventSink,
nsIInterfaceRequestor)
NS_IMETHODIMP
@ -375,29 +373,6 @@ txStylesheetSink::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext,
return rv;
}
NS_IMETHODIMP
txStylesheetSink::OnChannelRedirect(nsIChannel *aOldChannel,
nsIChannel *aNewChannel,
PRUint32 aFlags)
{
NS_PRECONDITION(aNewChannel, "Redirecting to null channel?");
nsCOMPtr<nsIURI> oldURI;
nsresult rv = aOldChannel->GetURI(getter_AddRefs(oldURI));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> newURI;
rv = aNewChannel->GetURI(getter_AddRefs(newURI));
NS_ENSURE_SUCCESS(rv, rv);
rv = nsContentUtils::GetSecurityManager()->
CheckSameOriginURI(oldURI, newURI, PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
txStylesheetSink::GetInterface(const nsIID& aIID, void** aResult)
{
@ -421,7 +396,7 @@ txStylesheetSink::GetInterface(const nsIID& aIID, void** aResult)
return NS_OK;
}
return QueryInterface(aIID, aResult);
return NS_ERROR_NO_INTERFACE;
}
class txCompileObserver : public txACompileObserver
@ -494,13 +469,19 @@ txCompileObserver::loadURI(const nsAString& aUri,
GetCodebasePrincipal(referrerUri, getter_AddRefs(referrerPrincipal));
NS_ENSURE_SUCCESS(rv, rv);
// Do security check
rv = nsContentUtils::
CheckSecurityBeforeLoad(uri, referrerPrincipal,
nsIScriptSecurityManager::STANDARD, PR_FALSE,
nsIContentPolicy::TYPE_STYLESHEET,
nsnull, NS_LITERAL_CSTRING("application/xml"));
// Content Policy
PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_STYLESHEET,
uri,
referrerPrincipal,
nsnull,
NS_LITERAL_CSTRING("application/xml"),
nsnull,
&shouldLoad);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_CP_REJECTED(shouldLoad)) {
return NS_ERROR_DOM_BAD_URI;
}
return startLoad(uri, aCompiler, referrerPrincipal);
}
@ -556,7 +537,13 @@ txCompileObserver::startLoad(nsIURI* aUri, txStylesheetCompiler* aCompiler,
parser->SetContentSink(sink);
parser->Parse(aUri);
return channel->AsyncOpen(sink, parser);
// Always install in case of redirects
nsCOMPtr<nsIStreamListener> listener =
new nsCrossSiteListenerProxy(sink, aReferrerPrincipal, channel, &rv);
NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_SUCCESS(rv, rv);
return channel->AsyncOpen(listener, parser);
}
nsresult
@ -567,14 +554,20 @@ TX_LoadSheet(nsIURI* aUri, txMozillaXSLTProcessor* aProcessor,
aUri->GetSpec(spec);
PR_LOG(txLog::xslt, PR_LOG_ALWAYS, ("TX_LoadSheet: %s\n", spec.get()));
// Pass source document as the context
nsresult rv = nsContentUtils::
CheckSecurityBeforeLoad(aUri, aCallerPrincipal,
nsIScriptSecurityManager::STANDARD, PR_FALSE,
nsIContentPolicy::TYPE_STYLESHEET,
aProcessor->GetSourceContentModel(),
NS_LITERAL_CSTRING("application/xml"));
// Content Policy
PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
nsresult rv =
NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_STYLESHEET,
aUri,
aCallerPrincipal,
aProcessor->GetSourceContentModel(),
NS_LITERAL_CSTRING("application/xml"),
nsnull,
&shouldLoad);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_CP_REJECTED(shouldLoad)) {
return NS_ERROR_DOM_BAD_URI;
}
nsRefPtr<txCompileObserver> observer =
new txCompileObserver(aProcessor, aLoadGroup);
@ -716,13 +709,19 @@ txSyncCompileObserver::loadURI(const nsAString& aUri,
GetCodebasePrincipal(referrerUri, getter_AddRefs(referrerPrincipal));
NS_ENSURE_SUCCESS(rv, rv);
// Security checks
rv = nsContentUtils::
CheckSecurityBeforeLoad(uri, referrerPrincipal,
nsIScriptSecurityManager::STANDARD,
PR_FALSE, nsIContentPolicy::TYPE_STYLESHEET,
nsnull, NS_LITERAL_CSTRING("application/xml"));
// Content Policy
PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_STYLESHEET,
uri,
referrerPrincipal,
nsnull,
NS_LITERAL_CSTRING("application/xml"),
nsnull,
&shouldLoad);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_CP_REJECTED(shouldLoad)) {
return NS_ERROR_DOM_BAD_URI;
}
// This is probably called by js, a loadGroup for the channel doesn't
// make sense.