From 857bbdaa67b809882732eb7f61e31fa2d3696efd Mon Sep 17 00:00:00 2001 From: "jonas%sicking.cc" Date: Fri, 27 Jul 2007 02:49:19 +0000 Subject: [PATCH] Bug 389508: Cross site XMLHttpRequest. r=jst/biesi sr/dveditz/biesi git-svn-id: svn://10.0.0.236/trunk@231095 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/content/base/public/nsIDocument.h | 15 +- mozilla/content/base/src/Makefile.in | 1 + .../base/src/nsCrossSiteListenerProxy.cpp | 1596 +++++++++-------- .../base/src/nsCrossSiteListenerProxy.h | 194 +- mozilla/content/base/src/nsDocument.cpp | 13 + mozilla/content/base/src/nsGenericElement.cpp | 9 +- .../base/src/nsObjectLoadingContent.cpp | 5 + mozilla/content/base/src/nsParserUtils.cpp | 97 +- mozilla/content/base/src/nsParserUtils.h | 21 + mozilla/content/base/src/nsXMLHttpRequest.cpp | 418 ++++- mozilla/content/base/src/nsXMLHttpRequest.h | 13 + mozilla/content/base/test/Makefile.in | 10 + .../html/document/src/nsHTMLDocument.cpp | 8 + .../html/document/src/nsHTMLDocument.h | 7 + .../html/document/src/nsIHTMLDocument.h | 5 + .../xml/document/src/nsXMLDocument.cpp | 29 +- .../content/xml/document/src/nsXMLDocument.h | 3 - mozilla/modules/libpref/src/init/all.js | 1 + 18 files changed, 1435 insertions(+), 1010 deletions(-) diff --git a/mozilla/content/base/public/nsIDocument.h b/mozilla/content/base/public/nsIDocument.h index 7835a7c9d4f..abb77b0d651 100644 --- a/mozilla/content/base/public/nsIDocument.h +++ b/mozilla/content/base/public/nsIDocument.h @@ -664,11 +664,6 @@ public: virtual nsresult AddXMLEventsContent(nsIContent * aXMLEventsElement) = 0; - virtual PRBool IsLoadedAsData() - { - return PR_FALSE; - } - /** * Create an element with the specified name, prefix and namespace ID. * If aDocumentDefaultType is true we create an element of the default type @@ -876,7 +871,11 @@ public: { return mMarkedCCGeneration; } - + + PRBool IsLoadedAsData() + { + return mLoadedAsData; + } protected: ~nsIDocument() @@ -940,6 +939,10 @@ protected: PRPackedBool mShellsAreHidden; + // True if we're loaded as data and therefor has any dangerous stuff, such + // as scripts and plugins, disabled. + PRPackedBool mLoadedAsData; + // The bidi options for this document. What this bitfield means is // defined in nsBidiUtils.h PRUint32 mBidiOptions; diff --git a/mozilla/content/base/src/Makefile.in b/mozilla/content/base/src/Makefile.in index 9c9138dc004..3ba54f079e6 100644 --- a/mozilla/content/base/src/Makefile.in +++ b/mozilla/content/base/src/Makefile.in @@ -113,6 +113,7 @@ CPPSRCS = \ nsContentSink.cpp \ nsContentUtils.cpp \ nsCopySupport.cpp \ + nsCrossSiteListenerProxy.cpp \ nsDataDocumentContentPolicy.cpp \ nsDOMAttribute.cpp \ nsDOMAttributeMap.cpp \ diff --git a/mozilla/content/base/src/nsCrossSiteListenerProxy.cpp b/mozilla/content/base/src/nsCrossSiteListenerProxy.cpp index 0505ffd2361..99ca24081c9 100644 --- a/mozilla/content/base/src/nsCrossSiteListenerProxy.cpp +++ b/mozilla/content/base/src/nsCrossSiteListenerProxy.cpp @@ -1,791 +1,805 @@ -/* -*- 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 (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" - -static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID); - -NS_IMPL_ISUPPORTS5(nsCrossSiteListenerProxy, nsIStreamListener, - nsIRequestObserver, nsIContentSink, nsIXMLContentSink, - nsIExpatSink) - -nsCrossSiteListenerProxy::nsCrossSiteListenerProxy(nsIStreamListener* aOuter, - nsIPrincipal* aRequestingPrincipal) - : mOuter(aOuter), mAcceptState(eNotSet), mHasForwardedRequest(PR_FALSE) -{ - aRequestingPrincipal->GetURI(getter_AddRefs(mRequestingURI)); -} - -nsresult -nsCrossSiteListenerProxy::ForwardRequest() -{ - if (mHasForwardedRequest) { - return NS_OK; - } - - mHasForwardedRequest = PR_TRUE; - mParser = nsnull; - mParserListener = nsnull; - - if (mAcceptState != eAccept) { - // Only call OnStartRequest here. The call to Cancel will make us - // get an OnStopRequest later so we'll forward OnStopRequest then. - mOuterRequest->Cancel(NS_ERROR_DOM_BAD_URI); - mOuter->OnStartRequest(mOuterRequest, mOuterContext); - - return NS_ERROR_DOM_BAD_URI; - } - - nsresult rv = mOuter->OnStartRequest(mOuterRequest, mOuterContext); - NS_ENSURE_SUCCESS(rv, rv); - - if (!mStoredData.IsEmpty()) { - nsCOMPtr stream; - rv = NS_NewCStringInputStream(getter_AddRefs(stream), mStoredData); - NS_ENSURE_SUCCESS(rv, rv); - - rv = mOuter->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 this was actually a cross domain request - nsCOMPtr channel = do_QueryInterface(aRequest); - if (!channel) { - return NS_ERROR_DOM_BAD_URI; - } - - nsCOMPtr finalURI; - channel->GetURI(getter_AddRefs(finalURI)); - nsresult rv = nsContentUtils::GetSecurityManager()-> - CheckSameOriginURI(mRequestingURI, finalURI); - if (NS_SUCCEEDED(rv)) { - mAcceptState = eAccept; - return ForwardRequest(); - } - - nsCOMPtr http = do_QueryInterface(channel); - if (http) { - PRBool succeeded; - rv = http->GetRequestSucceeded(&succeeded); - NS_ENSURE_SUCCESS(rv, rv); - - if (!succeeded) { - mAcceptState = eDeny; - return ForwardRequest(); - } - } - - // 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 Content-Access-Control header - if (http) { - nsCAutoString ac; - rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Access-Control"), ac); - - if (NS_SUCCEEDED(rv)) { - CheckHeader(ac); - } - } - - if (mAcceptState == eDeny) { - return ForwardRequest(); - } - - // 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 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 serv = - do_GetService("@mozilla.org/streamConverters;1", &rv); - if (NS_SUCCEEDED(rv)) { - nsCOMPtr 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 stackedListener = mParserListener; - return stackedListener->OnStartRequest(aRequest, aContext); -} - -NS_IMETHODIMP -nsCrossSiteListenerProxy::OnStopRequest(nsIRequest* aRequest, - nsISupports* aContext, - nsresult aStatusCode) -{ - if (mHasForwardedRequest) { - return mOuter->OnStopRequest(aRequest, aContext, aStatusCode); - } - - mAcceptState = eDeny; - return ForwardRequest(); -} - -NS_METHOD -StringSegmentWriter(nsIInputStream *aInStream, - void *aClosure, - const char *aFromSegment, - PRUint32 aToOffset, - PRUint32 aCount, - PRUint32 *aWriteCount) -{ - nsCString* dest = static_cast(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) { - return mOuter->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 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 stackedListener = mParserListener; - return stackedListener->OnDataAvailable(aRequest, aContext, stream, aOffset, - aCount); -} - -NS_IMETHODIMP -nsCrossSiteListenerProxy::HandleStartElement(const PRUnichar *aName, - const PRUnichar **aAtts, - PRUint32 aAttsCount, - PRInt32 aIndex, - PRUint32 aLineNumber) -{ - // We're done processing the prolog. - return ForwardRequest(); -} - -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 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(); - } - } - - PRBool matchesRule = PR_FALSE; - - nsWhitespaceTokenizer itemTok(itemList); - - if (!itemTok.hasMoreTokens()) { - mAcceptState = eDeny; - - return ForwardRequest(); - } - - 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(); - } - - 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(); - } - - return NS_OK; -} - -NS_IMETHODIMP -nsCrossSiteListenerProxy::WillBuildModel() -{ - nsCOMPtr dtd; - mParser->GetDTD(getter_AddRefs(dtd)); - NS_ASSERTION(dtd, "missing dtd in WillBuildModel"); - if (dtd && !(dtd->GetType() & NS_IPARSER_FLAG_XML)) { - return ForwardRequest(); - } - - 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; -} - -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 patternSubdomains; - - // subdomain ::= label | subdomain "." label - do { - iter = start; - if (!EatAlpha(iter, end)) { - DENY_AND_RETURN PR_FALSE; - } - - while (EatAlpha(iter, end) || - EatDigit(iter, end) || - EatChar(iter, end, '-')) {} - - 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 && - patternPort != NS_GetRealPort(mRequestingURI)) { - return PR_FALSE; - } - - // Check subdomain - PRUint32 patternPos = patternSubdomains.Length(); - PRUint32 reqPos = mReqSubdomains.Length(); - do { - --patternPos; - --reqPos; - if (!patternSubdomains[patternPos].LowerCaseEqualsASCII( - mReqSubdomains[reqPos].get(), mReqSubdomains[reqPos].Length())) { - return PR_FALSE; - } - } while (patternPos > 0 && reqPos > 0); - - // Only matches if we've matched all of pattern and either matched all of - // mRequestingURI and there's no wildcard, or there's a wildcard and there - // are still elements of mRequestingURI left. - - return patternPos == 0 && - ((reqPos == 0 && !patternHasWild) || - (reqPos != 0 && patternHasWild)); -} +/* -*- 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 (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" + +static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID); + +NS_IMPL_ISUPPORTS5(nsCrossSiteListenerProxy, nsIStreamListener, + nsIRequestObserver, nsIContentSink, nsIXMLContentSink, + nsIExpatSink) + +nsCrossSiteListenerProxy::nsCrossSiteListenerProxy(nsIStreamListener* aOuter, + nsIPrincipal* aRequestingPrincipal) + : mOuter(aOuter), mAcceptState(eNotSet), mHasForwardedRequest(PR_FALSE) +{ + aRequestingPrincipal->GetURI(getter_AddRefs(mRequestingURI)); +} + +nsresult +nsCrossSiteListenerProxy::ForwardRequest(PRBool aFromStop) +{ + if (mHasForwardedRequest) { + return NS_OK; + } + + mHasForwardedRequest = PR_TRUE; + mParser = nsnull; + mParserListener = nsnull; + + if (mAcceptState != eAccept) { + mOuterRequest->Cancel(NS_ERROR_DOM_BAD_URI); + mOuter->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) { + mOuter->OnStopRequest(mOuterRequest, mOuterContext, NS_ERROR_DOM_BAD_URI); + } + + return NS_ERROR_DOM_BAD_URI; + } + + nsresult rv = mOuter->OnStartRequest(mOuterRequest, mOuterContext); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mStoredData.IsEmpty()) { + nsCOMPtr stream; + rv = NS_NewCStringInputStream(getter_AddRefs(stream), mStoredData); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mOuter->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 channel = do_QueryInterface(aRequest); + if (!channel) { + return NS_ERROR_DOM_BAD_URI; + } + nsCOMPtr finalURI; + channel->GetURI(getter_AddRefs(finalURI)); + rv = nsContentUtils::GetSecurityManager()-> + CheckSameOriginURI(mRequestingURI, finalURI); + if (NS_SUCCEEDED(rv)) { + mAcceptState = eAccept; + return ForwardRequest(PR_FALSE); + } + + nsCOMPtr 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 Content-Access-Control header + if (http) { + nsCAutoString ac; + rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-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 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 serv = + do_GetService("@mozilla.org/streamConverters;1", &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr 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 stackedListener = mParserListener; + return stackedListener->OnStartRequest(aRequest, aContext); +} + +NS_IMETHODIMP +nsCrossSiteListenerProxy::OnStopRequest(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatusCode) +{ + if (mHasForwardedRequest) { + return mOuter->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(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) { + return mOuter->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 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 stackedListener = mParserListener; + return stackedListener->OnDataAvailable(aRequest, aContext, stream, aOffset, + aCount); +} + +NS_IMETHODIMP +nsCrossSiteListenerProxy::HandleStartElement(const PRUnichar *aName, + const PRUnichar **aAtts, + PRUint32 aAttsCount, + PRInt32 aIndex, + PRUint32 aLineNumber) +{ + // We're done processing the prolog. + return ForwardRequest(PR_FALSE); +} + +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 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 dtd; + mParser->GetDTD(getter_AddRefs(dtd)); + NS_ASSERTION(dtd, "missing dtd in WillBuildModel"); + if (dtd && !(dtd->GetType() & NS_IPARSER_FLAG_XML)) { + return ForwardRequest(PR_FALSE); + } + + 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; +} + +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 patternSubdomains; + + // subdomain ::= label | subdomain "." label + do { + iter = start; + if (!EatAlpha(iter, end)) { + DENY_AND_RETURN PR_FALSE; + } + + while (EatAlpha(iter, end) || + EatDigit(iter, end) || + EatChar(iter, end, '-')) {} + + 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 && + patternPort != NS_GetRealPort(mRequestingURI)) { + return PR_FALSE; + } + + // Check subdomain + PRUint32 patternPos = patternSubdomains.Length(); + PRUint32 reqPos = mReqSubdomains.Length(); + do { + --patternPos; + --reqPos; + if (!patternSubdomains[patternPos].LowerCaseEqualsASCII( + mReqSubdomains[reqPos].get(), mReqSubdomains[reqPos].Length())) { + return PR_FALSE; + } + } while (patternPos > 0 && reqPos > 0); + + // Only matches if we've matched all of pattern and either matched all of + // mRequestingURI and there's no wildcard, or there's a wildcard and there + // are still elements of mRequestingURI left. + + return patternPos == 0 && + ((reqPos == 0 && !patternHasWild) || + (reqPos != 0 && patternHasWild)); +} diff --git a/mozilla/content/base/src/nsCrossSiteListenerProxy.h b/mozilla/content/base/src/nsCrossSiteListenerProxy.h index 0492ad2e73c..7577b76aac9 100644 --- a/mozilla/content/base/src/nsCrossSiteListenerProxy.h +++ b/mozilla/content/base/src/nsCrossSiteListenerProxy.h @@ -1,97 +1,97 @@ -/* -*- 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 (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" - -class nsIURI; -class nsIParser; -class nsIPrincipal; - -class nsCrossSiteListenerProxy : public nsIStreamListener, - public nsIXMLContentSink, - public nsIExpatSink -{ -public: - nsCrossSiteListenerProxy(nsIStreamListener* aOuter, - nsIPrincipal* aRequestingPrincipal); - - NS_DECL_ISUPPORTS - NS_DECL_NSIREQUESTOBSERVER - NS_DECL_NSISTREAMLISTENER - NS_DECL_NSIEXPATSINK - - // 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 ForwardRequest(); - PRBool MatchPatternList(const char*& aIter, const char* aEnd); - void CheckHeader(const nsCString& aHeader); - PRBool VerifyAndMatchDomainPattern(const nsACString& aDomainPattern); - - nsCOMPtr mOuter; - nsCOMPtr mOuterRequest; - nsCOMPtr mOuterContext; - nsCOMPtr mParserListener; - nsCOMPtr mParser; - nsCOMPtr mRequestingURI; - nsTArray mReqSubdomains; - nsCString mStoredData; - enum { - eAccept, - eDeny, - eNotSet - } mAcceptState; - PRBool mHasForwardedRequest; -}; +/* -*- 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 (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" + +class nsIURI; +class nsIParser; +class nsIPrincipal; + +class nsCrossSiteListenerProxy : public nsIStreamListener, + public nsIXMLContentSink, + public nsIExpatSink +{ +public: + nsCrossSiteListenerProxy(nsIStreamListener* aOuter, + nsIPrincipal* aRequestingPrincipal); + + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIEXPATSINK + + // 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 ForwardRequest(PRBool aCallStop); + PRBool MatchPatternList(const char*& aIter, const char* aEnd); + void CheckHeader(const nsCString& aHeader); + PRBool VerifyAndMatchDomainPattern(const nsACString& aDomainPattern); + + nsCOMPtr mOuter; + nsCOMPtr mOuterRequest; + nsCOMPtr mOuterContext; + nsCOMPtr mParserListener; + nsCOMPtr mParser; + nsCOMPtr mRequestingURI; + nsTArray mReqSubdomains; + nsCString mStoredData; + enum { + eAccept, + eDeny, + eNotSet + } mAcceptState; + PRBool mHasForwardedRequest; +}; diff --git a/mozilla/content/base/src/nsDocument.cpp b/mozilla/content/base/src/nsDocument.cpp index 74b6d8a2847..04bfe5749f5 100644 --- a/mozilla/content/base/src/nsDocument.cpp +++ b/mozilla/content/base/src/nsDocument.cpp @@ -1428,6 +1428,19 @@ nsDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel, } #endif + if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) { + mLoadedAsData = PR_TRUE; + // We need to disable script & style loading in this case. + // We leave them disabled even in EndLoad(), and let anyone + // who puts the document on display to worry about enabling. + + // Do not load/process scripts when loading as data + ScriptLoader()->SetEnabled(PR_FALSE); + + // styles + CSSLoader()->SetEnabled(PR_FALSE); // Do not load/process styles when loading as data + } + if (aReset) { Reset(aChannel, aLoadGroup); } diff --git a/mozilla/content/base/src/nsGenericElement.cpp b/mozilla/content/base/src/nsGenericElement.cpp index 92044c5a8df..c866cb56b1c 100644 --- a/mozilla/content/base/src/nsGenericElement.cpp +++ b/mozilla/content/base/src/nsGenericElement.cpp @@ -3426,6 +3426,13 @@ nsGenericElement::AddScriptEventListener(nsIAtom* aEventName, const nsAString& aValue, PRBool aDefer) { + nsIDocument *ownerDoc = GetOwnerDoc(); + if (!ownerDoc || ownerDoc->IsLoadedAsData()) { + // Make this a no-op rather than throwing an error to avoid + // the error causing problems setting the attribute. + return NS_OK; + } + NS_PRECONDITION(aEventName, "Must have event name!"); nsCOMPtr target; PRBool defer = PR_TRUE; @@ -3437,8 +3444,6 @@ nsGenericElement::AddScriptEventListener(nsIAtom* aEventName, NS_ENSURE_SUCCESS(rv, rv); if (manager) { - nsIDocument *ownerDoc = GetOwnerDoc(); - defer = defer && aDefer; // only defer if everyone agrees... PRUint32 lang = GetScriptTypeID(); diff --git a/mozilla/content/base/src/nsObjectLoadingContent.cpp b/mozilla/content/base/src/nsObjectLoadingContent.cpp index 01cff3d09f2..f2c377b0372 100644 --- a/mozilla/content/base/src/nsObjectLoadingContent.cpp +++ b/mozilla/content/base/src/nsObjectLoadingContent.cpp @@ -847,6 +847,11 @@ nsObjectLoadingContent::LoadObject(nsIURI* aURI, } // Security checks + if (doc->IsLoadedAsData()) { + Fallback(PR_FALSE); + return NS_OK; + } + // Can't do security checks without a URI - hopefully the plugin will take // care of that // Null URIs happen when the URL to load is specified via other means than the diff --git a/mozilla/content/base/src/nsParserUtils.cpp b/mozilla/content/base/src/nsParserUtils.cpp index bb3ca24199b..833390c3fe2 100644 --- a/mozilla/content/base/src/nsParserUtils.cpp +++ b/mozilla/content/base/src/nsParserUtils.cpp @@ -47,20 +47,19 @@ #include "nsContentUtils.h" #include "nsIParserService.h" -#define SKIP_WHITESPACE(iter, end_iter) \ +#define SKIP_WHITESPACE(iter, end_iter, end_res) \ while ((iter) != (end_iter) && nsCRT::IsAsciiSpace(*(iter))) { \ ++(iter); \ } \ - if ((iter) == (end_iter)) \ - break + if ((iter) == (end_iter)) { \ + return (end_res); \ + } #define SKIP_ATTR_NAME(iter, end_iter) \ while ((iter) != (end_iter) && !nsCRT::IsAsciiSpace(*(iter)) && \ *(iter) != '=') { \ ++(iter); \ - } \ - if ((iter) == (end_iter)) \ - break + } PRBool nsParserUtils::GetQuotedAttributeValue(const nsString& aSource, nsIAtom *aName, @@ -73,29 +72,33 @@ nsParserUtils::GetQuotedAttributeValue(const nsString& aSource, nsIAtom *aName, const PRUnichar *iter; while (start != end) { - SKIP_WHITESPACE(start, end); + SKIP_WHITESPACE(start, end, PR_FALSE) iter = start; - SKIP_ATTR_NAME(iter, end); + SKIP_ATTR_NAME(iter, end) + + if (start == iter) { + return PR_FALSE; + } // Remember the attr name. const nsDependentSubstring & attrName = Substring(start, iter); // Now check whether this is a valid name="value" pair. start = iter; - SKIP_WHITESPACE(start, end); + SKIP_WHITESPACE(start, end, PR_FALSE) if (*start != '=') { // No '=', so this is not a name="value" pair. We don't know // what it is, and we have no way to handle it. - break; + return PR_FALSE; } // Have to skip the value. ++start; - SKIP_WHITESPACE(start, end); + SKIP_WHITESPACE(start, end, PR_FALSE) PRUnichar q = *start; if (q != kQuote && q != kApostrophe) { // Not a valid quoted value, so bail. - break; + return PR_FALSE; } ++start; // Point to the first char of the value. @@ -107,7 +110,7 @@ nsParserUtils::GetQuotedAttributeValue(const nsString& aSource, nsIAtom *aName, if (iter == end) { // Oops, unterminated quoted string. - break; + return PR_FALSE; } // At this point attrName holds the name of the "attribute" and @@ -168,6 +171,74 @@ nsParserUtils::GetQuotedAttributeValue(const nsString& aSource, nsIAtom *aName, return PR_FALSE; } +PRBool +nsParserUtils::GetQuotedAttrNameAt(const nsString& aSource, PRUint32 aIndex, + nsAString& aName) +{ + aName.Truncate(); + + const PRUnichar *start = aSource.get(); + const PRUnichar *end = start + aSource.Length(); + const PRUnichar *iter; + PRUint32 currIndex = 0; + + for (;;) { + SKIP_WHITESPACE(start, end, PR_TRUE) + + iter = start; + SKIP_ATTR_NAME(iter, end) + + if (start == iter) { + return PR_FALSE; + } + + // Remember the attr name. + const nsDependentSubstring & attrName = Substring(start, iter); + + // Now check whether this is a valid name="value" pair. + start = iter; + SKIP_WHITESPACE(start, end, PR_FALSE); + if (*start != '=') { + // No '=', so this is not a name="value" pair. We don't know + // what it is, and we have no way to handle it. + return PR_FALSE; + } + + // Have to skip the value. + ++start; + SKIP_WHITESPACE(start, end, PR_FALSE); + PRUnichar q = *start; + if (q != kQuote && q != kApostrophe) { + // Not a valid quoted value, so bail. + return PR_FALSE; + } + + // Scan to the end of the value. + do { + ++start; + } while (start != end && *start != q); + + if (start == end) { + // Oops, unterminated quoted string. + return PR_FALSE; + } + + // At this point attrName holds the name of the "attribute" + + if (aIndex == currIndex) { + aName = attrName; + + return PR_TRUE; + } + + // Resume scanning after the end of the attribute value (past the quote + // char). + ++start; + ++currIndex; + } + + return PR_TRUE; +} // Returns PR_TRUE if the language name is a version of JavaScript and // PR_FALSE otherwise diff --git a/mozilla/content/base/src/nsParserUtils.h b/mozilla/content/base/src/nsParserUtils.h index aa63ccbe15a..c7a8e503d75 100644 --- a/mozilla/content/base/src/nsParserUtils.h +++ b/mozilla/content/base/src/nsParserUtils.h @@ -57,11 +57,32 @@ public: * @param aName the name of the attribute to get the value for * @param aValue [out] the value for the attribute with name specified in * aAttribute. Empty if the attribute isn't present. + * @return PR_TRUE if the attribute exists and was successfully parsed. + * PR_FALSE if the attribute doesn't exist, or has a malformed + * value, such as an unknown or unterminated entity. */ static PRBool GetQuotedAttributeValue(const nsString& aSource, nsIAtom *aName, nsAString& aValue); + /** + * This will parse aSource, to extract the name of the pseudo attribute + * at the specified index. See + * http://www.w3.org/TR/xml-stylesheet/#NT-StyleSheetPI for the specification + * which is used to parse aSource. + * + * @param aSource the string to parse + * @param aIndex the index of the attribute to get the value for + * @param aName [out] the name for the attribute with specified index. + * Empty if there aren't enough attributes. + * @return PR_TRUE if parsing succeeded, even if there aren't enough + * attributes. + * PR_FALSE if parsing failed. + */ + static PRBool + GetQuotedAttrNameAt(const nsString& aSource, PRUint32 aIndex, + nsAString& aName); + static PRBool IsJavaScriptLanguage(const nsString& aName, PRUint32 *aVerFlags); diff --git a/mozilla/content/base/src/nsXMLHttpRequest.cpp b/mozilla/content/base/src/nsXMLHttpRequest.cpp index 7b300257b9f..08236bc502b 100644 --- a/mozilla/content/base/src/nsXMLHttpRequest.cpp +++ b/mozilla/content/base/src/nsXMLHttpRequest.cpp @@ -83,7 +83,10 @@ #include "nsContentPolicyUtils.h" #include "nsContentErrors.h" #include "nsLayoutStatics.h" +#include "nsCrossSiteListenerProxy.h" #include "nsDOMError.h" +#include "nsIHTMLDocument.h" +#include "nsWhitespaceTokenizer.h" #define LOAD_STR "load" #define ERROR_STR "error" @@ -106,9 +109,13 @@ #define XML_HTTP_REQUEST_ABORTED (1 << 7) // Internal #define XML_HTTP_REQUEST_ASYNC (1 << 8) // Internal #define XML_HTTP_REQUEST_PARSEBODY (1 << 9) // Internal -#define XML_HTTP_REQUEST_XSITEENABLED (1 << 10) // Internal +#define XML_HTTP_REQUEST_XSITEENABLED (1 << 10) // Internal, Is any cross-site request allowed? + // Even if this is false the + // access-control spec is supported #define XML_HTTP_REQUEST_SYNCLOOPING (1 << 11) // Internal #define XML_HTTP_REQUEST_MULTIPART (1 << 12) // Internal +#define XML_HTTP_REQUEST_USE_XSITE_AC (1 << 13) // Internal +#define XML_HTTP_REQUEST_NON_GET (1 << 14) // Internal #define XML_HTTP_REQUEST_LOADSTATES \ (XML_HTTP_REQUEST_UNINITIALIZED | \ @@ -220,6 +227,114 @@ 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, + const nsACString& aRequestMethod) + : mOuterChannel(aOuterChannel), mOuterListener(aOuterListener), + mOuterContext(aOuterContext), mRequestMethod(aRequestMethod) + { } + + NS_DECL_ISUPPORTS + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICHANNELEVENTSINK + +private: + nsCOMPtr mOuterChannel; + nsCOMPtr mOuterListener; + nsCOMPtr mOuterContext; + 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; + } + + nsCOMPtr http; + if (NS_SUCCEEDED(rv)) { + http = do_QueryInterface(aRequest, &rv); + } + if (NS_SUCCEEDED(rv)) { + rv = NS_ERROR_DOM_BAD_URI; + nsCString allow; + http->GetResponseHeader(NS_LITERAL_CSTRING("Allow"), allow); + nsCWhitespaceTokenizer tok(allow); + while (tok.hasMoreTokens()) { + if (mRequestMethod.Equals(tok.nextToken(), + nsCaseInsensitiveCStringComparator())) { + rv = NS_OK; + break; + } + } + } + + 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); +} + static nsIScriptContext * GetCurrentContext() @@ -767,6 +882,9 @@ nsXMLHttpRequest::Abort() if (mChannel) { mChannel->Cancel(NS_BINDING_ABORTED); } + if (mACGetChannel) { + mACGetChannel->Cancel(NS_BINDING_ABORTED); + } mDocument = nsnull; mState |= XML_HTTP_REQUEST_ABORTED; @@ -789,6 +907,10 @@ nsXMLHttpRequest::GetAllResponseHeaders(char **_retval) NS_ENSURE_ARG_POINTER(_retval); *_retval = nsnull; + if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) { + return NS_OK; + } + nsCOMPtr httpChannel = GetCurrentHttpChannel(); if (httpChannel) { @@ -817,6 +939,26 @@ nsXMLHttpRequest::GetResponseHeader(const nsACString& header, nsresult rv = NS_OK; _retval.Truncate(); + // Check for dangerous headers + if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) { + 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 httpChannel = GetCurrentHttpChannel(); if (httpChannel) { @@ -988,6 +1130,56 @@ nsXMLHttpRequest::GetCurrentHttpChannel() return httpChannel; } +static PRBool +IsSameOrigin(nsIPrincipal* aPrincipal, nsIChannel* aChannel) +{ + nsCOMPtr codebase; + nsresult rv = aPrincipal->GetURI(getter_AddRefs(codebase)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr channelURI; + rv = aChannel->GetURI(getter_AddRefs(channelURI)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = nsContentUtils::GetSecurityManager()->CheckSameOriginURI(codebase, channelURI); + return NS_SUCCEEDED(rv); +} + +nsresult +nsXMLHttpRequest::CheckChannelForCrossSiteRequest() +{ + // First check if this is a same-origin request, or if cross-site requests + // are enabled. + if ((mState & XML_HTTP_REQUEST_XSITEENABLED) || + IsSameOrigin(mPrincipal, mChannel)) { + 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 and set XMLHttpRequest-Security-Check + nsCOMPtr http = do_QueryInterface(mChannel); + if (http) { + PRUint32 i; + for (i = 0; i < mExtraRequestHeaders.Length(); ++i) { + http->SetRequestHeader(mExtraRequestHeaders[i], EmptyCString(), PR_FALSE); + } + mExtraRequestHeaders.Clear(); + } + + // Cancel if username/password is supplied to avoid brute-force password + // hacking + nsCOMPtr channelURI; + nsresult rv = mChannel->GetURI(getter_AddRefs(channelURI)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString userpass; + channelURI->GetUserPass(userpass); + return userpass.IsEmpty() ? NS_OK : NS_ERROR_DOM_BAD_URI; +} /* noscript void openRequest (in AUTF8String method, in AUTF8String url, in boolean async, in AString user, in AString password); */ NS_IMETHODIMP @@ -1002,11 +1194,25 @@ nsXMLHttpRequest::OpenRequest(const nsACString& method, // Disallow HTTP/1.1 TRACE method (see bug 302489) // and MS IIS equivalent TRACK (see bug 381264) - if (method.LowerCaseEqualsASCII("trace") || - method.LowerCaseEqualsASCII("track")) { + if (method.LowerCaseEqualsLiteral("trace") || + method.LowerCaseEqualsLiteral("track")) { return NS_ERROR_INVALID_ARG; } + // Get the principal. + // XXX This should be done at construction time. + nsCOMPtr doc = + do_QueryInterface(nsContentUtils::GetDocumentFromCaller()); + if (doc) { + mPrincipal = doc->NodePrincipal(); + } + else { + nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager(); + if (secMan) { + secMan->GetSubjectPrincipal(getter_AddRefs(mPrincipal)); + } + } + nsresult rv; nsCOMPtr uri; PRBool authp = PR_FALSE; @@ -1046,7 +1252,7 @@ nsXMLHttpRequest::OpenRequest(const nsACString& method, // mScriptContext should be initialized because of GetBaseURI() above. // Still need to consider the case that doc is nsnull however. - nsCOMPtr doc = GetDocumentFromScriptContext(mScriptContext); + doc = GetDocumentFromScriptContext(mScriptContext); PRInt16 shouldLoad = nsIContentPolicy::ACCEPT; rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_XMLHTTPREQUEST, uri, @@ -1095,11 +1301,36 @@ nsXMLHttpRequest::OpenRequest(const nsACString& method, loadFlags); if (NS_FAILED(rv)) return rv; + // Check if we're doing a cross-origin request. + if (!(mState & XML_HTTP_REQUEST_XSITEENABLED) && + !IsSameOrigin(mPrincipal, mChannel)) { + mState |= XML_HTTP_REQUEST_USE_XSITE_AC; + } + //mChannel->SetAuthTriedWithPrehost(authp); nsCOMPtr 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 GET 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)) { + rv = NS_NewChannel(getter_AddRefs(mACGetChannel), uri, nsnull, loadGroup, nsnull, + loadFlags); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr acHttp = do_QueryInterface(mACGetChannel); + rv = acHttp->SetRequestHeader( + NS_LITERAL_CSTRING("XMLHttpRequest-Security-Check"), method, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); } ChangeState(XML_HTTP_REQUEST_OPENED); @@ -1143,7 +1374,7 @@ nsXMLHttpRequest::Open(const nsACString& method, const nsACString& url) return NS_ERROR_FAILURE; } - rv = secMan->CheckConnect(cx, targetURI, "XMLHttpRequest","open"); + rv = secMan->CheckConnect(cx, targetURI, "XMLHttpRequest", "open-uri"); if (NS_FAILED(rv)) { // Security check failed. @@ -1295,6 +1526,13 @@ nsXMLHttpRequest::OnStartRequest(nsIRequest *request, nsISupports *ctxt) getter_AddRefs(mDocument)); NS_ENSURE_SUCCESS(rv, rv); + if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) { + nsCOMPtr htmlDoc = do_QueryInterface(mDocument); + if (htmlDoc) { + htmlDoc->DisableCookieAccess(); + } + } + // Reset responseBody mResponseBody.Truncate(); @@ -1551,18 +1789,6 @@ nsXMLHttpRequest::Send(nsIVariant *aBody) // if there are no event listeners set and we are doing // an asynchronous call. - nsCOMPtr doc = - do_QueryInterface(nsContentUtils::GetDocumentFromCaller()); - if (doc) { - mPrincipal = doc->NodePrincipal(); - } - else { - nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager(); - if (secMan) { - secMan->GetSubjectPrincipal(getter_AddRefs(mPrincipal)); - } - } - // Ignore argument if method is GET, there is no point in trying to // upload anything nsCAutoString method; @@ -1723,18 +1949,27 @@ nsXMLHttpRequest::Send(nsIVariant *aBody) mScriptContext = GetCurrentContext(); } + rv = CheckChannelForCrossSiteRequest(); + NS_ENSURE_SUCCESS(rv, rv); + // Hook us up to listen to redirects and the like mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks)); mChannel->SetNotificationCallbacks(this); - nsCOMPtr listener; + // Create our listener + nsCOMPtr listener = this; + 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); + NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY); + } + if (mState & XML_HTTP_REQUEST_MULTIPART) { - listener = new nsMultipartProxyListener(this); + listener = new nsMultipartProxyListener(listener); if (!listener) { return NS_ERROR_OUT_OF_MEMORY; } - } else { - listener = this; } // Bypass the network cache in cases where it makes no sense: @@ -1753,6 +1988,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 @@ -1760,8 +1999,22 @@ 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 acListener = + new nsACProxyListener(mChannel, listener, nsnull, method); + NS_ENSURE_TRUE(acListener, NS_ERROR_OUT_OF_MEMORY); + + listener = new nsCrossSiteListenerProxy(acListener, mPrincipal); + NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY); + + rv = mACGetChannel->AsyncOpen(acListener, 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 @@ -1798,7 +2051,23 @@ NS_IMETHODIMP nsXMLHttpRequest::SetRequestHeader(const nsACString& header, const nsACString& value) { - if (!mChannel) // open() initializes mChannel, and open() + 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; + } + } + + nsCOMPtr httpChannel(do_QueryInterface(mChannel)); + if (!httpChannel) // open() initializes mChannel, and open() return NS_ERROR_FAILURE; // must be called before first setRequestHeader() // Prevent modification to certain HTTP headers (see bug 302263), unless @@ -1810,31 +2079,55 @@ nsXMLHttpRequest::SetRequestHeader(const nsACString& header, } PRBool privileged; - nsresult rv = secMan->IsCapabilityEnabled("UniversalBrowserWrite", - &privileged); + rv = secMan->IsCapabilityEnabled("UniversalBrowserWrite", &privileged); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; if (!privileged) { + // Check for dangerous headers const char *kInvalidHeaders[] = { - "host", "content-length", "transfer-encoding", "via", "upgrade" + "accept-charset", "accept-encoding", "connection", "content-length", + "content-transfer-encoding", "date", "expect", "host", "keep-alive", + "proxy-connection", "referer", "referer-root", "te", "trailer", + "transfer-encoding", "upgrade", "via", "xmlhttprequest-security-check" }; - for (size_t i = 0; i < NS_ARRAY_LENGTH(kInvalidHeaders); ++i) { + PRUint32 i; + for (i = 0; i < NS_ARRAY_LENGTH(kInvalidHeaders); ++i) { if (header.LowerCaseEqualsASCII(kInvalidHeaders[i])) { 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); + } } - nsCOMPtr httpChannel(do_QueryInterface(mChannel)); - - if (httpChannel) { - // We need to set, not add to, the header. - return httpChannel->SetRequestHeader(header, value, PR_FALSE); - } - - return NS_OK; + // We need to set, not add to, the header. + return httpChannel->SetRequestHeader(header, value, PR_FALSE); } /* readonly attribute long readyState; */ @@ -2025,51 +2318,26 @@ nsXMLHttpRequest::OnChannelRedirect(nsIChannel *aOldChannel, { NS_PRECONDITION(aNewChannel, "Redirect without a channel?"); - if (mScriptContext && !(mState & XML_HTTP_REQUEST_XSITEENABLED)) { - nsresult rv = NS_ERROR_FAILURE; - - nsCOMPtr stack(do_GetService("@mozilla.org/js/xpc/ContextStack;1", & rv)); - if (NS_FAILED(rv)) - return rv; - - JSContext *cx = (JSContext *)mScriptContext->GetNativeContext(); - if (!cx) - return NS_ERROR_UNEXPECTED; - - nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager(); - if (!secMan) { - return NS_ERROR_FAILURE; - } - - nsCOMPtr newURI; - rv = aNewChannel->GetURI(getter_AddRefs(newURI)); // The redirected URI - if (NS_FAILED(rv)) - return rv; - - stack->Push(cx); - - rv = secMan->CheckSameOrigin(cx, newURI); - - stack->Pop(&cx); - - if (NS_FAILED(rv)) { - // The security manager set a pending exception. Since we're - // running under the event loop, we need to report it. - ::JS_ReportPendingException(cx); - return rv; - } - } - + nsresult rv; if (mChannelEventSink) { - nsresult rv = + rv = mChannelEventSink->OnChannelRedirect(aOldChannel, aNewChannel, aFlags); - if (NS_FAILED(rv)) { - return rv; - } + NS_ENSURE_SUCCESS(rv, rv); } mChannel = aNewChannel; + rv = CheckChannelForCrossSiteRequest(); + NS_ENSURE_SUCCESS(rv, 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; + } + return NS_OK; } diff --git a/mozilla/content/base/src/nsXMLHttpRequest.h b/mozilla/content/base/src/nsXMLHttpRequest.h index 4f387f75f94..990997c0ee9 100644 --- a/mozilla/content/base/src/nsXMLHttpRequest.h +++ b/mozilla/content/base/src/nsXMLHttpRequest.h @@ -157,11 +157,20 @@ protected: void ClearEventListeners(); already_AddRefed GetCurrentHttpChannel(); + /** + * Check if mChannel 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(); + nsCOMPtr mContext; nsCOMPtr mPrincipal; nsCOMPtr mChannel; nsCOMPtr mReadRequest; nsCOMPtr mDocument; + nsCOMPtr mACGetChannel; nsCOMArray mLoadEventListeners; nsCOMArray mErrorEventListeners; @@ -209,6 +218,10 @@ protected: nsCOMPtr mProgressEventSink; PRUint32 mState; + + // List of potentially dangerous headers explicitly set using + // SetRequestHeader. + nsTArray mExtraRequestHeaders; }; diff --git a/mozilla/content/base/test/Makefile.in b/mozilla/content/base/test/Makefile.in index 3394ff6438f..a6626674dfe 100644 --- a/mozilla/content/base/test/Makefile.in +++ b/mozilla/content/base/test/Makefile.in @@ -85,6 +85,16 @@ _TEST_FILES = test_bug5141.html \ test_bug375314.html \ test_bug382113.html \ bug382113_object.html \ + test_CrossSiteXHR.html \ + file_CrossSiteXHR_fail1.xml \ + file_CrossSiteXHR_fail2.xml \ + file_CrossSiteXHR_fail2.xml^headers^ \ + file_CrossSiteXHR_fail3.xml \ + file_CrossSiteXHR_fail4.xml \ + file_CrossSiteXHR_pass1.xml \ + file_CrossSiteXHR_pass1.xml^headers^ \ + file_CrossSiteXHR_pass2.xml \ + file_CrossSiteXHR_pass3.xml \ $(NULL) libs:: $(_TEST_FILES) diff --git a/mozilla/content/html/document/src/nsHTMLDocument.cpp b/mozilla/content/html/document/src/nsHTMLDocument.cpp index 032b30f2f3c..97d8c4c5e17 100644 --- a/mozilla/content/html/document/src/nsHTMLDocument.cpp +++ b/mozilla/content/html/document/src/nsHTMLDocument.cpp @@ -1964,6 +1964,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 service = do_GetService(NS_COOKIESERVICE_CONTRACTID); if (service) { @@ -1990,6 +1994,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 service = do_GetService(NS_COOKIESERVICE_CONTRACTID); if (service && mDocumentURI) { diff --git a/mozilla/content/html/document/src/nsHTMLDocument.h b/mozilla/content/html/document/src/nsHTMLDocument.h index 00e4596d03b..977037e016d 100644 --- a/mozilla/content/html/document/src/nsHTMLDocument.h +++ b/mozilla/content/html/document/src/nsHTMLDocument.h @@ -207,6 +207,11 @@ public: return mEditingState != eOff; } + virtual void DisableCookieAccess() + { + mDisableCookieAccess = PR_TRUE; + } + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(nsHTMLDocument, nsDocument) protected: @@ -389,6 +394,8 @@ protected: // XXXbz should this be reset if someone manually calls // SetContentType() on this document? PRInt32 mDefaultNamespaceID; + + PRBool mDisableCookieAccess; }; #endif /* nsHTMLDocument_h___ */ diff --git a/mozilla/content/html/document/src/nsIHTMLDocument.h b/mozilla/content/html/document/src/nsIHTMLDocument.h index 891bff0abad..b5695f8e75c 100644 --- a/mozilla/content/html/document/src/nsIHTMLDocument.h +++ b/mozilla/content/html/document/src/nsIHTMLDocument.h @@ -152,6 +152,11 @@ public: */ virtual nsresult GetDocumentAllResult(const nsAString& aID, nsISupports** aResult) = 0; + + /** + * Disables getting and setting cookies + */ + virtual void DisableCookieAccess() = 0; }; NS_DEFINE_STATIC_IID_ACCESSOR(nsIHTMLDocument, NS_IHTMLDOCUMENT_IID) diff --git a/mozilla/content/xml/document/src/nsXMLDocument.cpp b/mozilla/content/xml/document/src/nsXMLDocument.cpp index 84231037dd2..020a0cbb882 100644 --- a/mozilla/content/xml/document/src/nsXMLDocument.cpp +++ b/mozilla/content/xml/document/src/nsXMLDocument.cpp @@ -561,28 +561,18 @@ nsXMLDocument::StartDocumentLoad(const char* aCommand, PRBool aReset, nsIContentSink* aSink) { - if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) { - mLoadedAsData = PR_TRUE; - // We need to disable script & style loading in this case. - // We leave them disabled even in EndLoad(), and let anyone - // who puts the document on display to worry about enabling. - - // Do not load/process scripts when loading as data - ScriptLoader()->SetEnabled(PR_FALSE); - - // styles - CSSLoader()->SetEnabled(PR_FALSE); // Do not load/process styles when loading as data - } else if (nsCRT::strcmp("loadAsInteractiveData", aCommand) == 0) { - mLoadedAsInteractiveData = PR_TRUE; - aCommand = kLoadAsData; // XBL, for example, needs scripts and styles - } - nsresult rv = nsDocument::StartDocumentLoad(aCommand, aChannel, aLoadGroup, aContainer, aDocListener, aReset, aSink); if (NS_FAILED(rv)) return rv; + if (nsCRT::strcmp("loadAsInteractiveData", aCommand) == 0) { + mLoadedAsInteractiveData = PR_TRUE; + aCommand = kLoadAsData; // XBL, for example, needs scripts and styles + } + + PRInt32 charsetSource = kCharsetFromDocTypeDefault; nsCAutoString charset(NS_LITERAL_CSTRING("UTF-8")); TryChannelCharset(aChannel, charsetSource, charset); @@ -656,13 +646,6 @@ nsXMLDocument::EndLoad() nsDocument::EndLoad(); } -PRBool -nsXMLDocument::IsLoadedAsData() -{ - return mLoadedAsData; -} - - // nsIDOMNode interface NS_IMETHODIMP diff --git a/mozilla/content/xml/document/src/nsXMLDocument.h b/mozilla/content/xml/document/src/nsXMLDocument.h index 66544689351..9340d78a33d 100644 --- a/mozilla/content/xml/document/src/nsXMLDocument.h +++ b/mozilla/content/xml/document/src/nsXMLDocument.h @@ -75,8 +75,6 @@ public: virtual void EndLoad(); - virtual PRBool IsLoadedAsData(); - // nsIDOMNode interface NS_IMETHOD CloneNode(PRBool aDeep, nsIDOMNode** aReturn); @@ -112,7 +110,6 @@ protected: // cannot be null. PRPackedBool mChannelIsPending; PRPackedBool mCrossSiteAccessEnabled; - PRPackedBool mLoadedAsData; PRPackedBool mLoadedAsInteractiveData; PRPackedBool mAsync; PRPackedBool mLoopingForSyncLoad; diff --git a/mozilla/modules/libpref/src/init/all.js b/mozilla/modules/libpref/src/init/all.js index 22d223000e3..f9ba6bb846b 100644 --- a/mozilla/modules/libpref/src/init/all.js +++ b/mozilla/modules/libpref/src/init/all.js @@ -443,6 +443,7 @@ pref("capability.policy.mailnews.WebServiceProxyFactory.onError", "noAccess"); // XMLExtras pref("capability.policy.default.XMLHttpRequest.channel", "noAccess"); pref("capability.policy.default.XMLHttpRequest.getInterface", "noAccess"); +pref("capability.policy.default.XMLHttpRequest.open-uri", "allAccess"); pref("capability.policy.default.DOMParser.parseFromStream", "noAccess"); // Clipboard