зеркало из https://github.com/mozilla/pjs.git
Reland old Access-Control implementation. b=389508 r/sr=jst
This commit is contained in:
Родитель
32d3012ef7
Коммит
f9b484d02c
|
@ -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.
|
||||
|
|
Загрузка…
Ссылка в новой задаче