Bug 389508: Cross site XMLHttpRequest. r=jst/biesi sr/dveditz/biesi

This commit is contained in:
jonas@sicking.cc 2007-07-26 19:49:18 -07:00
parent 4afa23fd6d
commit 24c4c1d539
18 changed files with 1435 additions and 1010 deletions

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -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 <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"
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<nsIStreamListener> mOuter;
nsCOMPtr<nsIRequest> mOuterRequest;
nsCOMPtr<nsISupports> mOuterContext;
nsCOMPtr<nsIStreamListener> mParserListener;
nsCOMPtr<nsIParser> mParser;
nsCOMPtr<nsIURI> mRequestingURI;
nsTArray<nsCString> 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 <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"
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<nsIStreamListener> mOuter;
nsCOMPtr<nsIRequest> mOuterRequest;
nsCOMPtr<nsISupports> mOuterContext;
nsCOMPtr<nsIStreamListener> mParserListener;
nsCOMPtr<nsIParser> mParser;
nsCOMPtr<nsIURI> mRequestingURI;
nsTArray<nsCString> mReqSubdomains;
nsCString mStoredData;
enum {
eAccept,
eDeny,
eNotSet
} mAcceptState;
PRBool mHasForwardedRequest;
};

View File

@ -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);
}

View File

@ -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<nsISupports> 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();

View File

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

View File

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

View File

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

View File

@ -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<nsIChannel> mOuterChannel;
nsCOMPtr<nsIStreamListener> mOuterListener;
nsCOMPtr<nsISupports> 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<nsIHttpChannel> 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<nsIHttpChannel> 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<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
if (httpChannel) {
@ -988,6 +1130,56 @@ nsXMLHttpRequest::GetCurrentHttpChannel()
return httpChannel;
}
static PRBool
IsSameOrigin(nsIPrincipal* aPrincipal, nsIChannel* aChannel)
{
nsCOMPtr<nsIURI> codebase;
nsresult rv = aPrincipal->GetURI(getter_AddRefs(codebase));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> 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<nsIHttpChannel> 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<nsIURI> 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<nsIDocument> 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<nsIURI> 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<nsIDocument> 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<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 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<nsIHttpChannel> 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<nsIHTMLDocument> 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<nsIDocument> 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<nsIStreamListener> listener;
// Create our listener
nsCOMPtr<nsIStreamListener> 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<nsIStreamListener> 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<nsIHttpChannel> 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<nsIHttpChannel> 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<nsIJSContextStack> 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<nsIURI> 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;
}

View File

@ -157,11 +157,20 @@ protected:
void ClearEventListeners();
already_AddRefed<nsIHttpChannel> 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<nsISupports> mContext;
nsCOMPtr<nsIPrincipal> mPrincipal;
nsCOMPtr<nsIChannel> mChannel;
nsCOMPtr<nsIRequest> mReadRequest;
nsCOMPtr<nsIDOMDocument> mDocument;
nsCOMPtr<nsIChannel> mACGetChannel;
nsCOMArray<nsIDOMEventListener> mLoadEventListeners;
nsCOMArray<nsIDOMEventListener> mErrorEventListeners;
@ -209,6 +218,10 @@ protected:
nsCOMPtr<nsIProgressEventSink> mProgressEventSink;
PRUint32 mState;
// List of potentially dangerous headers explicitly set using
// SetRequestHeader.
nsTArray<nsCString> mExtraRequestHeaders;
};

View File

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

View File

@ -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<nsICookieService> 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<nsICookieService> service = do_GetService(NS_COOKIESERVICE_CONTRACTID);
if (service && mDocumentURI) {

View File

@ -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___ */

View File

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

View File

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

View File

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

View File

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