mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
9dd62aa901
--HG-- rename : content/base/test/test_bug308484.html => content/base/test/test_xhr_forbidden_headers.html
3448 lines
107 KiB
C++
3448 lines
107 KiB
C++
/* -*- 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
|
|
* Netscape Communications Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 1998
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either 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 "nsXMLHttpRequest.h"
|
|
#include "nsISimpleEnumerator.h"
|
|
#include "nsIXPConnect.h"
|
|
#include "nsICharsetConverterManager.h"
|
|
#include "nsLayoutCID.h"
|
|
#include "nsXPIDLString.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsIURI.h"
|
|
#include "nsILoadGroup.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsStreamUtils.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsIUploadChannel.h"
|
|
#include "nsIUploadChannel2.h"
|
|
#include "nsIDOMSerializer.h"
|
|
#include "nsXPCOM.h"
|
|
#include "nsISupportsPrimitives.h"
|
|
#include "nsGUIEvent.h"
|
|
#include "nsIPrivateDOMEvent.h"
|
|
#include "prprf.h"
|
|
#include "nsIDOMEventListener.h"
|
|
#include "nsIJSContextStack.h"
|
|
#include "nsIScriptSecurityManager.h"
|
|
#include "nsWeakPtr.h"
|
|
#include "nsICharsetAlias.h"
|
|
#include "nsIScriptGlobalObject.h"
|
|
#include "nsIDOMClassInfo.h"
|
|
#include "nsIDOMElement.h"
|
|
#include "nsIDOMWindow.h"
|
|
#include "nsIMIMEService.h"
|
|
#include "nsCExternalHandlerService.h"
|
|
#include "nsIVariant.h"
|
|
#include "nsVariant.h"
|
|
#include "nsIParser.h"
|
|
#include "nsLoadListenerProxy.h"
|
|
#include "nsStringStream.h"
|
|
#include "nsIStreamConverterService.h"
|
|
#include "nsICachingChannel.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsEventDispatcher.h"
|
|
#include "nsDOMJSUtils.h"
|
|
#include "nsCOMArray.h"
|
|
#include "nsDOMClassInfo.h"
|
|
#include "nsIScriptableUConv.h"
|
|
#include "nsCycleCollectionParticipant.h"
|
|
#include "nsIContentPolicy.h"
|
|
#include "nsContentPolicyUtils.h"
|
|
#include "nsContentErrors.h"
|
|
#include "nsLayoutStatics.h"
|
|
#include "nsCrossSiteListenerProxy.h"
|
|
#include "nsDOMError.h"
|
|
#include "nsIHTMLDocument.h"
|
|
#include "nsIDOM3Document.h"
|
|
#include "nsIMultiPartChannel.h"
|
|
#include "nsIScriptObjectPrincipal.h"
|
|
#include "nsIStorageStream.h"
|
|
#include "nsIPromptFactory.h"
|
|
#include "nsIWindowWatcher.h"
|
|
#include "nsCharSeparatedTokenizer.h"
|
|
#include "nsIConsoleService.h"
|
|
#include "nsIChannelPolicy.h"
|
|
#include "nsChannelPolicy.h"
|
|
#include "nsIContentSecurityPolicy.h"
|
|
#include "nsAsyncRedirectVerifyHelper.h"
|
|
#include "jstypedarray.h"
|
|
|
|
#define LOAD_STR "load"
|
|
#define ERROR_STR "error"
|
|
#define ABORT_STR "abort"
|
|
#define LOADSTART_STR "loadstart"
|
|
#define PROGRESS_STR "progress"
|
|
#define UPLOADPROGRESS_STR "uploadprogress"
|
|
#define READYSTATE_STR "readystatechange"
|
|
|
|
// CIDs
|
|
|
|
// State
|
|
#define XML_HTTP_REQUEST_UNINITIALIZED (1 << 0) // 0
|
|
#define XML_HTTP_REQUEST_OPENED (1 << 1) // 1 aka LOADING
|
|
#define XML_HTTP_REQUEST_LOADED (1 << 2) // 2
|
|
#define XML_HTTP_REQUEST_INTERACTIVE (1 << 3) // 3
|
|
#define XML_HTTP_REQUEST_COMPLETED (1 << 4) // 4
|
|
#define XML_HTTP_REQUEST_SENT (1 << 5) // Internal, LOADING in IE and external view
|
|
#define XML_HTTP_REQUEST_STOPPED (1 << 6) // Internal, INTERACTIVE in IE and external view
|
|
// The above states are mutually exclusive, change with ChangeState() only.
|
|
// The states below can be combined.
|
|
#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, 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_GOT_FINAL_STOP (1 << 13) // Internal
|
|
#define XML_HTTP_REQUEST_BACKGROUND (1 << 14) // Internal
|
|
// This is set when we've got the headers for a multipart XMLHttpRequest,
|
|
// but haven't yet started to process the first part.
|
|
#define XML_HTTP_REQUEST_MPART_HEADERS (1 << 15) // Internal
|
|
#define XML_HTTP_REQUEST_USE_XSITE_AC (1 << 16) // Internal
|
|
#define XML_HTTP_REQUEST_NEED_AC_PREFLIGHT (1 << 17) // Internal
|
|
#define XML_HTTP_REQUEST_AC_WITH_CREDENTIALS (1 << 18) // Internal
|
|
|
|
#define XML_HTTP_REQUEST_LOADSTATES \
|
|
(XML_HTTP_REQUEST_UNINITIALIZED | \
|
|
XML_HTTP_REQUEST_OPENED | \
|
|
XML_HTTP_REQUEST_LOADED | \
|
|
XML_HTTP_REQUEST_INTERACTIVE | \
|
|
XML_HTTP_REQUEST_COMPLETED | \
|
|
XML_HTTP_REQUEST_SENT | \
|
|
XML_HTTP_REQUEST_STOPPED)
|
|
|
|
#define ACCESS_CONTROL_CACHE_SIZE 100
|
|
|
|
#define NS_BADCERTHANDLER_CONTRACTID \
|
|
"@mozilla.org/content/xmlhttprequest-bad-cert-handler;1"
|
|
|
|
#define NS_PROGRESS_EVENT_INTERVAL 50
|
|
|
|
class nsResumeTimeoutsEvent : public nsRunnable
|
|
{
|
|
public:
|
|
nsResumeTimeoutsEvent(nsPIDOMWindow* aWindow) : mWindow(aWindow) {}
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
mWindow->ResumeTimeouts(PR_FALSE);
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
nsCOMPtr<nsPIDOMWindow> mWindow;
|
|
};
|
|
|
|
|
|
// This helper function adds the given load flags to the request's existing
|
|
// load flags.
|
|
static void AddLoadFlags(nsIRequest *request, nsLoadFlags newFlags)
|
|
{
|
|
nsLoadFlags flags;
|
|
request->GetLoadFlags(&flags);
|
|
flags |= newFlags;
|
|
request->SetLoadFlags(flags);
|
|
}
|
|
|
|
static nsresult IsCapabilityEnabled(const char *capability, PRBool *enabled)
|
|
{
|
|
nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager();
|
|
if (!secMan)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
return secMan->IsCapabilityEnabled(capability, enabled);
|
|
}
|
|
|
|
// Helper proxy class to be used when expecting an
|
|
// multipart/x-mixed-replace stream of XML documents.
|
|
|
|
class nsMultipartProxyListener : public nsIStreamListener
|
|
{
|
|
public:
|
|
nsMultipartProxyListener(nsIStreamListener *dest);
|
|
virtual ~nsMultipartProxyListener();
|
|
|
|
/* additional members */
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSISTREAMLISTENER
|
|
NS_DECL_NSIREQUESTOBSERVER
|
|
|
|
private:
|
|
nsCOMPtr<nsIStreamListener> mDestListener;
|
|
};
|
|
|
|
|
|
nsMultipartProxyListener::nsMultipartProxyListener(nsIStreamListener *dest)
|
|
: mDestListener(dest)
|
|
{
|
|
}
|
|
|
|
nsMultipartProxyListener::~nsMultipartProxyListener()
|
|
{
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS2(nsMultipartProxyListener, nsIStreamListener,
|
|
nsIRequestObserver)
|
|
|
|
/** nsIRequestObserver methods **/
|
|
|
|
NS_IMETHODIMP
|
|
nsMultipartProxyListener::OnStartRequest(nsIRequest *aRequest,
|
|
nsISupports *ctxt)
|
|
{
|
|
nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
|
|
NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
|
|
|
|
nsCAutoString contentType;
|
|
nsresult rv = channel->GetContentType(contentType);
|
|
|
|
if (!contentType.EqualsLiteral("multipart/x-mixed-replace")) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
// If multipart/x-mixed-replace content, we'll insert a MIME
|
|
// decoder in the pipeline to handle the content and pass it along
|
|
// to our original listener.
|
|
|
|
nsCOMPtr<nsIXMLHttpRequest> xhr = do_QueryInterface(mDestListener);
|
|
|
|
nsCOMPtr<nsIStreamConverterService> convServ =
|
|
do_GetService("@mozilla.org/streamConverters;1", &rv);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
nsCOMPtr<nsIStreamListener> toListener(mDestListener);
|
|
nsCOMPtr<nsIStreamListener> fromListener;
|
|
|
|
rv = convServ->AsyncConvertData("multipart/x-mixed-replace",
|
|
"*/*",
|
|
toListener,
|
|
nsnull,
|
|
getter_AddRefs(fromListener));
|
|
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && fromListener, NS_ERROR_UNEXPECTED);
|
|
|
|
mDestListener = fromListener;
|
|
}
|
|
|
|
if (xhr) {
|
|
static_cast<nsXMLHttpRequest*>(xhr.get())->mState |=
|
|
XML_HTTP_REQUEST_MPART_HEADERS;
|
|
}
|
|
|
|
return mDestListener->OnStartRequest(aRequest, ctxt);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsMultipartProxyListener::OnStopRequest(nsIRequest *aRequest,
|
|
nsISupports *ctxt,
|
|
nsresult status)
|
|
{
|
|
return mDestListener->OnStopRequest(aRequest, ctxt, status);
|
|
}
|
|
|
|
/** nsIStreamListener methods **/
|
|
|
|
NS_IMETHODIMP
|
|
nsMultipartProxyListener::OnDataAvailable(nsIRequest *aRequest,
|
|
nsISupports *ctxt,
|
|
nsIInputStream *inStr,
|
|
PRUint32 sourceOffset,
|
|
PRUint32 count)
|
|
{
|
|
return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset,
|
|
count);
|
|
}
|
|
|
|
// Class used as streamlistener and notification callback when
|
|
// doing the initial GET request for an access-control check
|
|
class nsACProxyListener : public nsIStreamListener,
|
|
public nsIInterfaceRequestor,
|
|
public nsIChannelEventSink
|
|
{
|
|
public:
|
|
nsACProxyListener(nsIChannel* aOuterChannel,
|
|
nsIStreamListener* aOuterListener,
|
|
nsISupports* aOuterContext,
|
|
nsIPrincipal* aReferrerPrincipal,
|
|
const nsACString& aRequestMethod,
|
|
PRBool aWithCredentials)
|
|
: mOuterChannel(aOuterChannel), mOuterListener(aOuterListener),
|
|
mOuterContext(aOuterContext), mReferrerPrincipal(aReferrerPrincipal),
|
|
mRequestMethod(aRequestMethod), mWithCredentials(aWithCredentials)
|
|
{ }
|
|
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSISTREAMLISTENER
|
|
NS_DECL_NSIREQUESTOBSERVER
|
|
NS_DECL_NSIINTERFACEREQUESTOR
|
|
NS_DECL_NSICHANNELEVENTSINK
|
|
|
|
private:
|
|
void AddResultToCache(nsIRequest* aRequest);
|
|
|
|
nsCOMPtr<nsIChannel> mOuterChannel;
|
|
nsCOMPtr<nsIStreamListener> mOuterListener;
|
|
nsCOMPtr<nsISupports> mOuterContext;
|
|
nsCOMPtr<nsIPrincipal> mReferrerPrincipal;
|
|
nsCString mRequestMethod;
|
|
PRBool mWithCredentials;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS4(nsACProxyListener, nsIStreamListener, nsIRequestObserver,
|
|
nsIInterfaceRequestor, nsIChannelEventSink)
|
|
|
|
void
|
|
nsACProxyListener::AddResultToCache(nsIRequest *aRequest)
|
|
{
|
|
nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
|
|
NS_ASSERTION(http, "Request was not http");
|
|
|
|
// The "Access-Control-Max-Age" header should return an age in seconds.
|
|
nsCAutoString headerVal;
|
|
http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Max-Age"),
|
|
headerVal);
|
|
if (headerVal.IsEmpty()) {
|
|
return;
|
|
}
|
|
|
|
// Sanitize the string. We only allow 'delta-seconds' as specified by
|
|
// http://dev.w3.org/2006/waf/access-control (digits 0-9 with no leading or
|
|
// trailing non-whitespace characters).
|
|
PRUint32 age = 0;
|
|
nsCSubstring::const_char_iterator iter, end;
|
|
headerVal.BeginReading(iter);
|
|
headerVal.EndReading(end);
|
|
while (iter != end) {
|
|
if (*iter < '0' || *iter > '9') {
|
|
return;
|
|
}
|
|
age = age * 10 + (*iter - '0');
|
|
// Cap at 24 hours. This also avoids overflow
|
|
age = NS_MIN(age, 86400U);
|
|
++iter;
|
|
}
|
|
|
|
if (!age || !nsXMLHttpRequest::EnsureACCache()) {
|
|
return;
|
|
}
|
|
|
|
|
|
// String seems fine, go ahead and cache.
|
|
// Note that we have already checked that these headers follow the correct
|
|
// syntax.
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
NS_GetFinalChannelURI(http, getter_AddRefs(uri));
|
|
|
|
// PR_Now gives microseconds
|
|
PRTime expirationTime = PR_Now() + (PRUint64)age * PR_USEC_PER_SEC;
|
|
|
|
nsAccessControlLRUCache::CacheEntry* entry =
|
|
nsXMLHttpRequest::sAccessControlCache->
|
|
GetEntry(uri, mReferrerPrincipal, mWithCredentials, PR_TRUE);
|
|
if (!entry) {
|
|
return;
|
|
}
|
|
|
|
// The "Access-Control-Allow-Methods" header contains a comma separated
|
|
// list of method names.
|
|
http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Methods"),
|
|
headerVal);
|
|
|
|
nsCCharSeparatedTokenizer methods(headerVal, ',');
|
|
while(methods.hasMoreTokens()) {
|
|
const nsDependentCSubstring& method = methods.nextToken();
|
|
if (method.IsEmpty()) {
|
|
continue;
|
|
}
|
|
PRUint32 i;
|
|
for (i = 0; i < entry->mMethods.Length(); ++i) {
|
|
if (entry->mMethods[i].token.Equals(method)) {
|
|
entry->mMethods[i].expirationTime = expirationTime;
|
|
break;
|
|
}
|
|
}
|
|
if (i == entry->mMethods.Length()) {
|
|
nsAccessControlLRUCache::TokenTime* newMethod =
|
|
entry->mMethods.AppendElement();
|
|
if (!newMethod) {
|
|
return;
|
|
}
|
|
|
|
newMethod->token = method;
|
|
newMethod->expirationTime = expirationTime;
|
|
}
|
|
}
|
|
|
|
// The "Access-Control-Allow-Headers" header contains a comma separated
|
|
// list of method names.
|
|
http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Headers"),
|
|
headerVal);
|
|
|
|
nsCCharSeparatedTokenizer headers(headerVal, ',');
|
|
while(headers.hasMoreTokens()) {
|
|
const nsDependentCSubstring& header = headers.nextToken();
|
|
if (header.IsEmpty()) {
|
|
continue;
|
|
}
|
|
PRUint32 i;
|
|
for (i = 0; i < entry->mHeaders.Length(); ++i) {
|
|
if (entry->mHeaders[i].token.Equals(header)) {
|
|
entry->mHeaders[i].expirationTime = expirationTime;
|
|
break;
|
|
}
|
|
}
|
|
if (i == entry->mHeaders.Length()) {
|
|
nsAccessControlLRUCache::TokenTime* newHeader =
|
|
entry->mHeaders.AppendElement();
|
|
if (!newHeader) {
|
|
return;
|
|
}
|
|
|
|
newHeader->token = header;
|
|
newHeader->expirationTime = expirationTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsACProxyListener::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
|
|
{
|
|
nsresult status;
|
|
nsresult rv = aRequest->GetStatus(&status);
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = status;
|
|
}
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
// Everything worked, try to cache and then fire off the actual request.
|
|
AddResultToCache(aRequest);
|
|
|
|
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::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
|
|
nsIChannel *aNewChannel,
|
|
PRUint32 aFlags,
|
|
nsIAsyncVerifyRedirectCallback *callback)
|
|
{
|
|
// Only internal redirects allowed for now.
|
|
if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags))
|
|
return NS_ERROR_DOM_BAD_URI;
|
|
|
|
callback->OnRedirectVerifyCallback(NS_OK);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsACProxyListener::GetInterface(const nsIID & aIID, void **aResult)
|
|
{
|
|
return QueryInterface(aIID, aResult);
|
|
}
|
|
|
|
/////////////////////////////////////////////
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(nsXHREventTarget)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsXHREventTarget,
|
|
nsDOMEventTargetWrapperCache)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnLoadListener)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnErrorListener)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnAbortListener)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnLoadStartListener)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnProgressListener)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsXHREventTarget,
|
|
nsDOMEventTargetWrapperCache)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnLoadListener)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnErrorListener)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnAbortListener)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnLoadStartListener)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnProgressListener)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsXHREventTarget)
|
|
NS_INTERFACE_MAP_ENTRY(nsIXMLHttpRequestEventTarget)
|
|
NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetWrapperCache)
|
|
|
|
NS_IMPL_ADDREF_INHERITED(nsXHREventTarget, nsDOMEventTargetWrapperCache)
|
|
NS_IMPL_RELEASE_INHERITED(nsXHREventTarget, nsDOMEventTargetWrapperCache)
|
|
|
|
NS_IMETHODIMP
|
|
nsXHREventTarget::GetOnload(nsIDOMEventListener** aOnLoad)
|
|
{
|
|
return GetInnerEventListener(mOnLoadListener, aOnLoad);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsXHREventTarget::SetOnload(nsIDOMEventListener* aOnLoad)
|
|
{
|
|
return RemoveAddEventListener(NS_LITERAL_STRING(LOAD_STR),
|
|
mOnLoadListener, aOnLoad);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsXHREventTarget::GetOnerror(nsIDOMEventListener** aOnerror)
|
|
{
|
|
return GetInnerEventListener(mOnErrorListener, aOnerror);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsXHREventTarget::SetOnerror(nsIDOMEventListener* aOnerror)
|
|
{
|
|
return RemoveAddEventListener(NS_LITERAL_STRING(ERROR_STR),
|
|
mOnErrorListener, aOnerror);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsXHREventTarget::GetOnabort(nsIDOMEventListener** aOnabort)
|
|
{
|
|
return GetInnerEventListener(mOnAbortListener, aOnabort);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsXHREventTarget::SetOnabort(nsIDOMEventListener* aOnabort)
|
|
{
|
|
return RemoveAddEventListener(NS_LITERAL_STRING(ABORT_STR),
|
|
mOnAbortListener, aOnabort);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsXHREventTarget::GetOnloadstart(nsIDOMEventListener** aOnloadstart)
|
|
{
|
|
return GetInnerEventListener(mOnLoadStartListener, aOnloadstart);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsXHREventTarget::SetOnloadstart(nsIDOMEventListener* aOnloadstart)
|
|
{
|
|
return RemoveAddEventListener(NS_LITERAL_STRING(LOADSTART_STR),
|
|
mOnLoadStartListener, aOnloadstart);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsXHREventTarget::GetOnprogress(nsIDOMEventListener** aOnprogress)
|
|
{
|
|
return GetInnerEventListener(mOnProgressListener, aOnprogress);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsXHREventTarget::SetOnprogress(nsIDOMEventListener* aOnprogress)
|
|
{
|
|
return RemoveAddEventListener(NS_LITERAL_STRING(PROGRESS_STR),
|
|
mOnProgressListener, aOnprogress);
|
|
}
|
|
|
|
/////////////////////////////////////////////
|
|
|
|
nsXMLHttpRequestUpload::~nsXMLHttpRequestUpload()
|
|
{
|
|
if (mListenerManager) {
|
|
mListenerManager->Disconnect();
|
|
}
|
|
}
|
|
|
|
DOMCI_DATA(XMLHttpRequestUpload, nsXMLHttpRequestUpload)
|
|
|
|
NS_INTERFACE_MAP_BEGIN(nsXMLHttpRequestUpload)
|
|
NS_INTERFACE_MAP_ENTRY(nsIXMLHttpRequestUpload)
|
|
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XMLHttpRequestUpload)
|
|
NS_INTERFACE_MAP_END_INHERITING(nsXHREventTarget)
|
|
|
|
NS_IMPL_ADDREF_INHERITED(nsXMLHttpRequestUpload, nsXHREventTarget)
|
|
NS_IMPL_RELEASE_INHERITED(nsXMLHttpRequestUpload, nsXHREventTarget)
|
|
|
|
void
|
|
nsAccessControlLRUCache::CacheEntry::PurgeExpired(PRTime now)
|
|
{
|
|
PRUint32 i;
|
|
for (i = 0; i < mMethods.Length(); ++i) {
|
|
if (now >= mMethods[i].expirationTime) {
|
|
mMethods.RemoveElementAt(i--);
|
|
}
|
|
}
|
|
for (i = 0; i < mHeaders.Length(); ++i) {
|
|
if (now >= mHeaders[i].expirationTime) {
|
|
mHeaders.RemoveElementAt(i--);
|
|
}
|
|
}
|
|
}
|
|
|
|
PRBool
|
|
nsAccessControlLRUCache::CacheEntry::CheckRequest(const nsCString& aMethod,
|
|
const nsTArray<nsCString>& aHeaders)
|
|
{
|
|
PurgeExpired(PR_Now());
|
|
|
|
if (!aMethod.EqualsLiteral("GET") && !aMethod.EqualsLiteral("POST")) {
|
|
PRUint32 i;
|
|
for (i = 0; i < mMethods.Length(); ++i) {
|
|
if (aMethod.Equals(mMethods[i].token))
|
|
break;
|
|
}
|
|
if (i == mMethods.Length()) {
|
|
return PR_FALSE;
|
|
}
|
|
}
|
|
|
|
for (PRUint32 i = 0; i < aHeaders.Length(); ++i) {
|
|
PRUint32 j;
|
|
for (j = 0; j < mHeaders.Length(); ++j) {
|
|
if (aHeaders[i].Equals(mHeaders[j].token,
|
|
nsCaseInsensitiveCStringComparator())) {
|
|
break;
|
|
}
|
|
}
|
|
if (j == mHeaders.Length()) {
|
|
return PR_FALSE;
|
|
}
|
|
}
|
|
|
|
return PR_TRUE;
|
|
}
|
|
|
|
nsAccessControlLRUCache::CacheEntry*
|
|
nsAccessControlLRUCache::GetEntry(nsIURI* aURI,
|
|
nsIPrincipal* aPrincipal,
|
|
PRBool aWithCredentials,
|
|
PRBool aCreate)
|
|
{
|
|
nsCString key;
|
|
if (!GetCacheKey(aURI, aPrincipal, aWithCredentials, key)) {
|
|
NS_WARNING("Invalid cache key!");
|
|
return nsnull;
|
|
}
|
|
|
|
CacheEntry* entry;
|
|
|
|
if (mTable.Get(key, &entry)) {
|
|
// Entry already existed so just return it. Also update the LRU list.
|
|
|
|
// Move to the head of the list.
|
|
PR_REMOVE_LINK(entry);
|
|
PR_INSERT_LINK(entry, &mList);
|
|
|
|
return entry;
|
|
}
|
|
|
|
if (!aCreate) {
|
|
return nsnull;
|
|
}
|
|
|
|
// This is a new entry, allocate and insert into the table now so that any
|
|
// failures don't cause items to be removed from a full cache.
|
|
entry = new CacheEntry(key);
|
|
if (!entry) {
|
|
NS_WARNING("Failed to allocate new cache entry!");
|
|
return nsnull;
|
|
}
|
|
|
|
if (!mTable.Put(key, entry)) {
|
|
// Failed, clean up the new entry.
|
|
delete entry;
|
|
|
|
NS_WARNING("Failed to add entry to the access control cache!");
|
|
return nsnull;
|
|
}
|
|
|
|
PR_INSERT_LINK(entry, &mList);
|
|
|
|
NS_ASSERTION(mTable.Count() <= ACCESS_CONTROL_CACHE_SIZE + 1,
|
|
"Something is borked, too many entries in the cache!");
|
|
|
|
// Now enforce the max count.
|
|
if (mTable.Count() > ACCESS_CONTROL_CACHE_SIZE) {
|
|
// Try to kick out all the expired entries.
|
|
PRTime now = PR_Now();
|
|
mTable.Enumerate(RemoveExpiredEntries, &now);
|
|
|
|
// If that didn't remove anything then kick out the least recently used
|
|
// entry.
|
|
if (mTable.Count() > ACCESS_CONTROL_CACHE_SIZE) {
|
|
CacheEntry* lruEntry = static_cast<CacheEntry*>(PR_LIST_TAIL(&mList));
|
|
PR_REMOVE_LINK(lruEntry);
|
|
|
|
// This will delete 'lruEntry'.
|
|
mTable.Remove(lruEntry->mKey);
|
|
|
|
NS_ASSERTION(mTable.Count() == ACCESS_CONTROL_CACHE_SIZE,
|
|
"Somehow tried to remove an entry that was never added!");
|
|
}
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
|
|
void
|
|
nsAccessControlLRUCache::RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal)
|
|
{
|
|
CacheEntry* entry;
|
|
nsCString key;
|
|
if (GetCacheKey(aURI, aPrincipal, PR_TRUE, key) &&
|
|
mTable.Get(key, &entry)) {
|
|
PR_REMOVE_LINK(entry);
|
|
mTable.Remove(key);
|
|
}
|
|
|
|
if (GetCacheKey(aURI, aPrincipal, PR_FALSE, key) &&
|
|
mTable.Get(key, &entry)) {
|
|
PR_REMOVE_LINK(entry);
|
|
mTable.Remove(key);
|
|
}
|
|
}
|
|
|
|
void
|
|
nsAccessControlLRUCache::Clear()
|
|
{
|
|
PR_INIT_CLIST(&mList);
|
|
mTable.Clear();
|
|
}
|
|
|
|
/* static */ PLDHashOperator
|
|
nsAccessControlLRUCache::RemoveExpiredEntries(const nsACString& aKey,
|
|
nsAutoPtr<CacheEntry>& aValue,
|
|
void* aUserData)
|
|
{
|
|
PRTime* now = static_cast<PRTime*>(aUserData);
|
|
|
|
aValue->PurgeExpired(*now);
|
|
|
|
if (aValue->mHeaders.IsEmpty() &&
|
|
aValue->mHeaders.IsEmpty()) {
|
|
// Expired, remove from the list as well as the hash table.
|
|
PR_REMOVE_LINK(aValue);
|
|
return PL_DHASH_REMOVE;
|
|
}
|
|
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
/* static */ PRBool
|
|
nsAccessControlLRUCache::GetCacheKey(nsIURI* aURI,
|
|
nsIPrincipal* aPrincipal,
|
|
PRBool aWithCredentials,
|
|
nsACString& _retval)
|
|
{
|
|
NS_ASSERTION(aURI, "Null uri!");
|
|
NS_ASSERTION(aPrincipal, "Null principal!");
|
|
|
|
NS_NAMED_LITERAL_CSTRING(space, " ");
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
|
|
NS_ENSURE_SUCCESS(rv, PR_FALSE);
|
|
|
|
nsCAutoString scheme, host, port;
|
|
if (uri) {
|
|
uri->GetScheme(scheme);
|
|
uri->GetHost(host);
|
|
port.AppendInt(NS_GetRealPort(uri));
|
|
}
|
|
|
|
nsCAutoString cred;
|
|
if (aWithCredentials) {
|
|
_retval.AssignLiteral("cred");
|
|
}
|
|
else {
|
|
_retval.AssignLiteral("nocred");
|
|
}
|
|
|
|
nsCAutoString spec;
|
|
rv = aURI->GetSpec(spec);
|
|
NS_ENSURE_SUCCESS(rv, PR_FALSE);
|
|
|
|
_retval.Assign(cred + space + scheme + space + host + space + port + space +
|
|
spec);
|
|
|
|
return PR_TRUE;
|
|
}
|
|
|
|
/////////////////////////////////////////////
|
|
//
|
|
//
|
|
/////////////////////////////////////////////
|
|
|
|
// Will be initialized in nsXMLHttpRequest::EnsureACCache.
|
|
nsAccessControlLRUCache* nsXMLHttpRequest::sAccessControlCache = nsnull;
|
|
|
|
nsXMLHttpRequest::nsXMLHttpRequest()
|
|
: mRequestObserver(nsnull), mState(XML_HTTP_REQUEST_UNINITIALIZED),
|
|
mUploadTransferred(0), mUploadTotal(0), mUploadComplete(PR_TRUE),
|
|
mUploadProgress(0), mUploadProgressMax(0),
|
|
mErrorLoad(PR_FALSE), mTimerIsActive(PR_FALSE),
|
|
mProgressEventWasDelayed(PR_FALSE),
|
|
mLoadLengthComputable(PR_FALSE), mLoadTotal(0),
|
|
mFirstStartRequestSeen(PR_FALSE)
|
|
{
|
|
mResponseBodyUnicode.SetIsVoid(PR_TRUE);
|
|
nsLayoutStatics::AddRef();
|
|
}
|
|
|
|
nsXMLHttpRequest::~nsXMLHttpRequest()
|
|
{
|
|
if (mListenerManager) {
|
|
mListenerManager->Disconnect();
|
|
}
|
|
|
|
if (mState & (XML_HTTP_REQUEST_STOPPED |
|
|
XML_HTTP_REQUEST_SENT |
|
|
XML_HTTP_REQUEST_INTERACTIVE)) {
|
|
Abort();
|
|
}
|
|
|
|
NS_ABORT_IF_FALSE(!(mState & XML_HTTP_REQUEST_SYNCLOOPING), "we rather crash than hang");
|
|
mState &= ~XML_HTTP_REQUEST_SYNCLOOPING;
|
|
|
|
nsLayoutStatics::Release();
|
|
}
|
|
|
|
/**
|
|
* This Init method is called from the factory constructor.
|
|
*/
|
|
nsresult
|
|
nsXMLHttpRequest::Init()
|
|
{
|
|
// Set the original mScriptContext and mPrincipal, if available.
|
|
// Get JSContext from stack.
|
|
nsCOMPtr<nsIJSContextStack> stack =
|
|
do_GetService("@mozilla.org/js/xpc/ContextStack;1");
|
|
|
|
if (!stack) {
|
|
return NS_OK;
|
|
}
|
|
|
|
JSContext *cx;
|
|
|
|
if (NS_FAILED(stack->Peek(&cx)) || !cx) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager();
|
|
nsCOMPtr<nsIPrincipal> subjectPrincipal;
|
|
if (secMan) {
|
|
secMan->GetSubjectPrincipal(getter_AddRefs(subjectPrincipal));
|
|
}
|
|
NS_ENSURE_STATE(subjectPrincipal);
|
|
mPrincipal = subjectPrincipal;
|
|
|
|
nsIScriptContext* context = GetScriptContextFromJSContext(cx);
|
|
if (context) {
|
|
mScriptContext = context;
|
|
nsCOMPtr<nsPIDOMWindow> window =
|
|
do_QueryInterface(context->GetGlobalObject());
|
|
if (window) {
|
|
mOwner = window->GetCurrentInnerWindow();
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
/**
|
|
* This Init method should only be called by C++ consumers.
|
|
*/
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::Init(nsIPrincipal* aPrincipal,
|
|
nsIScriptContext* aScriptContext,
|
|
nsPIDOMWindow* aOwnerWindow,
|
|
nsIURI* aBaseURI)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aPrincipal);
|
|
|
|
// This object may have already been initialized in the other Init call above
|
|
// if JS was on the stack. Clear the old values for mScriptContext and mOwner
|
|
// if new ones are not supplied here.
|
|
|
|
mPrincipal = aPrincipal;
|
|
mScriptContext = aScriptContext;
|
|
if (aOwnerWindow) {
|
|
mOwner = aOwnerWindow->GetCurrentInnerWindow();
|
|
}
|
|
else {
|
|
mOwner = nsnull;
|
|
}
|
|
mBaseURI = aBaseURI;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* This Initialize method is called from XPConnect via nsIJSNativeInitializer.
|
|
*/
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::Initialize(nsISupports* aOwner, JSContext* cx, JSObject* obj,
|
|
PRUint32 argc, jsval *argv)
|
|
{
|
|
mOwner = do_QueryInterface(aOwner);
|
|
if (!mOwner) {
|
|
NS_WARNING("Unexpected nsIJSNativeInitializer owner");
|
|
return NS_OK;
|
|
}
|
|
|
|
// This XHR object is bound to a |window|,
|
|
// so re-set principal and script context.
|
|
nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal = do_QueryInterface(aOwner);
|
|
NS_ENSURE_STATE(scriptPrincipal);
|
|
mPrincipal = scriptPrincipal->GetPrincipal();
|
|
nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aOwner);
|
|
NS_ENSURE_STATE(sgo);
|
|
mScriptContext = sgo->GetContext();
|
|
NS_ENSURE_STATE(mScriptContext);
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsXMLHttpRequest::SetRequestObserver(nsIRequestObserver* aObserver)
|
|
{
|
|
mRequestObserver = aObserver;
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(nsXMLHttpRequest)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsXMLHttpRequest,
|
|
nsXHREventTarget)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mContext)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mChannel)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mReadRequest)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mResponseXML)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mACGetChannel)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnUploadProgressListener)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnReadystatechangeListener)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mXMLParserStreamListener)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mChannelEventSink)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mProgressEventSink)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mUpload,
|
|
nsIXMLHttpRequestUpload)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsXMLHttpRequest,
|
|
nsXHREventTarget)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mContext)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mChannel)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mReadRequest)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mResponseXML)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mACGetChannel)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnUploadProgressListener)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnReadystatechangeListener)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mXMLParserStreamListener)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mChannelEventSink)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mProgressEventSink)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mUpload)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
DOMCI_DATA(XMLHttpRequest, nsXMLHttpRequest)
|
|
|
|
// QueryInterface implementation for nsXMLHttpRequest
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsXMLHttpRequest)
|
|
NS_INTERFACE_MAP_ENTRY(nsIXMLHttpRequest)
|
|
NS_INTERFACE_MAP_ENTRY(nsIJSXMLHttpRequest)
|
|
NS_INTERFACE_MAP_ENTRY(nsIDOMLoadListener)
|
|
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
|
|
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
|
|
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
|
|
NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
|
|
NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
|
|
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
|
|
NS_INTERFACE_MAP_ENTRY(nsIJSNativeInitializer)
|
|
NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
|
|
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XMLHttpRequest)
|
|
NS_INTERFACE_MAP_END_INHERITING(nsXHREventTarget)
|
|
|
|
NS_IMPL_ADDREF_INHERITED(nsXMLHttpRequest, nsXHREventTarget)
|
|
NS_IMPL_RELEASE_INHERITED(nsXMLHttpRequest, nsXHREventTarget)
|
|
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::GetOnreadystatechange(nsIDOMEventListener * *aOnreadystatechange)
|
|
{
|
|
return
|
|
nsXHREventTarget::GetInnerEventListener(mOnReadystatechangeListener,
|
|
aOnreadystatechange);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::SetOnreadystatechange(nsIDOMEventListener * aOnreadystatechange)
|
|
{
|
|
return
|
|
nsXHREventTarget::RemoveAddEventListener(NS_LITERAL_STRING(READYSTATE_STR),
|
|
mOnReadystatechangeListener,
|
|
aOnreadystatechange);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::GetOnuploadprogress(nsIDOMEventListener * *aOnuploadprogress)
|
|
{
|
|
return
|
|
nsXHREventTarget::GetInnerEventListener(mOnUploadProgressListener,
|
|
aOnuploadprogress);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::SetOnuploadprogress(nsIDOMEventListener * aOnuploadprogress)
|
|
{
|
|
return
|
|
nsXHREventTarget::RemoveAddEventListener(NS_LITERAL_STRING(UPLOADPROGRESS_STR),
|
|
mOnUploadProgressListener,
|
|
aOnuploadprogress);
|
|
}
|
|
|
|
/* readonly attribute nsIChannel channel; */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::GetChannel(nsIChannel **aChannel)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aChannel);
|
|
NS_IF_ADDREF(*aChannel = mChannel);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* readonly attribute nsIDOMDocument responseXML; */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::GetResponseXML(nsIDOMDocument **aResponseXML)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aResponseXML);
|
|
*aResponseXML = nsnull;
|
|
if ((XML_HTTP_REQUEST_COMPLETED & mState) && mResponseXML) {
|
|
*aResponseXML = mResponseXML;
|
|
NS_ADDREF(*aResponseXML);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/*
|
|
* This piece copied from nsXMLDocument, we try to get the charset
|
|
* from HTTP headers.
|
|
*/
|
|
nsresult
|
|
nsXMLHttpRequest::DetectCharset(nsACString& aCharset)
|
|
{
|
|
aCharset.Truncate();
|
|
nsresult rv;
|
|
nsCAutoString charsetVal;
|
|
nsCOMPtr<nsIChannel> channel(do_QueryInterface(mReadRequest));
|
|
if (!channel) {
|
|
channel = mChannel;
|
|
if (!channel) {
|
|
// There will be no mChannel when we got a necko error in
|
|
// OnStopRequest or if we were never sent.
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
}
|
|
|
|
rv = channel->GetContentCharset(charsetVal);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
nsCOMPtr<nsICharsetAlias> calias(do_GetService(NS_CHARSETALIAS_CONTRACTID,&rv));
|
|
if(NS_SUCCEEDED(rv) && calias) {
|
|
rv = calias->GetPreferred(charsetVal, aCharset);
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsXMLHttpRequest::ConvertBodyToText(nsAString& aOutBuffer)
|
|
{
|
|
// This code here is basically a copy of a similar thing in
|
|
// nsScanner::Append(const char* aBuffer, PRUint32 aLen).
|
|
// If we get illegal characters in the input we replace
|
|
// them and don't just fail.
|
|
if (!mResponseBodyUnicode.IsVoid()) {
|
|
aOutBuffer = mResponseBodyUnicode;
|
|
return NS_OK;
|
|
}
|
|
|
|
PRInt32 dataLen = mResponseBody.Length();
|
|
if (!dataLen) {
|
|
mResponseBodyUnicode.SetIsVoid(PR_FALSE);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult rv = NS_OK;
|
|
|
|
nsCAutoString dataCharset;
|
|
nsCOMPtr<nsIDocument> document(do_QueryInterface(mResponseXML));
|
|
if (document) {
|
|
dataCharset = document->GetDocumentCharacterSet();
|
|
} else {
|
|
if (NS_FAILED(DetectCharset(dataCharset)) || dataCharset.IsEmpty()) {
|
|
// MS documentation states UTF-8 is default for responseText
|
|
dataCharset.AssignLiteral("UTF-8");
|
|
}
|
|
}
|
|
|
|
// XXXbz is the charset ever "ASCII" as opposed to "us-ascii"?
|
|
if (dataCharset.EqualsLiteral("ASCII")) {
|
|
CopyASCIItoUTF16(mResponseBody, mResponseBodyUnicode);
|
|
aOutBuffer = mResponseBodyUnicode;
|
|
return NS_OK;
|
|
}
|
|
|
|
// can't fast-path UTF-8 using CopyUTF8toUTF16, since above we assumed UTF-8
|
|
// by default and CopyUTF8toUTF16 will stop if it encounters bytes that aren't
|
|
// valid UTF-8. So we have to do the whole unicode decoder thing.
|
|
|
|
nsCOMPtr<nsICharsetConverterManager> ccm =
|
|
do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
nsCOMPtr<nsIUnicodeDecoder> decoder;
|
|
rv = ccm->GetUnicodeDecoderRaw(dataCharset.get(),
|
|
getter_AddRefs(decoder));
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
const char * inBuffer = mResponseBody.get();
|
|
PRInt32 outBufferLength;
|
|
rv = decoder->GetMaxLength(inBuffer, dataLen, &outBufferLength);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
PRUnichar * outBuffer =
|
|
static_cast<PRUnichar*>(nsMemory::Alloc((outBufferLength + 1) *
|
|
sizeof(PRUnichar)));
|
|
if (!outBuffer) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
PRInt32 totalChars = 0,
|
|
outBufferIndex = 0,
|
|
outLen = outBufferLength;
|
|
|
|
do {
|
|
PRInt32 inBufferLength = dataLen;
|
|
rv = decoder->Convert(inBuffer,
|
|
&inBufferLength,
|
|
&outBuffer[outBufferIndex],
|
|
&outLen);
|
|
totalChars += outLen;
|
|
if (NS_FAILED(rv)) {
|
|
// We consume one byte, replace it with U+FFFD
|
|
// and try the conversion again.
|
|
outBuffer[outBufferIndex + outLen++] = (PRUnichar)0xFFFD;
|
|
outBufferIndex += outLen;
|
|
outLen = outBufferLength - (++totalChars);
|
|
|
|
decoder->Reset();
|
|
|
|
if((inBufferLength + 1) > dataLen) {
|
|
inBufferLength = dataLen;
|
|
} else {
|
|
inBufferLength++;
|
|
}
|
|
|
|
inBuffer = &inBuffer[inBufferLength];
|
|
dataLen -= inBufferLength;
|
|
}
|
|
} while ( NS_FAILED(rv) && (dataLen > 0) );
|
|
|
|
mResponseBodyUnicode.Assign(outBuffer, totalChars);
|
|
aOutBuffer = mResponseBodyUnicode;
|
|
nsMemory::Free(outBuffer);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* readonly attribute AString responseText; */
|
|
NS_IMETHODIMP nsXMLHttpRequest::GetResponseText(nsAString& aResponseText)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
|
|
aResponseText.Truncate();
|
|
|
|
if (mState & (XML_HTTP_REQUEST_COMPLETED |
|
|
XML_HTTP_REQUEST_INTERACTIVE)) {
|
|
rv = ConvertBodyToText(aResponseText);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* readonly attribute jsval (ArrayBuffer) mozResponseArrayBuffer; */
|
|
NS_IMETHODIMP nsXMLHttpRequest::GetMozResponseArrayBuffer(jsval *aResult)
|
|
{
|
|
JSContext *cx = nsContentUtils::GetCurrentJSContext();
|
|
if (!cx)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
if (!(mState & (XML_HTTP_REQUEST_COMPLETED |
|
|
XML_HTTP_REQUEST_INTERACTIVE))) {
|
|
*aResult = JSVAL_NULL;
|
|
return NS_OK;
|
|
}
|
|
|
|
PRInt32 dataLen = mResponseBody.Length();
|
|
JSObject *obj = js_CreateArrayBuffer(cx, dataLen);
|
|
if (!obj)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
*aResult = OBJECT_TO_JSVAL(obj);
|
|
|
|
if (dataLen > 0) {
|
|
js::ArrayBuffer *abuf = js::ArrayBuffer::fromJSObject(obj);
|
|
NS_ASSERTION(abuf, "What happened?");
|
|
memcpy(abuf->data, mResponseBody.BeginReading(), dataLen);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* readonly attribute unsigned long status; */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::GetStatus(PRUint32 *aStatus)
|
|
{
|
|
*aStatus = 0;
|
|
|
|
if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) {
|
|
// Make sure we don't leak status information from denied cross-site
|
|
// requests.
|
|
if (mChannel) {
|
|
nsresult status;
|
|
mChannel->GetStatus(&status);
|
|
if (NS_FAILED(status)) {
|
|
return NS_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
|
|
|
|
if (httpChannel) {
|
|
nsresult rv = httpChannel->GetResponseStatus(aStatus);
|
|
if (rv == NS_ERROR_NOT_AVAILABLE) {
|
|
// Someone's calling this before we got a response... Check our
|
|
// ReadyState. If we're at 3 or 4, then this means the connection
|
|
// errored before we got any data; return 0 in that case.
|
|
PRInt32 readyState;
|
|
GetReadyState(&readyState);
|
|
if (readyState >= 3) {
|
|
*aStatus = 0;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* readonly attribute AUTF8String statusText; */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::GetStatusText(nsACString& aStatusText)
|
|
{
|
|
nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
|
|
|
|
aStatusText.Truncate();
|
|
|
|
nsresult rv = NS_OK;
|
|
|
|
if (httpChannel) {
|
|
if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) {
|
|
// Make sure we don't leak status information from denied cross-site
|
|
// requests.
|
|
if (mChannel) {
|
|
nsresult status;
|
|
mChannel->GetStatus(&status);
|
|
if (NS_FAILED(status)) {
|
|
return NS_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
httpChannel->GetResponseStatusText(aStatusText);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void abort (); */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::Abort()
|
|
{
|
|
if (mReadRequest) {
|
|
mReadRequest->Cancel(NS_BINDING_ABORTED);
|
|
}
|
|
if (mChannel) {
|
|
mChannel->Cancel(NS_BINDING_ABORTED);
|
|
}
|
|
if (mACGetChannel) {
|
|
mACGetChannel->Cancel(NS_BINDING_ABORTED);
|
|
}
|
|
mResponseXML = nsnull;
|
|
PRUint32 responseLength = mResponseBody.Length();
|
|
mResponseBody.Truncate();
|
|
mResponseBodyUnicode.SetIsVoid(PR_TRUE);
|
|
mState |= XML_HTTP_REQUEST_ABORTED;
|
|
|
|
if (!(mState & (XML_HTTP_REQUEST_UNINITIALIZED |
|
|
XML_HTTP_REQUEST_OPENED |
|
|
XML_HTTP_REQUEST_COMPLETED))) {
|
|
ChangeState(XML_HTTP_REQUEST_COMPLETED, PR_TRUE);
|
|
}
|
|
|
|
if (!(mState & XML_HTTP_REQUEST_SYNCLOOPING)) {
|
|
NS_NAMED_LITERAL_STRING(abortStr, ABORT_STR);
|
|
DispatchProgressEvent(this, abortStr, mLoadLengthComputable, responseLength,
|
|
mLoadTotal);
|
|
if (mUpload && !mUploadComplete) {
|
|
mUploadComplete = PR_TRUE;
|
|
DispatchProgressEvent(mUpload, abortStr, PR_TRUE, mUploadTransferred,
|
|
mUploadTotal);
|
|
}
|
|
}
|
|
|
|
// The ChangeState call above calls onreadystatechange handlers which
|
|
// if they load a new url will cause nsXMLHttpRequest::OpenRequest to clear
|
|
// the abort state bit. If this occurs we're not uninitialized (bug 361773).
|
|
if (mState & XML_HTTP_REQUEST_ABORTED) {
|
|
ChangeState(XML_HTTP_REQUEST_UNINITIALIZED, PR_FALSE); // IE seems to do it
|
|
}
|
|
|
|
mState &= ~XML_HTTP_REQUEST_SYNCLOOPING;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* string getAllResponseHeaders (); */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::GetAllResponseHeaders(char **_retval)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
*_retval = nsnull;
|
|
|
|
if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) {
|
|
*_retval = ToNewCString(EmptyString());
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
|
|
|
|
if (httpChannel) {
|
|
nsRefPtr<nsHeaderVisitor> visitor = new nsHeaderVisitor();
|
|
nsresult rv = httpChannel->VisitResponseHeaders(visitor);
|
|
if (NS_SUCCEEDED(rv))
|
|
*_retval = ToNewCString(visitor->Headers());
|
|
}
|
|
|
|
if (!*_retval) {
|
|
*_retval = ToNewCString(EmptyString());
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* ACString getResponseHeader (in AUTF8String header); */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::GetResponseHeader(const nsACString& header,
|
|
nsACString& _retval)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
_retval.SetIsVoid(PR_TRUE);
|
|
|
|
nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
|
|
|
|
if (!httpChannel) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// See bug #380418. Hide "Set-Cookie" headers from non-chrome scripts.
|
|
PRBool chrome = PR_FALSE; // default to false in case IsCapabilityEnabled fails
|
|
IsCapabilityEnabled("UniversalXPConnect", &chrome);
|
|
if (!chrome &&
|
|
(header.LowerCaseEqualsASCII("set-cookie") ||
|
|
header.LowerCaseEqualsASCII("set-cookie2"))) {
|
|
NS_WARNING("blocked access to response header");
|
|
return NS_OK;
|
|
}
|
|
|
|
// Check for dangerous headers
|
|
if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) {
|
|
// Make sure we don't leak header information from denied cross-site
|
|
// requests.
|
|
if (mChannel) {
|
|
nsresult status;
|
|
mChannel->GetStatus(&status);
|
|
if (NS_FAILED(status)) {
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
const char *kCrossOriginSafeHeaders[] = {
|
|
"cache-control", "content-language", "content-type", "expires",
|
|
"last-modified", "pragma"
|
|
};
|
|
PRBool safeHeader = PR_FALSE;
|
|
PRUint32 i;
|
|
for (i = 0; i < NS_ARRAY_LENGTH(kCrossOriginSafeHeaders); ++i) {
|
|
if (header.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
|
|
safeHeader = PR_TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!safeHeader) {
|
|
nsCAutoString headerVal;
|
|
// The "Access-Control-Expose-Headers" header contains a comma separated
|
|
// list of method names.
|
|
httpChannel->
|
|
GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Expose-Headers"),
|
|
headerVal);
|
|
nsCCharSeparatedTokenizer exposeTokens(headerVal, ',');
|
|
while(exposeTokens.hasMoreTokens()) {
|
|
const nsDependentCSubstring& token = exposeTokens.nextToken();
|
|
if (token.IsEmpty()) {
|
|
continue;
|
|
}
|
|
if (!IsValidHTTPToken(token)) {
|
|
return NS_OK;
|
|
}
|
|
if (header.Equals(token, nsCaseInsensitiveCStringComparator())) {
|
|
safeHeader = PR_TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!safeHeader) {
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
rv = httpChannel->GetResponseHeader(header, _retval);
|
|
if (rv == NS_ERROR_NOT_AVAILABLE) {
|
|
// Means no header
|
|
_retval.SetIsVoid(PR_TRUE);
|
|
rv = NS_OK;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsXMLHttpRequest::GetLoadGroup(nsILoadGroup **aLoadGroup)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aLoadGroup);
|
|
*aLoadGroup = nsnull;
|
|
|
|
if (mState & XML_HTTP_REQUEST_BACKGROUND) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocument> doc =
|
|
nsContentUtils::GetDocumentFromScriptContext(mScriptContext);
|
|
if (doc) {
|
|
*aLoadGroup = doc->GetDocumentLoadGroup().get(); // already_AddRefed
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsXMLHttpRequest::CreateReadystatechangeEvent(nsIDOMEvent** aDOMEvent)
|
|
{
|
|
nsresult rv = nsEventDispatcher::CreateEvent(nsnull, nsnull,
|
|
NS_LITERAL_STRING("Events"),
|
|
aDOMEvent);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrivateDOMEvent> privevent(do_QueryInterface(*aDOMEvent));
|
|
if (!privevent) {
|
|
NS_IF_RELEASE(*aDOMEvent);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
(*aDOMEvent)->InitEvent(NS_LITERAL_STRING(READYSTATE_STR),
|
|
PR_FALSE, PR_FALSE);
|
|
|
|
// We assume anyone who managed to call CreateReadystatechangeEvent is trusted
|
|
privevent->SetTrusted(PR_TRUE);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsXMLHttpRequest::DispatchProgressEvent(nsPIDOMEventTarget* aTarget,
|
|
const nsAString& aType,
|
|
PRBool aUseLSEventWrapper,
|
|
PRBool aLengthComputable,
|
|
PRUint64 aLoaded, PRUint64 aTotal,
|
|
PRUint64 aPosition, PRUint64 aTotalSize)
|
|
{
|
|
NS_ASSERTION(aTarget, "null target");
|
|
if (aType.IsEmpty() ||
|
|
(!AllowUploadProgress() &&
|
|
(aTarget == mUpload || aType.EqualsLiteral(UPLOADPROGRESS_STR)))) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMEvent> event;
|
|
nsresult rv = nsEventDispatcher::CreateEvent(nsnull, nsnull,
|
|
NS_LITERAL_STRING("ProgressEvent"),
|
|
getter_AddRefs(event));
|
|
if (NS_FAILED(rv)) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrivateDOMEvent> privevent(do_QueryInterface(event));
|
|
if (!privevent) {
|
|
return;
|
|
}
|
|
privevent->SetTrusted(PR_TRUE);
|
|
|
|
nsCOMPtr<nsIDOMProgressEvent> progress = do_QueryInterface(event);
|
|
if (!progress) {
|
|
return;
|
|
}
|
|
|
|
progress->InitProgressEvent(aType, PR_FALSE, PR_FALSE, aLengthComputable,
|
|
aLoaded, (aTotal == LL_MAXUINT) ? 0 : aTotal);
|
|
|
|
if (aUseLSEventWrapper) {
|
|
nsCOMPtr<nsIDOMProgressEvent> xhrprogressEvent =
|
|
new nsXMLHttpProgressEvent(progress, aPosition, aTotalSize);
|
|
if (!xhrprogressEvent) {
|
|
return;
|
|
}
|
|
event = xhrprogressEvent;
|
|
}
|
|
aTarget->DispatchDOMEvent(nsnull, event, nsnull, nsnull);
|
|
}
|
|
|
|
already_AddRefed<nsIHttpChannel>
|
|
nsXMLHttpRequest::GetCurrentHttpChannel()
|
|
{
|
|
nsIHttpChannel *httpChannel = nsnull;
|
|
|
|
if (mReadRequest) {
|
|
CallQueryInterface(mReadRequest, &httpChannel);
|
|
}
|
|
|
|
if (!httpChannel && mChannel) {
|
|
CallQueryInterface(mChannel, &httpChannel);
|
|
}
|
|
|
|
return httpChannel;
|
|
}
|
|
|
|
nsresult
|
|
nsXMLHttpRequest::CheckChannelForCrossSiteRequest(nsIChannel* aChannel)
|
|
{
|
|
// First check if cross-site requests are enabled
|
|
if ((mState & XML_HTTP_REQUEST_XSITEENABLED)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// or if this is a same-origin request.
|
|
NS_ASSERTION(!nsContentUtils::IsSystemPrincipal(mPrincipal),
|
|
"Shouldn't get here!");
|
|
if (nsContentUtils::CheckMayLoad(mPrincipal, aChannel)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// This is a cross-site request
|
|
mState |= XML_HTTP_REQUEST_USE_XSITE_AC;
|
|
|
|
// Check if we need to do a preflight request.
|
|
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
|
|
NS_ENSURE_TRUE(httpChannel, NS_ERROR_DOM_BAD_URI);
|
|
|
|
nsCAutoString method;
|
|
httpChannel->GetRequestMethod(method);
|
|
if (!mACUnsafeHeaders.IsEmpty() ||
|
|
HasListenersFor(NS_LITERAL_STRING(UPLOADPROGRESS_STR)) ||
|
|
(mUpload && mUpload->HasListeners()) ||
|
|
(!method.LowerCaseEqualsLiteral("get") &&
|
|
!method.LowerCaseEqualsLiteral("post") &&
|
|
!method.LowerCaseEqualsLiteral("head"))) {
|
|
mState |= XML_HTTP_REQUEST_NEED_AC_PREFLIGHT;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* noscript void openRequest (in AUTF8String method, in AUTF8String url, in boolean async, in AString user, in AString password); */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::OpenRequest(const nsACString& method,
|
|
const nsACString& url,
|
|
PRBool async,
|
|
const nsAString& user,
|
|
const nsAString& password)
|
|
{
|
|
NS_ENSURE_ARG(!method.IsEmpty());
|
|
NS_ENSURE_ARG(!url.IsEmpty());
|
|
|
|
NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
// Disallow HTTP/1.1 TRACE method (see bug 302489)
|
|
// and MS IIS equivalent TRACK (see bug 381264)
|
|
if (method.LowerCaseEqualsLiteral("trace") ||
|
|
method.LowerCaseEqualsLiteral("track")) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsIURI> uri;
|
|
PRBool authp = PR_FALSE;
|
|
|
|
if (mState & (XML_HTTP_REQUEST_OPENED |
|
|
XML_HTTP_REQUEST_LOADED |
|
|
XML_HTTP_REQUEST_INTERACTIVE |
|
|
XML_HTTP_REQUEST_SENT |
|
|
XML_HTTP_REQUEST_STOPPED)) {
|
|
// IE aborts as well
|
|
Abort();
|
|
|
|
// XXX We should probably send a warning to the JS console
|
|
// that load was aborted and event listeners were cleared
|
|
// since this looks like a situation that could happen
|
|
// by accident and you could spend a lot of time wondering
|
|
// why things didn't work.
|
|
}
|
|
|
|
if (mState & XML_HTTP_REQUEST_ABORTED) {
|
|
// Something caused this request to abort (e.g the current request
|
|
// was caceled, channels closed etc), most likely the abort()
|
|
// function was called by script. Unset our aborted state, and
|
|
// proceed as normal
|
|
|
|
mState &= ~XML_HTTP_REQUEST_ABORTED;
|
|
}
|
|
|
|
if (async) {
|
|
mState |= XML_HTTP_REQUEST_ASYNC;
|
|
} else {
|
|
mState &= ~XML_HTTP_REQUEST_ASYNC;
|
|
}
|
|
|
|
mState &= ~XML_HTTP_REQUEST_MPART_HEADERS;
|
|
|
|
nsCOMPtr<nsIDocument> doc =
|
|
nsContentUtils::GetDocumentFromScriptContext(mScriptContext);
|
|
|
|
nsCOMPtr<nsIURI> baseURI;
|
|
if (mBaseURI) {
|
|
baseURI = mBaseURI;
|
|
}
|
|
else if (doc) {
|
|
baseURI = doc->GetBaseURI();
|
|
}
|
|
|
|
rv = NS_NewURI(getter_AddRefs(uri), url, nsnull, baseURI);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// mScriptContext should be initialized because of GetBaseURI() above.
|
|
// Still need to consider the case that doc is nsnull however.
|
|
rv = CheckInnerWindowCorrectness();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
|
|
rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_XMLHTTPREQUEST,
|
|
uri,
|
|
mPrincipal,
|
|
doc,
|
|
EmptyCString(), //mime guess
|
|
nsnull, //extra
|
|
&shouldLoad,
|
|
nsContentUtils::GetContentPolicy(),
|
|
nsContentUtils::GetSecurityManager());
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (NS_CP_REJECTED(shouldLoad)) {
|
|
// Disallowed by content policy
|
|
return NS_ERROR_CONTENT_BLOCKED;
|
|
}
|
|
|
|
if (!user.IsEmpty()) {
|
|
nsCAutoString userpass;
|
|
CopyUTF16toUTF8(user, userpass);
|
|
if (!password.IsEmpty()) {
|
|
userpass.Append(':');
|
|
AppendUTF16toUTF8(password, userpass);
|
|
}
|
|
uri->SetUserPass(userpass);
|
|
authp = PR_TRUE;
|
|
}
|
|
|
|
// When we are called from JS we can find the load group for the page,
|
|
// and add ourselves to it. This way any pending requests
|
|
// will be automatically aborted if the user leaves the page.
|
|
nsCOMPtr<nsILoadGroup> loadGroup;
|
|
GetLoadGroup(getter_AddRefs(loadGroup));
|
|
|
|
// get Content Security Policy from principal to pass into channel
|
|
nsCOMPtr<nsIChannelPolicy> channelPolicy;
|
|
nsCOMPtr<nsIContentSecurityPolicy> csp;
|
|
rv = mPrincipal->GetCsp(getter_AddRefs(csp));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (csp) {
|
|
channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1");
|
|
channelPolicy->SetContentSecurityPolicy(csp);
|
|
channelPolicy->SetLoadType(nsIContentPolicy::TYPE_XMLHTTPREQUEST);
|
|
}
|
|
rv = NS_NewChannel(getter_AddRefs(mChannel),
|
|
uri,
|
|
nsnull, // ioService
|
|
loadGroup,
|
|
nsnull, // callbacks
|
|
nsIRequest::LOAD_BACKGROUND,
|
|
channelPolicy);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Check if we're doing a cross-origin request.
|
|
if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
|
|
// Chrome callers are always allowed to read from different origins.
|
|
mState |= XML_HTTP_REQUEST_XSITEENABLED;
|
|
}
|
|
|
|
mState &= ~(XML_HTTP_REQUEST_USE_XSITE_AC |
|
|
XML_HTTP_REQUEST_NEED_AC_PREFLIGHT);
|
|
|
|
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
|
|
if (httpChannel) {
|
|
rv = httpChannel->SetRequestMethod(method);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
ChangeState(XML_HTTP_REQUEST_OPENED);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* void open (in AUTF8String method, in AUTF8String url); */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::Open(const nsACString& method, const nsACString& url,
|
|
PRBool async, const nsAString& user,
|
|
const nsAString& password, PRUint8 optional_argc)
|
|
{
|
|
if (nsContentUtils::GetCurrentJSContext()) {
|
|
// We're (likely) called from JS
|
|
|
|
// Find out if UniversalBrowserRead privileges are enabled
|
|
if (nsContentUtils::IsCallerTrustedForRead()) {
|
|
mState |= XML_HTTP_REQUEST_XSITEENABLED;
|
|
} else {
|
|
mState &= ~XML_HTTP_REQUEST_XSITEENABLED;
|
|
}
|
|
}
|
|
|
|
if (!optional_argc) {
|
|
// No optional arguments were passed in. Default async to true.
|
|
async = PR_TRUE;
|
|
}
|
|
|
|
return OpenRequest(method, url, async, user, password);
|
|
}
|
|
|
|
/*
|
|
* "Copy" from a stream.
|
|
*/
|
|
NS_METHOD
|
|
nsXMLHttpRequest::StreamReaderFunc(nsIInputStream* in,
|
|
void* closure,
|
|
const char* fromRawSegment,
|
|
PRUint32 toOffset,
|
|
PRUint32 count,
|
|
PRUint32 *writeCount)
|
|
{
|
|
nsXMLHttpRequest* xmlHttpRequest = static_cast<nsXMLHttpRequest*>(closure);
|
|
if (!xmlHttpRequest || !writeCount) {
|
|
NS_WARNING("XMLHttpRequest cannot read from stream: no closure or writeCount");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// Copy for our own use
|
|
xmlHttpRequest->mResponseBody.Append(fromRawSegment,count);
|
|
xmlHttpRequest->mResponseBodyUnicode.SetIsVoid(PR_TRUE);
|
|
|
|
nsresult rv = NS_OK;
|
|
|
|
if (xmlHttpRequest->mState & XML_HTTP_REQUEST_PARSEBODY) {
|
|
// Give the same data to the parser.
|
|
|
|
// We need to wrap the data in a new lightweight stream and pass that
|
|
// to the parser, because calling ReadSegments() recursively on the same
|
|
// stream is not supported.
|
|
nsCOMPtr<nsIInputStream> copyStream;
|
|
rv = NS_NewByteInputStream(getter_AddRefs(copyStream), fromRawSegment, count);
|
|
|
|
if (NS_SUCCEEDED(rv) && xmlHttpRequest->mXMLParserStreamListener) {
|
|
NS_ASSERTION(copyStream, "NS_NewByteInputStream lied");
|
|
nsresult parsingResult = xmlHttpRequest->mXMLParserStreamListener
|
|
->OnDataAvailable(xmlHttpRequest->mReadRequest,
|
|
xmlHttpRequest->mContext,
|
|
copyStream, toOffset, count);
|
|
|
|
// No use to continue parsing if we failed here, but we
|
|
// should still finish reading the stream
|
|
if (NS_FAILED(parsingResult)) {
|
|
xmlHttpRequest->mState &= ~XML_HTTP_REQUEST_PARSEBODY;
|
|
}
|
|
}
|
|
}
|
|
|
|
xmlHttpRequest->ChangeState(XML_HTTP_REQUEST_INTERACTIVE);
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
*writeCount = count;
|
|
} else {
|
|
*writeCount = 0;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long sourceOffset, in unsigned long count); */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, nsIInputStream *inStr, PRUint32 sourceOffset, PRUint32 count)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(inStr);
|
|
|
|
NS_ABORT_IF_FALSE(mContext.get() == ctxt,"start context different from OnDataAvailable context");
|
|
|
|
PRUint32 totalRead;
|
|
return inStr->ReadSegments(nsXMLHttpRequest::StreamReaderFunc, (void*)this, count, &totalRead);
|
|
}
|
|
|
|
PRBool
|
|
IsSameOrBaseChannel(nsIRequest* aPossibleBase, nsIChannel* aChannel)
|
|
{
|
|
nsCOMPtr<nsIMultiPartChannel> mpChannel = do_QueryInterface(aPossibleBase);
|
|
if (mpChannel) {
|
|
nsCOMPtr<nsIChannel> baseChannel;
|
|
nsresult rv = mpChannel->GetBaseChannel(getter_AddRefs(baseChannel));
|
|
NS_ENSURE_SUCCESS(rv, PR_FALSE);
|
|
|
|
return baseChannel == aChannel;
|
|
}
|
|
|
|
return aPossibleBase == aChannel;
|
|
}
|
|
|
|
/* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
if (!mFirstStartRequestSeen && mRequestObserver) {
|
|
mFirstStartRequestSeen = PR_TRUE;
|
|
mRequestObserver->OnStartRequest(request, ctxt);
|
|
}
|
|
|
|
if (!IsSameOrBaseChannel(request, mChannel)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Don't do anything if we have been aborted
|
|
if (mState & XML_HTTP_REQUEST_UNINITIALIZED)
|
|
return NS_OK;
|
|
|
|
if (mState & XML_HTTP_REQUEST_ABORTED) {
|
|
NS_ERROR("Ugh, still getting data on an aborted XMLHttpRequest!");
|
|
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
|
|
NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
|
|
|
|
nsCOMPtr<nsIPrincipal> documentPrincipal = mPrincipal;
|
|
if (nsContentUtils::IsSystemPrincipal(documentPrincipal)) {
|
|
// Don't give this document the system principal. We need to keep track of
|
|
// mPrincipal being system because we use it for various security checks
|
|
// that should be passing, but the document data shouldn't get a system
|
|
// principal.
|
|
nsresult rv;
|
|
documentPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
channel->SetOwner(documentPrincipal);
|
|
|
|
mReadRequest = request;
|
|
mContext = ctxt;
|
|
mState |= XML_HTTP_REQUEST_PARSEBODY;
|
|
mState &= ~XML_HTTP_REQUEST_MPART_HEADERS;
|
|
ChangeState(XML_HTTP_REQUEST_LOADED);
|
|
|
|
nsresult status;
|
|
request->GetStatus(&status);
|
|
mErrorLoad = mErrorLoad || NS_FAILED(status);
|
|
|
|
if (mUpload && !mUploadComplete && !mErrorLoad &&
|
|
(mState & XML_HTTP_REQUEST_ASYNC)) {
|
|
mUploadComplete = PR_TRUE;
|
|
DispatchProgressEvent(mUpload, NS_LITERAL_STRING(LOAD_STR),
|
|
PR_TRUE, mUploadTotal, mUploadTotal);
|
|
}
|
|
|
|
// Reset responseBody
|
|
mResponseBody.Truncate();
|
|
mResponseBodyUnicode.SetIsVoid(PR_TRUE);
|
|
|
|
// Set up responseXML
|
|
PRBool parseBody = PR_TRUE;
|
|
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
|
|
if (httpChannel) {
|
|
nsCAutoString method;
|
|
httpChannel->GetRequestMethod(method);
|
|
parseBody = !method.EqualsLiteral("HEAD");
|
|
}
|
|
|
|
if (parseBody && NS_SUCCEEDED(status)) {
|
|
if (!mOverrideMimeType.IsEmpty()) {
|
|
channel->SetContentType(mOverrideMimeType);
|
|
}
|
|
|
|
// We can gain a huge performance win by not even trying to
|
|
// parse non-XML data. This also protects us from the situation
|
|
// where we have an XML document and sink, but HTML (or other)
|
|
// parser, which can produce unreliable results.
|
|
nsCAutoString type;
|
|
channel->GetContentType(type);
|
|
|
|
if (type.Find("xml") == kNotFound) {
|
|
mState &= ~XML_HTTP_REQUEST_PARSEBODY;
|
|
}
|
|
} else {
|
|
// The request failed, so we shouldn't be parsing anyway
|
|
mState &= ~XML_HTTP_REQUEST_PARSEBODY;
|
|
}
|
|
|
|
if (mState & XML_HTTP_REQUEST_PARSEBODY) {
|
|
nsCOMPtr<nsIURI> baseURI, docURI;
|
|
nsCOMPtr<nsIDocument> doc =
|
|
nsContentUtils::GetDocumentFromScriptContext(mScriptContext);
|
|
|
|
if (doc) {
|
|
docURI = doc->GetDocumentURI();
|
|
baseURI = doc->GetBaseURI();
|
|
}
|
|
|
|
// Create an empty document from it. Here we have to cheat a little bit...
|
|
// Setting the base URI to |baseURI| won't work if the document has a null
|
|
// principal, so use mPrincipal when creating the document, then reset the
|
|
// principal.
|
|
const nsAString& emptyStr = EmptyString();
|
|
nsCOMPtr<nsIScriptGlobalObject> global = do_QueryInterface(mOwner);
|
|
rv = nsContentUtils::CreateDocument(emptyStr, emptyStr, nsnull, docURI,
|
|
baseURI, mPrincipal, global,
|
|
getter_AddRefs(mResponseXML));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
nsCOMPtr<nsIDocument> responseDoc = do_QueryInterface(mResponseXML);
|
|
responseDoc->SetPrincipal(documentPrincipal);
|
|
|
|
if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
|
|
responseDoc->ForceEnableXULXBL();
|
|
}
|
|
|
|
if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) {
|
|
nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(mResponseXML);
|
|
if (htmlDoc) {
|
|
htmlDoc->DisableCookieAccess();
|
|
}
|
|
}
|
|
|
|
// Register as a load listener on the document
|
|
nsCOMPtr<nsPIDOMEventTarget> target(do_QueryInterface(mResponseXML));
|
|
if (target) {
|
|
nsWeakPtr requestWeak =
|
|
do_GetWeakReference(static_cast<nsIXMLHttpRequest*>(this));
|
|
nsCOMPtr<nsIDOMEventListener> proxy = new nsLoadListenerProxy(requestWeak);
|
|
if (!proxy) return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
// This will addref the proxy
|
|
rv = target->AddEventListenerByIID(static_cast<nsIDOMEventListener*>
|
|
(proxy),
|
|
NS_GET_IID(nsIDOMLoadListener));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
|
|
|
|
nsCOMPtr<nsIStreamListener> listener;
|
|
nsCOMPtr<nsILoadGroup> loadGroup;
|
|
channel->GetLoadGroup(getter_AddRefs(loadGroup));
|
|
|
|
rv = responseDoc->StartDocumentLoad(kLoadAsData, channel, loadGroup,
|
|
nsnull, getter_AddRefs(listener),
|
|
!(mState & XML_HTTP_REQUEST_USE_XSITE_AC));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mXMLParserStreamListener = listener;
|
|
rv = mXMLParserStreamListener->OnStartRequest(request, ctxt);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
// We won't get any progress events anyway if we didn't have progress
|
|
// events when starting the request - so maybe no need to start timer here.
|
|
if (NS_SUCCEEDED(rv) &&
|
|
(mState & XML_HTTP_REQUEST_ASYNC) &&
|
|
HasListenersFor(NS_LITERAL_STRING(PROGRESS_STR))) {
|
|
StartProgressEventTimer();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status, in wstring statusArg); */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
|
|
{
|
|
if (!IsSameOrBaseChannel(request, mChannel)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult rv = NS_OK;
|
|
|
|
// If we're loading a multipart stream of XML documents, we'll get
|
|
// an OnStopRequest() for the last part in the stream, and then
|
|
// another one for the end of the initiating
|
|
// "multipart/x-mixed-replace" stream too. So we must check that we
|
|
// still have an xml parser stream listener before accessing it
|
|
// here.
|
|
nsCOMPtr<nsIMultiPartChannel> mpChannel = do_QueryInterface(request);
|
|
if (mpChannel) {
|
|
PRBool last;
|
|
rv = mpChannel->GetIsLastPart(&last);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (last) {
|
|
mState |= XML_HTTP_REQUEST_GOT_FINAL_STOP;
|
|
}
|
|
}
|
|
else {
|
|
mState |= XML_HTTP_REQUEST_GOT_FINAL_STOP;
|
|
}
|
|
|
|
if (mRequestObserver && mState & XML_HTTP_REQUEST_GOT_FINAL_STOP) {
|
|
NS_ASSERTION(mFirstStartRequestSeen, "Inconsistent state!");
|
|
mFirstStartRequestSeen = PR_FALSE;
|
|
mRequestObserver->OnStopRequest(request, ctxt, status);
|
|
}
|
|
|
|
// make sure to notify the listener if we were aborted
|
|
// XXX in fact, why don't we do the cleanup below in this case??
|
|
if (mState & XML_HTTP_REQUEST_UNINITIALIZED) {
|
|
if (mXMLParserStreamListener)
|
|
(void) mXMLParserStreamListener->OnStopRequest(request, ctxt, status);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIParser> parser;
|
|
|
|
// Is this good enough here?
|
|
if (mState & XML_HTTP_REQUEST_PARSEBODY && mXMLParserStreamListener) {
|
|
parser = do_QueryInterface(mXMLParserStreamListener);
|
|
NS_ABORT_IF_FALSE(parser, "stream listener was expected to be a parser");
|
|
rv = mXMLParserStreamListener->OnStopRequest(request, ctxt, status);
|
|
}
|
|
|
|
mXMLParserStreamListener = nsnull;
|
|
mReadRequest = nsnull;
|
|
mContext = nsnull;
|
|
|
|
nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
|
|
NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
|
|
|
|
channel->SetNotificationCallbacks(nsnull);
|
|
mNotificationCallbacks = nsnull;
|
|
mChannelEventSink = nsnull;
|
|
mProgressEventSink = nsnull;
|
|
|
|
if (NS_FAILED(status)) {
|
|
// This can happen if the server is unreachable. Other possible
|
|
// reasons are that the user leaves the page or hits the ESC key.
|
|
Error(nsnull);
|
|
|
|
// By nulling out channel here we make it so that Send() can test
|
|
// for that and throw. Also calling the various status
|
|
// methods/members will not throw.
|
|
// This matches what IE does.
|
|
mChannel = nsnull;
|
|
} else if (!parser || parser->IsParserEnabled()) {
|
|
// If we don't have a parser, we never attempted to parse the
|
|
// incoming data, and we can proceed to call RequestCompleted().
|
|
// Alternatively, if we do have a parser, its possible that we
|
|
// have given it some data and this caused it to block e.g. by a
|
|
// by a xml-stylesheet PI. In this case, we will have to wait till
|
|
// it gets enabled again and RequestCompleted() must be called
|
|
// later, when we get the load event from the document. If the
|
|
// parser is enabled, it is not blocked and we can still go ahead
|
|
// and call RequestCompleted() and expect everything to get
|
|
// cleaned up immediately.
|
|
RequestCompleted();
|
|
} else {
|
|
ChangeState(XML_HTTP_REQUEST_STOPPED, PR_FALSE);
|
|
}
|
|
|
|
mState &= ~XML_HTTP_REQUEST_SYNCLOOPING;
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsXMLHttpRequest::RequestCompleted()
|
|
{
|
|
nsresult rv = NS_OK;
|
|
|
|
mState &= ~XML_HTTP_REQUEST_SYNCLOOPING;
|
|
|
|
// If we're uninitialized at this point, we encountered an error
|
|
// earlier and listeners have already been notified. Also we do
|
|
// not want to do this if we already completed.
|
|
if (mState & (XML_HTTP_REQUEST_UNINITIALIZED |
|
|
XML_HTTP_REQUEST_COMPLETED)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// We might have been sent non-XML data. If that was the case,
|
|
// we should null out the document member. The idea in this
|
|
// check here is that if there is no document element it is not
|
|
// an XML document. We might need a fancier check...
|
|
if (mResponseXML) {
|
|
nsCOMPtr<nsIDOMElement> root;
|
|
mResponseXML->GetDocumentElement(getter_AddRefs(root));
|
|
if (!root) {
|
|
mResponseXML = nsnull;
|
|
}
|
|
}
|
|
|
|
ChangeState(XML_HTTP_REQUEST_COMPLETED, PR_TRUE);
|
|
|
|
PRUint32 responseLength = mResponseBody.Length();
|
|
NS_NAMED_LITERAL_STRING(errorStr, ERROR_STR);
|
|
NS_NAMED_LITERAL_STRING(loadStr, LOAD_STR);
|
|
DispatchProgressEvent(this,
|
|
mErrorLoad ? errorStr : loadStr,
|
|
!mErrorLoad,
|
|
responseLength,
|
|
mErrorLoad ? 0 : responseLength);
|
|
if (mErrorLoad && mUpload && !mUploadComplete) {
|
|
DispatchProgressEvent(mUpload, errorStr, PR_TRUE,
|
|
mUploadTransferred, mUploadTotal);
|
|
}
|
|
|
|
if (!(mState & XML_HTTP_REQUEST_GOT_FINAL_STOP)) {
|
|
// We're a multipart request, so we're not done. Reset to opened.
|
|
ChangeState(XML_HTTP_REQUEST_OPENED);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::SendAsBinary(const nsAString &aBody)
|
|
{
|
|
char *data = static_cast<char*>(NS_Alloc(aBody.Length() + 1));
|
|
if (!data)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
nsAString::const_iterator iter, end;
|
|
aBody.BeginReading(iter);
|
|
aBody.EndReading(end);
|
|
char *p = data;
|
|
while (iter != end) {
|
|
if (*iter & 0xFF00) {
|
|
NS_Free(data);
|
|
return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
|
|
}
|
|
*p++ = static_cast<char>(*iter++);
|
|
}
|
|
*p = '\0';
|
|
|
|
nsCOMPtr<nsIInputStream> stream;
|
|
nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), data,
|
|
aBody.Length(), NS_ASSIGNMENT_ADOPT);
|
|
if (NS_FAILED(rv))
|
|
NS_Free(data);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIWritableVariant> variant = new nsVariant();
|
|
if (!variant) return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
rv = variant->SetAsISupports(stream);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return Send(variant);
|
|
}
|
|
|
|
static nsresult
|
|
GetRequestBody(nsIVariant* aBody, nsIInputStream** aResult,
|
|
nsACString& aContentType, nsACString& aCharset)
|
|
{
|
|
*aResult = nsnull;
|
|
aContentType.AssignLiteral("text/plain");
|
|
aCharset.AssignLiteral("UTF-8");
|
|
|
|
PRUint16 dataType;
|
|
nsresult rv = aBody->GetDataType(&dataType);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (dataType == nsIDataType::VTYPE_INTERFACE ||
|
|
dataType == nsIDataType::VTYPE_INTERFACE_IS) {
|
|
nsCOMPtr<nsISupports> supports;
|
|
nsID *iid;
|
|
rv = aBody->GetAsInterface(&iid, getter_AddRefs(supports));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsMemory::Free(iid);
|
|
|
|
// document?
|
|
nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(supports);
|
|
if (doc) {
|
|
aContentType.AssignLiteral("application/xml");
|
|
nsCOMPtr<nsIDOM3Document> dom3doc = do_QueryInterface(doc);
|
|
if (dom3doc) {
|
|
nsAutoString inputEncoding;
|
|
dom3doc->GetInputEncoding(inputEncoding);
|
|
if (!DOMStringIsNull(inputEncoding)) {
|
|
CopyUTF16toUTF8(inputEncoding, aCharset);
|
|
}
|
|
}
|
|
|
|
// Serialize to a stream so that the encoding used will
|
|
// match the document's.
|
|
nsCOMPtr<nsIDOMSerializer> serializer =
|
|
do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIStorageStream> storStream;
|
|
rv = NS_NewStorageStream(4096, PR_UINT32_MAX,
|
|
getter_AddRefs(storStream));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIOutputStream> output;
|
|
rv = storStream->GetOutputStream(0, getter_AddRefs(output));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Make sure to use the encoding we'll send
|
|
rv = serializer->SerializeToStream(doc, output, aCharset);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
output->Close();
|
|
|
|
return storStream->NewInputStream(0, aResult);
|
|
}
|
|
|
|
// nsISupportsString?
|
|
nsCOMPtr<nsISupportsString> wstr = do_QueryInterface(supports);
|
|
if (wstr) {
|
|
nsAutoString string;
|
|
wstr->GetData(string);
|
|
|
|
return NS_NewCStringInputStream(aResult,
|
|
NS_ConvertUTF16toUTF8(string));
|
|
}
|
|
|
|
// nsIInputStream?
|
|
nsCOMPtr<nsIInputStream> stream = do_QueryInterface(supports);
|
|
if (stream) {
|
|
*aResult = stream.forget().get();
|
|
aCharset.Truncate();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsIXHRSendable?
|
|
nsCOMPtr<nsIXHRSendable> sendable = do_QueryInterface(supports);
|
|
if (sendable) {
|
|
return sendable->GetSendInfo(aResult, aContentType, aCharset);
|
|
}
|
|
}
|
|
else if (dataType == nsIDataType::VTYPE_VOID ||
|
|
dataType == nsIDataType::VTYPE_EMPTY) {
|
|
// Makes us act as if !aBody, don't upload anything
|
|
return NS_OK;
|
|
}
|
|
|
|
PRUnichar* data = nsnull;
|
|
PRUint32 len = 0;
|
|
rv = aBody->GetAsWStringWithSize(&len, &data);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsString string;
|
|
string.Adopt(data, len);
|
|
|
|
return NS_NewCStringInputStream(aResult, NS_ConvertUTF16toUTF8(string));
|
|
}
|
|
|
|
/* void send (in nsIVariant aBody); */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::Send(nsIVariant *aBody)
|
|
{
|
|
NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
nsresult rv = CheckInnerWindowCorrectness();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Return error if we're already processing a request
|
|
if (XML_HTTP_REQUEST_SENT & mState) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// Make sure we've been opened
|
|
if (!mChannel || !(XML_HTTP_REQUEST_OPENED & mState)) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
|
|
// nsIRequest::LOAD_BACKGROUND prevents throbber from becoming active, which
|
|
// in turn keeps STOP button from becoming active. If the consumer passed in
|
|
// a progress event handler we must load with nsIRequest::LOAD_NORMAL or
|
|
// necko won't generate any progress notifications.
|
|
if (HasListenersFor(NS_LITERAL_STRING(PROGRESS_STR)) ||
|
|
HasListenersFor(NS_LITERAL_STRING(UPLOADPROGRESS_STR)) ||
|
|
(mUpload && mUpload->HasListenersFor(NS_LITERAL_STRING(PROGRESS_STR)))) {
|
|
nsLoadFlags loadFlags;
|
|
mChannel->GetLoadFlags(&loadFlags);
|
|
loadFlags &= ~nsIRequest::LOAD_BACKGROUND;
|
|
loadFlags |= nsIRequest::LOAD_NORMAL;
|
|
mChannel->SetLoadFlags(loadFlags);
|
|
}
|
|
|
|
// XXX We should probably send a warning to the JS console
|
|
// if there are no event listeners set and we are doing
|
|
// an asynchronous call.
|
|
|
|
// Ignore argument if method is GET, there is no point in trying to
|
|
// upload anything
|
|
nsCAutoString method;
|
|
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
|
|
|
|
if (httpChannel) {
|
|
httpChannel->GetRequestMethod(method); // If GET, method name will be uppercase
|
|
|
|
if (!nsContentUtils::IsSystemPrincipal(mPrincipal)) {
|
|
nsCOMPtr<nsIURI> codebase;
|
|
mPrincipal->GetURI(getter_AddRefs(codebase));
|
|
|
|
httpChannel->SetReferrer(codebase);
|
|
}
|
|
|
|
// Some extensions override the http protocol handler and provide their own
|
|
// implementation. The channels returned from that implementation doesn't
|
|
// seem to always implement the nsIUploadChannel2 interface, presumably
|
|
// because it's a new interface.
|
|
// Eventually we should remove this and simply require that http channels
|
|
// implement the new interface.
|
|
// See bug 529041
|
|
nsCOMPtr<nsIUploadChannel2> uploadChannel2 =
|
|
do_QueryInterface(httpChannel);
|
|
if (!uploadChannel2) {
|
|
nsCOMPtr<nsIConsoleService> consoleService =
|
|
do_GetService(NS_CONSOLESERVICE_CONTRACTID);
|
|
if (consoleService) {
|
|
consoleService->LogStringMessage(NS_LITERAL_STRING(
|
|
"Http channel implementation doesn't support nsIUploadChannel2. An extension has supplied a non-functional http protocol handler. This will break behavior and in future releases not work at all."
|
|
).get());
|
|
}
|
|
}
|
|
}
|
|
|
|
mUploadTransferred = 0;
|
|
mUploadTotal = 0;
|
|
// By default we don't have any upload, so mark upload complete.
|
|
mUploadComplete = PR_TRUE;
|
|
mErrorLoad = PR_FALSE;
|
|
mLoadLengthComputable = PR_FALSE;
|
|
mLoadTotal = 0;
|
|
mUploadProgress = 0;
|
|
mUploadProgressMax = 0;
|
|
if (aBody && httpChannel && !method.EqualsLiteral("GET")) {
|
|
|
|
nsCAutoString charset;
|
|
nsCAutoString defaultContentType;
|
|
nsCOMPtr<nsIInputStream> postDataStream;
|
|
|
|
rv = GetRequestBody(aBody, getter_AddRefs(postDataStream),
|
|
defaultContentType, charset);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (postDataStream) {
|
|
// If no content type header was set by the client, we set it to
|
|
// application/xml.
|
|
nsCAutoString contentType;
|
|
if (NS_FAILED(httpChannel->
|
|
GetRequestHeader(NS_LITERAL_CSTRING("Content-Type"),
|
|
contentType)) ||
|
|
contentType.IsEmpty()) {
|
|
contentType = defaultContentType;
|
|
}
|
|
|
|
// We don't want to set a charset for streams.
|
|
if (!charset.IsEmpty()) {
|
|
nsCAutoString specifiedCharset;
|
|
PRBool haveCharset;
|
|
PRInt32 charsetStart, charsetEnd;
|
|
rv = NS_ExtractCharsetFromContentType(contentType, specifiedCharset,
|
|
&haveCharset, &charsetStart,
|
|
&charsetEnd);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
// If the content-type the page set already has a charset parameter,
|
|
// and it's the same charset, up to case, as |charset|, just send the
|
|
// page-set content-type header. Apparently at least
|
|
// google-web-toolkit is broken and relies on the exact case of its
|
|
// charset parameter, which makes things break if we use |charset|
|
|
// (which is always a fully resolved charset per our charset alias
|
|
// table, hence might be differently cased).
|
|
if (!specifiedCharset.Equals(charset,
|
|
nsCaseInsensitiveCStringComparator())) {
|
|
nsCAutoString newCharset("; charset=");
|
|
newCharset.Append(charset);
|
|
contentType.Replace(charsetStart, charsetEnd - charsetStart,
|
|
newCharset);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If necessary, wrap the stream in a buffered stream so as to guarantee
|
|
// support for our upload when calling ExplicitSetUploadStream.
|
|
if (!NS_InputStreamIsBuffered(postDataStream)) {
|
|
nsCOMPtr<nsIInputStream> bufferedStream;
|
|
rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
|
|
postDataStream,
|
|
4096);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
postDataStream = bufferedStream;
|
|
}
|
|
|
|
mUploadComplete = PR_FALSE;
|
|
PRUint32 uploadTotal = 0;
|
|
postDataStream->Available(&uploadTotal);
|
|
mUploadTotal = uploadTotal;
|
|
|
|
// We want to use a newer version of the upload channel that won't
|
|
// ignore the necessary headers for an empty Content-Type.
|
|
nsCOMPtr<nsIUploadChannel2> uploadChannel2(do_QueryInterface(httpChannel));
|
|
// This assertion will fire if buggy extensions are installed
|
|
NS_ASSERTION(uploadChannel2, "http must support nsIUploadChannel2");
|
|
if (uploadChannel2) {
|
|
uploadChannel2->ExplicitSetUploadStream(postDataStream, contentType,
|
|
-1, method, PR_FALSE);
|
|
}
|
|
else {
|
|
// http channel doesn't support the new nsIUploadChannel2. Emulate
|
|
// as best we can using nsIUploadChannel
|
|
if (contentType.IsEmpty()) {
|
|
contentType.AssignLiteral("application/octet-stream");
|
|
}
|
|
nsCOMPtr<nsIUploadChannel> uploadChannel =
|
|
do_QueryInterface(httpChannel);
|
|
uploadChannel->SetUploadStream(postDataStream, contentType, -1);
|
|
// Reset the method to its original value
|
|
httpChannel->SetRequestMethod(method);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (httpChannel) {
|
|
nsCAutoString contentTypeHeader;
|
|
rv = httpChannel->GetRequestHeader(NS_LITERAL_CSTRING("Content-Type"),
|
|
contentTypeHeader);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
nsCAutoString contentType, charset;
|
|
rv = NS_ParseContentType(contentTypeHeader, contentType, charset);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!contentType.LowerCaseEqualsLiteral("text/plain") &&
|
|
!contentType.LowerCaseEqualsLiteral("application/x-www-form-urlencoded") &&
|
|
!contentType.LowerCaseEqualsLiteral("multipart/form-data")) {
|
|
mACUnsafeHeaders.AppendElement(NS_LITERAL_CSTRING("Content-Type"));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reset responseBody
|
|
mResponseBody.Truncate();
|
|
mResponseBodyUnicode.SetIsVoid(PR_TRUE);
|
|
|
|
// Reset responseXML
|
|
mResponseXML = nsnull;
|
|
|
|
rv = CheckChannelForCrossSiteRequest(mChannel);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
PRBool withCredentials = !!(mState & XML_HTTP_REQUEST_AC_WITH_CREDENTIALS);
|
|
|
|
// If so, set up the preflight
|
|
if (mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT) {
|
|
// Check to see if this initial OPTIONS request has already been cached
|
|
// in our special Access Control Cache.
|
|
nsCOMPtr<nsIURI> uri;
|
|
rv = NS_GetFinalChannelURI(mChannel, getter_AddRefs(uri));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsAccessControlLRUCache::CacheEntry* entry =
|
|
sAccessControlCache ?
|
|
sAccessControlCache->GetEntry(uri, mPrincipal, withCredentials, PR_FALSE) :
|
|
nsnull;
|
|
|
|
if (!entry || !entry->CheckRequest(method, mACUnsafeHeaders)) {
|
|
// Either it wasn't cached or the cached result has expired. Build a
|
|
// channel for the OPTIONS request.
|
|
nsCOMPtr<nsILoadGroup> loadGroup;
|
|
GetLoadGroup(getter_AddRefs(loadGroup));
|
|
|
|
nsLoadFlags loadFlags;
|
|
rv = mChannel->GetLoadFlags(&loadFlags);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = NS_NewChannel(getter_AddRefs(mACGetChannel), uri, nsnull,
|
|
loadGroup, nsnull, loadFlags);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIHttpChannel> acHttp = do_QueryInterface(mACGetChannel);
|
|
NS_ASSERTION(acHttp, "Failed to QI to nsIHttpChannel!");
|
|
|
|
rv = acHttp->SetRequestMethod(NS_LITERAL_CSTRING("OPTIONS"));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
// Hook us up to listen to redirects and the like
|
|
mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
|
|
mChannel->SetNotificationCallbacks(this);
|
|
|
|
// Create our listener
|
|
nsCOMPtr<nsIStreamListener> listener = this;
|
|
if (mState & XML_HTTP_REQUEST_MULTIPART) {
|
|
listener = new nsMultipartProxyListener(listener);
|
|
if (!listener) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
if (!(mState & XML_HTTP_REQUEST_XSITEENABLED)) {
|
|
// Always create a nsCrossSiteListenerProxy here even if it's
|
|
// a same-origin request right now, since it could be redirected.
|
|
listener = new nsCrossSiteListenerProxy(listener, mPrincipal, mChannel,
|
|
withCredentials, &rv);
|
|
NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
// Bypass the network cache in cases where it makes no sense:
|
|
// 1) Multipart responses are very large and would likely be doomed by the
|
|
// cache once they grow too large, so they are not worth caching.
|
|
// 2) POST responses are always unique, and we provide no API that would
|
|
// allow our consumers to specify a "cache key" to access old POST
|
|
// responses, so they are not worth caching.
|
|
if ((mState & XML_HTTP_REQUEST_MULTIPART) || method.EqualsLiteral("POST")) {
|
|
AddLoadFlags(mChannel,
|
|
nsIRequest::LOAD_BYPASS_CACHE | nsIRequest::INHIBIT_CACHING);
|
|
}
|
|
// When we are sync loading, we need to bypass the local cache when it would
|
|
// otherwise block us waiting for exclusive access to the cache. If we don't
|
|
// do this, then we could dead lock in some cases (see bug 309424).
|
|
else if (!(mState & XML_HTTP_REQUEST_ASYNC)) {
|
|
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
|
|
// This means that we always try to parse local files as XML
|
|
// ignoring return value, as this is not critical
|
|
mChannel->SetContentType(NS_LITERAL_CSTRING("application/xml"));
|
|
|
|
// 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> acProxyListener =
|
|
new nsACProxyListener(mChannel, listener, nsnull, mPrincipal, method,
|
|
withCredentials);
|
|
NS_ENSURE_TRUE(acProxyListener, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
acProxyListener =
|
|
new nsCrossSiteListenerProxy(acProxyListener, mPrincipal, mACGetChannel,
|
|
withCredentials, method, mACUnsafeHeaders,
|
|
&rv);
|
|
NS_ENSURE_TRUE(acProxyListener, NS_ERROR_OUT_OF_MEMORY);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = mACGetChannel->AsyncOpen(acProxyListener, 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
|
|
mChannel = nsnull;
|
|
mACGetChannel = nsnull;
|
|
return rv;
|
|
}
|
|
|
|
// If we're synchronous, spin an event loop here and wait
|
|
if (!(mState & XML_HTTP_REQUEST_ASYNC)) {
|
|
mState |= XML_HTTP_REQUEST_SYNCLOOPING;
|
|
|
|
nsCOMPtr<nsIDocument> suspendedDoc;
|
|
nsCOMPtr<nsIRunnable> resumeTimeoutRunnable;
|
|
if (mOwner) {
|
|
nsCOMPtr<nsIDOMWindow> topWindow;
|
|
if (NS_SUCCEEDED(mOwner->GetTop(getter_AddRefs(topWindow)))) {
|
|
nsCOMPtr<nsPIDOMWindow> suspendedWindow(do_QueryInterface(topWindow));
|
|
if (suspendedWindow &&
|
|
(suspendedWindow = suspendedWindow->GetCurrentInnerWindow())) {
|
|
suspendedDoc = do_QueryInterface(suspendedWindow->GetExtantDocument());
|
|
if (suspendedDoc) {
|
|
suspendedDoc->SuppressEventHandling();
|
|
}
|
|
suspendedWindow->SuspendTimeouts(1, PR_FALSE);
|
|
resumeTimeoutRunnable = new nsResumeTimeoutsEvent(suspendedWindow);
|
|
}
|
|
}
|
|
}
|
|
|
|
ChangeState(XML_HTTP_REQUEST_SENT);
|
|
// Note, calling ChangeState may have cleared
|
|
// XML_HTTP_REQUEST_SYNCLOOPING flag.
|
|
nsIThread *thread = NS_GetCurrentThread();
|
|
while (mState & XML_HTTP_REQUEST_SYNCLOOPING) {
|
|
if (!NS_ProcessNextEvent(thread)) {
|
|
rv = NS_ERROR_UNEXPECTED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (suspendedDoc) {
|
|
suspendedDoc->UnsuppressEventHandlingAndFireEvents(PR_TRUE);
|
|
}
|
|
|
|
if (resumeTimeoutRunnable) {
|
|
NS_DispatchToCurrentThread(resumeTimeoutRunnable);
|
|
}
|
|
} else {
|
|
// Now that we've successfully opened the channel, we can change state. Note
|
|
// that this needs to come after the AsyncOpen() and rv check, because this
|
|
// can run script that would try to restart this request, and that could end
|
|
// up doing our AsyncOpen on a null channel if the reentered AsyncOpen fails.
|
|
ChangeState(XML_HTTP_REQUEST_SENT);
|
|
if (!mUploadComplete &&
|
|
HasListenersFor(NS_LITERAL_STRING(UPLOADPROGRESS_STR)) ||
|
|
(mUpload && mUpload->HasListenersFor(NS_LITERAL_STRING(PROGRESS_STR)))) {
|
|
StartProgressEventTimer();
|
|
}
|
|
DispatchProgressEvent(this, NS_LITERAL_STRING(LOADSTART_STR), PR_FALSE,
|
|
0, 0);
|
|
if (mUpload && !mUploadComplete) {
|
|
DispatchProgressEvent(mUpload, NS_LITERAL_STRING(LOADSTART_STR), PR_TRUE,
|
|
0, mUploadTotal);
|
|
}
|
|
}
|
|
|
|
if (!mChannel) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* void setRequestHeader (in AUTF8String header, in AUTF8String value); */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::SetRequestHeader(const nsACString& header,
|
|
const nsACString& value)
|
|
{
|
|
nsresult rv;
|
|
|
|
// Make sure we don't store an invalid header name in mACUnsafeHeaders
|
|
if (!IsValidHTTPToken(header)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// Check that we haven't already opened the channel. We can't rely on
|
|
// the channel throwing from mChannel->SetRequestHeader since we might
|
|
// still be waiting for mACGetChannel to actually open mChannel
|
|
if (mACGetChannel) {
|
|
PRBool pending;
|
|
rv = mACGetChannel->IsPending(&pending);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (pending) {
|
|
return NS_ERROR_IN_PROGRESS;
|
|
}
|
|
}
|
|
|
|
if (!mChannel) // open() initializes mChannel, and open()
|
|
return NS_ERROR_FAILURE; // must be called before first setRequestHeader()
|
|
|
|
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
|
|
if (!httpChannel) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Prevent modification to certain HTTP headers (see bug 302263), unless
|
|
// the executing script has UniversalBrowserWrite permission.
|
|
|
|
PRBool privileged;
|
|
rv = IsCapabilityEnabled("UniversalBrowserWrite", &privileged);
|
|
if (NS_FAILED(rv))
|
|
return NS_ERROR_FAILURE;
|
|
|
|
if (!privileged) {
|
|
// Check for dangerous headers
|
|
const char *kInvalidHeaders[] = {
|
|
"accept-charset", "accept-encoding", "access-control-request-headers",
|
|
"access-control-request-method", "connection", "content-length",
|
|
"cookie", "cookie2", "content-transfer-encoding", "date", "expect",
|
|
"host", "keep-alive", "origin", "referer", "te", "trailer",
|
|
"transfer-encoding", "upgrade", "user-agent", "via"
|
|
};
|
|
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;
|
|
}
|
|
}
|
|
if (StringBeginsWith(header, NS_LITERAL_CSTRING("proxy-"),
|
|
nsCaseInsensitiveCStringComparator()) ||
|
|
StringBeginsWith(header, NS_LITERAL_CSTRING("sec-"),
|
|
nsCaseInsensitiveCStringComparator())) {
|
|
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) {
|
|
// Content-Type isn't always safe, but we'll deal with it in Send()
|
|
const char *kCrossOriginSafeHeaders[] = {
|
|
"accept", "accept-language", "content-language", "content-type",
|
|
"last-event-id"
|
|
};
|
|
for (i = 0; i < NS_ARRAY_LENGTH(kCrossOriginSafeHeaders); ++i) {
|
|
if (header.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
|
|
safeHeader = PR_TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!safeHeader) {
|
|
mACUnsafeHeaders.AppendElement(header);
|
|
}
|
|
}
|
|
|
|
// We need to set, not add to, the header.
|
|
return httpChannel->SetRequestHeader(header, value, PR_FALSE);
|
|
}
|
|
|
|
/* readonly attribute long readyState; */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::GetReadyState(PRInt32 *aState)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aState);
|
|
// Translate some of our internal states for external consumers
|
|
if (mState & XML_HTTP_REQUEST_UNINITIALIZED) {
|
|
*aState = 0; // UNINITIALIZED
|
|
} else if (mState & (XML_HTTP_REQUEST_OPENED | XML_HTTP_REQUEST_SENT)) {
|
|
*aState = 1; // LOADING
|
|
} else if (mState & XML_HTTP_REQUEST_LOADED) {
|
|
*aState = 2; // LOADED
|
|
} else if (mState & (XML_HTTP_REQUEST_INTERACTIVE | XML_HTTP_REQUEST_STOPPED)) {
|
|
*aState = 3; // INTERACTIVE
|
|
} else if (mState & XML_HTTP_REQUEST_COMPLETED) {
|
|
*aState = 4; // COMPLETED
|
|
} else {
|
|
NS_ERROR("Should not happen");
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void overrideMimeType(in AUTF8String mimetype); */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::OverrideMimeType(const nsACString& aMimeType)
|
|
{
|
|
// XXX Should we do some validation here?
|
|
mOverrideMimeType.Assign(aMimeType);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
/* attribute boolean multipart; */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::GetMultipart(PRBool *_retval)
|
|
{
|
|
*_retval = !!(mState & XML_HTTP_REQUEST_MULTIPART);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* attribute boolean multipart; */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::SetMultipart(PRBool aMultipart)
|
|
{
|
|
if (!(mState & XML_HTTP_REQUEST_UNINITIALIZED)) {
|
|
// Can't change this while we're in the middle of something.
|
|
return NS_ERROR_IN_PROGRESS;
|
|
}
|
|
|
|
if (aMultipart) {
|
|
mState |= XML_HTTP_REQUEST_MULTIPART;
|
|
} else {
|
|
mState &= ~XML_HTTP_REQUEST_MULTIPART;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* attribute boolean mozBackgroundRequest; */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::GetMozBackgroundRequest(PRBool *_retval)
|
|
{
|
|
*_retval = !!(mState & XML_HTTP_REQUEST_BACKGROUND);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* attribute boolean mozBackgroundRequest; */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::SetMozBackgroundRequest(PRBool aMozBackgroundRequest)
|
|
{
|
|
PRBool privileged;
|
|
|
|
nsresult rv = IsCapabilityEnabled("UniversalXPConnect", &privileged);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!privileged)
|
|
return NS_ERROR_DOM_SECURITY_ERR;
|
|
|
|
if (!(mState & XML_HTTP_REQUEST_UNINITIALIZED)) {
|
|
// Can't change this while we're in the middle of something.
|
|
return NS_ERROR_IN_PROGRESS;
|
|
}
|
|
|
|
if (aMozBackgroundRequest) {
|
|
mState |= XML_HTTP_REQUEST_BACKGROUND;
|
|
} else {
|
|
mState &= ~XML_HTTP_REQUEST_BACKGROUND;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* attribute boolean withCredentials; */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::GetWithCredentials(PRBool *_retval)
|
|
{
|
|
*_retval = !!(mState & XML_HTTP_REQUEST_AC_WITH_CREDENTIALS);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* attribute boolean withCredentials; */
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::SetWithCredentials(PRBool aWithCredentials)
|
|
{
|
|
// Return error if we're already processing a request
|
|
if (XML_HTTP_REQUEST_SENT & mState) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (aWithCredentials) {
|
|
mState |= XML_HTTP_REQUEST_AC_WITH_CREDENTIALS;
|
|
}
|
|
else {
|
|
mState &= ~XML_HTTP_REQUEST_AC_WITH_CREDENTIALS;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsIDOMEventListener
|
|
nsresult
|
|
nsXMLHttpRequest::HandleEvent(nsIDOMEvent* aEvent)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsIDOMLoadListener
|
|
nsresult
|
|
nsXMLHttpRequest::Load(nsIDOMEvent* aEvent)
|
|
{
|
|
// If we had an XML error in the data, the parser terminated and
|
|
// we received the load event, even though we might still be
|
|
// loading data into responseBody/responseText. We will delay
|
|
// sending the load event until OnStopRequest(). In normal case
|
|
// there is no harm done, we will get OnStopRequest() immediately
|
|
// after the load event.
|
|
//
|
|
// However, if the data we were loading caused the parser to stop,
|
|
// for example when loading external stylesheets, we can receive
|
|
// the OnStopRequest() call before the parser has finished building
|
|
// the document. In that case, we obviously should not fire the event
|
|
// in OnStopRequest(). For those documents, we must wait for the load
|
|
// event from the document to fire our RequestCompleted().
|
|
if (mState & XML_HTTP_REQUEST_STOPPED) {
|
|
RequestCompleted();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsXMLHttpRequest::Unload(nsIDOMEvent* aEvent)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsXMLHttpRequest::BeforeUnload(nsIDOMEvent* aEvent)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsXMLHttpRequest::Abort(nsIDOMEvent* aEvent)
|
|
{
|
|
Abort();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsXMLHttpRequest::Error(nsIDOMEvent* aEvent)
|
|
{
|
|
mResponseXML = nsnull;
|
|
ChangeState(XML_HTTP_REQUEST_COMPLETED);
|
|
|
|
mState &= ~XML_HTTP_REQUEST_SYNCLOOPING;
|
|
|
|
DispatchProgressEvent(this, NS_LITERAL_STRING(ERROR_STR), PR_FALSE,
|
|
mResponseBody.Length(), 0);
|
|
if (mUpload && !mUploadComplete) {
|
|
mUploadComplete = PR_TRUE;
|
|
DispatchProgressEvent(mUpload, NS_LITERAL_STRING(ERROR_STR), PR_TRUE,
|
|
mUploadTransferred, mUploadTotal);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsXMLHttpRequest::ChangeState(PRUint32 aState, PRBool aBroadcast)
|
|
{
|
|
// If we are setting one of the mutually exclusive states,
|
|
// unset those state bits first.
|
|
if (aState & XML_HTTP_REQUEST_LOADSTATES) {
|
|
mState &= ~XML_HTTP_REQUEST_LOADSTATES;
|
|
}
|
|
mState |= aState;
|
|
nsresult rv = NS_OK;
|
|
|
|
if (mProgressNotifier &&
|
|
!(aState & (XML_HTTP_REQUEST_LOADED | XML_HTTP_REQUEST_INTERACTIVE))) {
|
|
mTimerIsActive = PR_FALSE;
|
|
mProgressNotifier->Cancel();
|
|
}
|
|
|
|
if ((aState & XML_HTTP_REQUEST_LOADSTATES) && // Broadcast load states only
|
|
aBroadcast &&
|
|
(mState & XML_HTTP_REQUEST_ASYNC ||
|
|
aState & XML_HTTP_REQUEST_OPENED ||
|
|
aState & XML_HTTP_REQUEST_COMPLETED)) {
|
|
nsCOMPtr<nsIDOMEvent> event;
|
|
rv = CreateReadystatechangeEvent(getter_AddRefs(event));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
DispatchDOMEvent(nsnull, event, nsnull, nsnull);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* Simple helper class that just forwards the redirect callback back
|
|
* to the nsXMLHttpRequest.
|
|
*/
|
|
class AsyncVerifyRedirectCallbackForwarder : public nsIAsyncVerifyRedirectCallback
|
|
{
|
|
public:
|
|
AsyncVerifyRedirectCallbackForwarder(nsXMLHttpRequest *xhr)
|
|
: mXHR(xhr)
|
|
{
|
|
}
|
|
|
|
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
|
NS_DECL_CYCLE_COLLECTION_CLASS(AsyncVerifyRedirectCallbackForwarder)
|
|
|
|
// nsIAsyncVerifyRedirectCallback implementation
|
|
NS_IMETHOD OnRedirectVerifyCallback(nsresult result)
|
|
{
|
|
mXHR->OnRedirectVerifyCallback(result);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
nsRefPtr<nsXMLHttpRequest> mXHR;
|
|
};
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(AsyncVerifyRedirectCallbackForwarder)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AsyncVerifyRedirectCallbackForwarder)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mXHR, nsIDOMEventListener)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AsyncVerifyRedirectCallbackForwarder)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mXHR)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AsyncVerifyRedirectCallbackForwarder)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(AsyncVerifyRedirectCallbackForwarder)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(AsyncVerifyRedirectCallbackForwarder)
|
|
|
|
|
|
/////////////////////////////////////////////////////
|
|
// nsIChannelEventSink methods:
|
|
//
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
|
|
nsIChannel *aNewChannel,
|
|
PRUint32 aFlags,
|
|
nsIAsyncVerifyRedirectCallback *callback)
|
|
{
|
|
NS_PRECONDITION(aNewChannel, "Redirect without a channel?");
|
|
|
|
nsresult rv;
|
|
|
|
if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) {
|
|
rv = CheckChannelForCrossSiteRequest(aNewChannel);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("nsXMLHttpRequest::OnChannelRedirect: "
|
|
"CheckChannelForCrossSiteRequest returned failure");
|
|
return rv;
|
|
}
|
|
|
|
// Disable redirects for preflighted 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_NEED_AC_PREFLIGHT)) {
|
|
return NS_ERROR_DOM_BAD_URI;
|
|
}
|
|
}
|
|
|
|
// Prepare to receive callback
|
|
mRedirectCallback = callback;
|
|
mNewRedirectChannel = aNewChannel;
|
|
|
|
if (mChannelEventSink) {
|
|
nsRefPtr<AsyncVerifyRedirectCallbackForwarder> fwd =
|
|
new AsyncVerifyRedirectCallbackForwarder(this);
|
|
|
|
rv = mChannelEventSink->AsyncOnChannelRedirect(aOldChannel,
|
|
aNewChannel,
|
|
aFlags, fwd);
|
|
if (NS_FAILED(rv)) {
|
|
mRedirectCallback = nsnull;
|
|
mNewRedirectChannel = nsnull;
|
|
}
|
|
return rv;
|
|
}
|
|
OnRedirectVerifyCallback(NS_OK);
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsXMLHttpRequest::OnRedirectVerifyCallback(nsresult result)
|
|
{
|
|
NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
|
|
NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
|
|
|
|
if (NS_SUCCEEDED(result))
|
|
mChannel = mNewRedirectChannel;
|
|
else
|
|
mErrorLoad = PR_TRUE;
|
|
|
|
mNewRedirectChannel = nsnull;
|
|
|
|
mRedirectCallback->OnRedirectVerifyCallback(result);
|
|
mRedirectCallback = nsnull;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////
|
|
// nsIProgressEventSink methods:
|
|
//
|
|
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::OnProgress(nsIRequest *aRequest, nsISupports *aContext, PRUint64 aProgress, PRUint64 aProgressMax)
|
|
{
|
|
// We're in middle of processing multipart headers and we don't want to report
|
|
// any progress because upload's 'load' is dispatched when we start to load
|
|
// the first response.
|
|
if (XML_HTTP_REQUEST_MPART_HEADERS & mState) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// We're uploading if our state is XML_HTTP_REQUEST_OPENED or
|
|
// XML_HTTP_REQUEST_SENT
|
|
PRBool upload = !!((XML_HTTP_REQUEST_OPENED | XML_HTTP_REQUEST_SENT) & mState);
|
|
PRUint64 loaded = aProgress;
|
|
PRUint64 total = aProgressMax;
|
|
// When uploading, OnProgress reports also headers in aProgress and aProgressMax.
|
|
// So, try to remove the headers, if possible.
|
|
PRBool lengthComputable = (aProgressMax != LL_MAXUINT);
|
|
if (upload) {
|
|
if (lengthComputable) {
|
|
PRUint64 headerSize = aProgressMax - mUploadTotal;
|
|
loaded -= headerSize;
|
|
total -= headerSize;
|
|
}
|
|
mUploadTransferred = loaded;
|
|
mUploadProgress = aProgress;
|
|
mUploadProgressMax = aProgressMax;
|
|
} else {
|
|
mLoadLengthComputable = lengthComputable;
|
|
mLoadTotal = mLoadLengthComputable ? total : 0;
|
|
}
|
|
|
|
if (mTimerIsActive) {
|
|
// The progress event will be dispatched when the notifier calls Notify().
|
|
mProgressEventWasDelayed = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!mErrorLoad && (mState & XML_HTTP_REQUEST_ASYNC)) {
|
|
StartProgressEventTimer();
|
|
NS_NAMED_LITERAL_STRING(progress, PROGRESS_STR);
|
|
NS_NAMED_LITERAL_STRING(uploadprogress, UPLOADPROGRESS_STR);
|
|
DispatchProgressEvent(this, upload ? uploadprogress : progress, PR_TRUE,
|
|
lengthComputable, loaded, lengthComputable ? total : 0,
|
|
aProgress, aProgressMax);
|
|
|
|
if (upload && mUpload && !mUploadComplete) {
|
|
NS_WARN_IF_FALSE(mUploadTotal == total, "Wrong upload total?");
|
|
DispatchProgressEvent(mUpload, progress, PR_TRUE, lengthComputable, loaded,
|
|
lengthComputable ? total : 0, aProgress, aProgressMax);
|
|
}
|
|
}
|
|
|
|
if (mProgressEventSink) {
|
|
mProgressEventSink->OnProgress(aRequest, aContext, aProgress,
|
|
aProgressMax);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::OnStatus(nsIRequest *aRequest, nsISupports *aContext, nsresult aStatus, const PRUnichar *aStatusArg)
|
|
{
|
|
if (mProgressEventSink) {
|
|
mProgressEventSink->OnStatus(aRequest, aContext, aStatus, aStatusArg);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
PRBool
|
|
nsXMLHttpRequest::AllowUploadProgress()
|
|
{
|
|
return !(mState & XML_HTTP_REQUEST_USE_XSITE_AC) ||
|
|
(mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////
|
|
// nsIInterfaceRequestor methods:
|
|
//
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::GetInterface(const nsIID & aIID, void **aResult)
|
|
{
|
|
nsresult rv;
|
|
|
|
// Make sure to return ourselves for the channel event sink interface and
|
|
// progress event sink interface, no matter what. We can forward these to
|
|
// mNotificationCallbacks if it wants to get notifications for them. But we
|
|
// need to see these notifications for proper functioning.
|
|
if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
|
|
mChannelEventSink = do_GetInterface(mNotificationCallbacks);
|
|
*aResult = static_cast<nsIChannelEventSink*>(this);
|
|
NS_ADDREF_THIS();
|
|
return NS_OK;
|
|
} else if (aIID.Equals(NS_GET_IID(nsIProgressEventSink))) {
|
|
mProgressEventSink = do_GetInterface(mNotificationCallbacks);
|
|
*aResult = static_cast<nsIProgressEventSink*>(this);
|
|
NS_ADDREF_THIS();
|
|
return NS_OK;
|
|
}
|
|
|
|
// Now give mNotificationCallbacks (if non-null) a chance to return the
|
|
// desired interface.
|
|
if (mNotificationCallbacks) {
|
|
rv = mNotificationCallbacks->GetInterface(aIID, aResult);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
NS_ASSERTION(*aResult, "Lying nsIInterfaceRequestor implementation!");
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (mState & XML_HTTP_REQUEST_BACKGROUND) {
|
|
nsCOMPtr<nsIInterfaceRequestor> badCertHandler(do_CreateInstance(NS_BADCERTHANDLER_CONTRACTID, &rv));
|
|
|
|
// Ignore failure to get component, we may not have all its dependencies
|
|
// available
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = badCertHandler->GetInterface(aIID, aResult);
|
|
if (NS_SUCCEEDED(rv))
|
|
return rv;
|
|
}
|
|
}
|
|
else if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
|
|
aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
|
|
nsCOMPtr<nsIPromptFactory> wwatch =
|
|
do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Get the an auth prompter for our window so that the parenting
|
|
// of the dialogs works as it should when using tabs.
|
|
|
|
nsCOMPtr<nsIDOMWindow> window;
|
|
if (mOwner) {
|
|
window = mOwner->GetOuterWindow();
|
|
}
|
|
|
|
return wwatch->GetPrompt(window, aIID,
|
|
reinterpret_cast<void**>(aResult));
|
|
|
|
}
|
|
|
|
return QueryInterface(aIID, aResult);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::GetUpload(nsIXMLHttpRequestUpload** aUpload)
|
|
{
|
|
*aUpload = nsnull;
|
|
|
|
nsresult rv;
|
|
nsIScriptContext* scriptContext =
|
|
GetContextForEventHandlers(&rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (!mUpload) {
|
|
mUpload = new nsXMLHttpRequestUpload(mOwner, scriptContext);
|
|
NS_ENSURE_TRUE(mUpload, NS_ERROR_OUT_OF_MEMORY);
|
|
}
|
|
NS_ADDREF(*aUpload = mUpload);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsXMLHttpRequest::Notify(nsITimer* aTimer)
|
|
{
|
|
mTimerIsActive = PR_FALSE;
|
|
if (NS_SUCCEEDED(CheckInnerWindowCorrectness()) && !mErrorLoad &&
|
|
(mState & XML_HTTP_REQUEST_ASYNC)) {
|
|
if (mProgressEventWasDelayed) {
|
|
mProgressEventWasDelayed = PR_FALSE;
|
|
if (!(XML_HTTP_REQUEST_MPART_HEADERS & mState)) {
|
|
StartProgressEventTimer();
|
|
// We're uploading if our state is XML_HTTP_REQUEST_OPENED or
|
|
// XML_HTTP_REQUEST_SENT
|
|
if ((XML_HTTP_REQUEST_OPENED | XML_HTTP_REQUEST_SENT) & mState) {
|
|
DispatchProgressEvent(this, NS_LITERAL_STRING(UPLOADPROGRESS_STR),
|
|
PR_TRUE, PR_TRUE, mUploadTransferred,
|
|
mUploadTotal, mUploadProgress,
|
|
mUploadProgressMax);
|
|
if (mUpload && !mUploadComplete) {
|
|
DispatchProgressEvent(mUpload, NS_LITERAL_STRING(PROGRESS_STR),
|
|
PR_TRUE, PR_TRUE, mUploadTransferred,
|
|
mUploadTotal, mUploadProgress,
|
|
mUploadProgressMax);
|
|
}
|
|
} else {
|
|
DispatchProgressEvent(this, NS_LITERAL_STRING(PROGRESS_STR),
|
|
mLoadLengthComputable, mResponseBody.Length(),
|
|
mLoadTotal);
|
|
}
|
|
}
|
|
}
|
|
} else if (mProgressNotifier) {
|
|
mProgressNotifier->Cancel();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsXMLHttpRequest::StartProgressEventTimer()
|
|
{
|
|
if (!mProgressNotifier) {
|
|
mProgressNotifier = do_CreateInstance(NS_TIMER_CONTRACTID);
|
|
}
|
|
if (mProgressNotifier) {
|
|
mProgressEventWasDelayed = PR_FALSE;
|
|
mTimerIsActive = PR_TRUE;
|
|
mProgressNotifier->Cancel();
|
|
mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL,
|
|
nsITimer::TYPE_ONE_SHOT);
|
|
}
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS1(nsXMLHttpRequest::nsHeaderVisitor, nsIHttpHeaderVisitor)
|
|
|
|
NS_IMETHODIMP nsXMLHttpRequest::
|
|
nsHeaderVisitor::VisitHeader(const nsACString &header, const nsACString &value)
|
|
{
|
|
// See bug #380418. Hide "Set-Cookie" headers from non-chrome scripts.
|
|
PRBool chrome = PR_FALSE; // default to false in case IsCapabilityEnabled fails
|
|
IsCapabilityEnabled("UniversalXPConnect", &chrome);
|
|
if (!chrome &&
|
|
(header.LowerCaseEqualsASCII("set-cookie") ||
|
|
header.LowerCaseEqualsASCII("set-cookie2"))) {
|
|
NS_WARNING("blocked access to response header");
|
|
} else {
|
|
mHeaders.Append(header);
|
|
mHeaders.Append(": ");
|
|
mHeaders.Append(value);
|
|
mHeaders.Append('\n');
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
// DOM event class to handle progress notifications
|
|
nsXMLHttpProgressEvent::nsXMLHttpProgressEvent(nsIDOMProgressEvent* aInner,
|
|
PRUint64 aCurrentProgress,
|
|
PRUint64 aMaxProgress)
|
|
{
|
|
mInner = static_cast<nsDOMProgressEvent*>(aInner);
|
|
mCurProgress = aCurrentProgress;
|
|
mMaxProgress = aMaxProgress;
|
|
}
|
|
|
|
nsXMLHttpProgressEvent::~nsXMLHttpProgressEvent()
|
|
{}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(nsXMLHttpProgressEvent)
|
|
|
|
DOMCI_DATA(XMLHttpProgressEvent, nsXMLHttpProgressEvent)
|
|
|
|
// QueryInterface implementation for nsXMLHttpProgressEvent
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXMLHttpProgressEvent)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMProgressEvent)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEvent, nsIDOMProgressEvent)
|
|
NS_INTERFACE_MAP_ENTRY(nsIDOMNSEvent)
|
|
NS_INTERFACE_MAP_ENTRY(nsIPrivateDOMEvent)
|
|
NS_INTERFACE_MAP_ENTRY(nsIDOMProgressEvent)
|
|
NS_INTERFACE_MAP_ENTRY(nsIDOMLSProgressEvent)
|
|
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XMLHttpProgressEvent)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXMLHttpProgressEvent)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXMLHttpProgressEvent)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXMLHttpProgressEvent)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mInner);
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXMLHttpProgressEvent)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mInner,
|
|
nsIDOMProgressEvent)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_IMETHODIMP nsXMLHttpProgressEvent::GetInput(nsIDOMLSInput * *aInput)
|
|
{
|
|
*aInput = nsnull;
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP nsXMLHttpProgressEvent::GetPosition(PRUint32 *aPosition)
|
|
{
|
|
// XXX can we change the iface?
|
|
LL_L2UI(*aPosition, mCurProgress);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsXMLHttpProgressEvent::GetTotalSize(PRUint32 *aTotalSize)
|
|
{
|
|
// XXX can we change the iface?
|
|
LL_L2UI(*aTotalSize, mMaxProgress);
|
|
return NS_OK;
|
|
}
|
|
|