Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | #include "nsXMLHttpRequest.h" |
michael@0 | 8 | |
michael@0 | 9 | #include "mozilla/ArrayUtils.h" |
michael@0 | 10 | #include "mozilla/dom/XMLHttpRequestUploadBinding.h" |
michael@0 | 11 | #include "mozilla/EventDispatcher.h" |
michael@0 | 12 | #include "mozilla/EventListenerManager.h" |
michael@0 | 13 | #include "mozilla/MemoryReporting.h" |
michael@0 | 14 | #include "nsDOMBlobBuilder.h" |
michael@0 | 15 | #include "nsIDOMDocument.h" |
michael@0 | 16 | #include "nsIDOMProgressEvent.h" |
michael@0 | 17 | #include "nsIJARChannel.h" |
michael@0 | 18 | #include "nsLayoutCID.h" |
michael@0 | 19 | #include "nsReadableUtils.h" |
michael@0 | 20 | |
michael@0 | 21 | #include "nsIURI.h" |
michael@0 | 22 | #include "nsILoadGroup.h" |
michael@0 | 23 | #include "nsNetUtil.h" |
michael@0 | 24 | #include "nsStreamUtils.h" |
michael@0 | 25 | #include "nsThreadUtils.h" |
michael@0 | 26 | #include "nsIUploadChannel.h" |
michael@0 | 27 | #include "nsIUploadChannel2.h" |
michael@0 | 28 | #include "nsIDOMSerializer.h" |
michael@0 | 29 | #include "nsXPCOM.h" |
michael@0 | 30 | #include "nsIDOMEventListener.h" |
michael@0 | 31 | #include "nsIScriptSecurityManager.h" |
michael@0 | 32 | #include "nsIDOMWindow.h" |
michael@0 | 33 | #include "nsIVariant.h" |
michael@0 | 34 | #include "nsVariant.h" |
michael@0 | 35 | #include "nsIScriptError.h" |
michael@0 | 36 | #include "nsIStreamConverterService.h" |
michael@0 | 37 | #include "nsICachingChannel.h" |
michael@0 | 38 | #include "nsContentUtils.h" |
michael@0 | 39 | #include "nsCxPusher.h" |
michael@0 | 40 | #include "nsCycleCollectionParticipant.h" |
michael@0 | 41 | #include "nsIContentPolicy.h" |
michael@0 | 42 | #include "nsContentPolicyUtils.h" |
michael@0 | 43 | #include "nsError.h" |
michael@0 | 44 | #include "nsCrossSiteListenerProxy.h" |
michael@0 | 45 | #include "nsIHTMLDocument.h" |
michael@0 | 46 | #include "nsIStorageStream.h" |
michael@0 | 47 | #include "nsIPromptFactory.h" |
michael@0 | 48 | #include "nsIWindowWatcher.h" |
michael@0 | 49 | #include "nsIConsoleService.h" |
michael@0 | 50 | #include "nsIChannelPolicy.h" |
michael@0 | 51 | #include "nsChannelPolicy.h" |
michael@0 | 52 | #include "nsIContentSecurityPolicy.h" |
michael@0 | 53 | #include "nsAsyncRedirectVerifyHelper.h" |
michael@0 | 54 | #include "nsStringBuffer.h" |
michael@0 | 55 | #include "nsDOMFile.h" |
michael@0 | 56 | #include "nsIFileChannel.h" |
michael@0 | 57 | #include "mozilla/Telemetry.h" |
michael@0 | 58 | #include "jsfriendapi.h" |
michael@0 | 59 | #include "GeckoProfiler.h" |
michael@0 | 60 | #include "mozilla/dom/EncodingUtils.h" |
michael@0 | 61 | #include "nsIUnicodeDecoder.h" |
michael@0 | 62 | #include "mozilla/dom/XMLHttpRequestBinding.h" |
michael@0 | 63 | #include "mozilla/Attributes.h" |
michael@0 | 64 | #include "nsIPermissionManager.h" |
michael@0 | 65 | #include "nsMimeTypes.h" |
michael@0 | 66 | #include "nsIHttpChannelInternal.h" |
michael@0 | 67 | #include "nsCharSeparatedTokenizer.h" |
michael@0 | 68 | #include "nsFormData.h" |
michael@0 | 69 | #include "nsStreamListenerWrapper.h" |
michael@0 | 70 | #include "xpcjsid.h" |
michael@0 | 71 | #include "nsITimedChannel.h" |
michael@0 | 72 | |
michael@0 | 73 | #include "nsWrapperCacheInlines.h" |
michael@0 | 74 | |
michael@0 | 75 | using namespace mozilla; |
michael@0 | 76 | using namespace mozilla::dom; |
michael@0 | 77 | |
michael@0 | 78 | // Maximum size that we'll grow an ArrayBuffer instead of doubling, |
michael@0 | 79 | // once doubling reaches this threshold |
michael@0 | 80 | #define XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH (32*1024*1024) |
michael@0 | 81 | // start at 32k to avoid lots of doubling right at the start |
michael@0 | 82 | #define XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE (32*1024) |
michael@0 | 83 | // the maximum Content-Length that we'll preallocate. 1GB. Must fit |
michael@0 | 84 | // in an int32_t! |
michael@0 | 85 | #define XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE (1*1024*1024*1024LL) |
michael@0 | 86 | |
michael@0 | 87 | #define LOAD_STR "load" |
michael@0 | 88 | #define ERROR_STR "error" |
michael@0 | 89 | #define ABORT_STR "abort" |
michael@0 | 90 | #define TIMEOUT_STR "timeout" |
michael@0 | 91 | #define LOADSTART_STR "loadstart" |
michael@0 | 92 | #define PROGRESS_STR "progress" |
michael@0 | 93 | #define READYSTATE_STR "readystatechange" |
michael@0 | 94 | #define LOADEND_STR "loadend" |
michael@0 | 95 | |
michael@0 | 96 | // CIDs |
michael@0 | 97 | |
michael@0 | 98 | // State |
michael@0 | 99 | #define XML_HTTP_REQUEST_UNSENT (1 << 0) // 0 UNSENT |
michael@0 | 100 | #define XML_HTTP_REQUEST_OPENED (1 << 1) // 1 OPENED |
michael@0 | 101 | #define XML_HTTP_REQUEST_HEADERS_RECEIVED (1 << 2) // 2 HEADERS_RECEIVED |
michael@0 | 102 | #define XML_HTTP_REQUEST_LOADING (1 << 3) // 3 LOADING |
michael@0 | 103 | #define XML_HTTP_REQUEST_DONE (1 << 4) // 4 DONE |
michael@0 | 104 | #define XML_HTTP_REQUEST_SENT (1 << 5) // Internal, OPENED in IE and external view |
michael@0 | 105 | // The above states are mutually exclusive, change with ChangeState() only. |
michael@0 | 106 | // The states below can be combined. |
michael@0 | 107 | #define XML_HTTP_REQUEST_ABORTED (1 << 7) // Internal |
michael@0 | 108 | #define XML_HTTP_REQUEST_ASYNC (1 << 8) // Internal |
michael@0 | 109 | #define XML_HTTP_REQUEST_PARSEBODY (1 << 9) // Internal |
michael@0 | 110 | #define XML_HTTP_REQUEST_SYNCLOOPING (1 << 10) // Internal |
michael@0 | 111 | #define XML_HTTP_REQUEST_BACKGROUND (1 << 13) // Internal |
michael@0 | 112 | #define XML_HTTP_REQUEST_USE_XSITE_AC (1 << 14) // Internal |
michael@0 | 113 | #define XML_HTTP_REQUEST_NEED_AC_PREFLIGHT (1 << 15) // Internal |
michael@0 | 114 | #define XML_HTTP_REQUEST_AC_WITH_CREDENTIALS (1 << 16) // Internal |
michael@0 | 115 | #define XML_HTTP_REQUEST_TIMED_OUT (1 << 17) // Internal |
michael@0 | 116 | #define XML_HTTP_REQUEST_DELETED (1 << 18) // Internal |
michael@0 | 117 | |
michael@0 | 118 | #define XML_HTTP_REQUEST_LOADSTATES \ |
michael@0 | 119 | (XML_HTTP_REQUEST_UNSENT | \ |
michael@0 | 120 | XML_HTTP_REQUEST_OPENED | \ |
michael@0 | 121 | XML_HTTP_REQUEST_HEADERS_RECEIVED | \ |
michael@0 | 122 | XML_HTTP_REQUEST_LOADING | \ |
michael@0 | 123 | XML_HTTP_REQUEST_DONE | \ |
michael@0 | 124 | XML_HTTP_REQUEST_SENT) |
michael@0 | 125 | |
michael@0 | 126 | #define NS_BADCERTHANDLER_CONTRACTID \ |
michael@0 | 127 | "@mozilla.org/content/xmlhttprequest-bad-cert-handler;1" |
michael@0 | 128 | |
michael@0 | 129 | #define NS_PROGRESS_EVENT_INTERVAL 50 |
michael@0 | 130 | |
michael@0 | 131 | #define IMPL_CSTRING_GETTER(_name) \ |
michael@0 | 132 | NS_IMETHODIMP \ |
michael@0 | 133 | nsXMLHttpRequest::_name(nsACString& aOut) \ |
michael@0 | 134 | { \ |
michael@0 | 135 | nsCString tmp; \ |
michael@0 | 136 | _name(tmp); \ |
michael@0 | 137 | aOut = tmp; \ |
michael@0 | 138 | return NS_OK; \ |
michael@0 | 139 | } |
michael@0 | 140 | |
michael@0 | 141 | NS_IMPL_ISUPPORTS(nsXHRParseEndListener, nsIDOMEventListener) |
michael@0 | 142 | |
michael@0 | 143 | class nsResumeTimeoutsEvent : public nsRunnable |
michael@0 | 144 | { |
michael@0 | 145 | public: |
michael@0 | 146 | nsResumeTimeoutsEvent(nsPIDOMWindow* aWindow) : mWindow(aWindow) {} |
michael@0 | 147 | |
michael@0 | 148 | NS_IMETHOD Run() |
michael@0 | 149 | { |
michael@0 | 150 | mWindow->ResumeTimeouts(false); |
michael@0 | 151 | return NS_OK; |
michael@0 | 152 | } |
michael@0 | 153 | |
michael@0 | 154 | private: |
michael@0 | 155 | nsCOMPtr<nsPIDOMWindow> mWindow; |
michael@0 | 156 | }; |
michael@0 | 157 | |
michael@0 | 158 | |
michael@0 | 159 | // This helper function adds the given load flags to the request's existing |
michael@0 | 160 | // load flags. |
michael@0 | 161 | static void AddLoadFlags(nsIRequest *request, nsLoadFlags newFlags) |
michael@0 | 162 | { |
michael@0 | 163 | nsLoadFlags flags; |
michael@0 | 164 | request->GetLoadFlags(&flags); |
michael@0 | 165 | flags |= newFlags; |
michael@0 | 166 | request->SetLoadFlags(flags); |
michael@0 | 167 | } |
michael@0 | 168 | |
michael@0 | 169 | //----------------------------------------------------------------------------- |
michael@0 | 170 | // XMLHttpRequestAuthPrompt |
michael@0 | 171 | //----------------------------------------------------------------------------- |
michael@0 | 172 | |
michael@0 | 173 | class XMLHttpRequestAuthPrompt : public nsIAuthPrompt |
michael@0 | 174 | { |
michael@0 | 175 | public: |
michael@0 | 176 | NS_DECL_ISUPPORTS |
michael@0 | 177 | NS_DECL_NSIAUTHPROMPT |
michael@0 | 178 | |
michael@0 | 179 | XMLHttpRequestAuthPrompt(); |
michael@0 | 180 | virtual ~XMLHttpRequestAuthPrompt(); |
michael@0 | 181 | }; |
michael@0 | 182 | |
michael@0 | 183 | NS_IMPL_ISUPPORTS(XMLHttpRequestAuthPrompt, nsIAuthPrompt) |
michael@0 | 184 | |
michael@0 | 185 | XMLHttpRequestAuthPrompt::XMLHttpRequestAuthPrompt() |
michael@0 | 186 | { |
michael@0 | 187 | MOZ_COUNT_CTOR(XMLHttpRequestAuthPrompt); |
michael@0 | 188 | } |
michael@0 | 189 | |
michael@0 | 190 | XMLHttpRequestAuthPrompt::~XMLHttpRequestAuthPrompt() |
michael@0 | 191 | { |
michael@0 | 192 | MOZ_COUNT_DTOR(XMLHttpRequestAuthPrompt); |
michael@0 | 193 | } |
michael@0 | 194 | |
michael@0 | 195 | NS_IMETHODIMP |
michael@0 | 196 | XMLHttpRequestAuthPrompt::Prompt(const char16_t* aDialogTitle, |
michael@0 | 197 | const char16_t* aText, |
michael@0 | 198 | const char16_t* aPasswordRealm, |
michael@0 | 199 | uint32_t aSavePassword, |
michael@0 | 200 | const char16_t* aDefaultText, |
michael@0 | 201 | char16_t** aResult, |
michael@0 | 202 | bool* aRetval) |
michael@0 | 203 | { |
michael@0 | 204 | *aRetval = false; |
michael@0 | 205 | return NS_OK; |
michael@0 | 206 | } |
michael@0 | 207 | |
michael@0 | 208 | NS_IMETHODIMP |
michael@0 | 209 | XMLHttpRequestAuthPrompt::PromptUsernameAndPassword(const char16_t* aDialogTitle, |
michael@0 | 210 | const char16_t* aDialogText, |
michael@0 | 211 | const char16_t* aPasswordRealm, |
michael@0 | 212 | uint32_t aSavePassword, |
michael@0 | 213 | char16_t** aUser, |
michael@0 | 214 | char16_t** aPwd, |
michael@0 | 215 | bool* aRetval) |
michael@0 | 216 | { |
michael@0 | 217 | *aRetval = false; |
michael@0 | 218 | return NS_OK; |
michael@0 | 219 | } |
michael@0 | 220 | |
michael@0 | 221 | NS_IMETHODIMP |
michael@0 | 222 | XMLHttpRequestAuthPrompt::PromptPassword(const char16_t* aDialogTitle, |
michael@0 | 223 | const char16_t* aText, |
michael@0 | 224 | const char16_t* aPasswordRealm, |
michael@0 | 225 | uint32_t aSavePassword, |
michael@0 | 226 | char16_t** aPwd, |
michael@0 | 227 | bool* aRetval) |
michael@0 | 228 | { |
michael@0 | 229 | *aRetval = false; |
michael@0 | 230 | return NS_OK; |
michael@0 | 231 | } |
michael@0 | 232 | |
michael@0 | 233 | ///////////////////////////////////////////// |
michael@0 | 234 | |
michael@0 | 235 | NS_IMPL_CYCLE_COLLECTION_CLASS(nsXHREventTarget) |
michael@0 | 236 | |
michael@0 | 237 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsXHREventTarget, |
michael@0 | 238 | DOMEventTargetHelper) |
michael@0 | 239 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
michael@0 | 240 | |
michael@0 | 241 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsXHREventTarget, |
michael@0 | 242 | DOMEventTargetHelper) |
michael@0 | 243 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
michael@0 | 244 | |
michael@0 | 245 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsXHREventTarget) |
michael@0 | 246 | NS_INTERFACE_MAP_ENTRY(nsIXMLHttpRequestEventTarget) |
michael@0 | 247 | NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) |
michael@0 | 248 | |
michael@0 | 249 | NS_IMPL_ADDREF_INHERITED(nsXHREventTarget, DOMEventTargetHelper) |
michael@0 | 250 | NS_IMPL_RELEASE_INHERITED(nsXHREventTarget, DOMEventTargetHelper) |
michael@0 | 251 | |
michael@0 | 252 | void |
michael@0 | 253 | nsXHREventTarget::DisconnectFromOwner() |
michael@0 | 254 | { |
michael@0 | 255 | DOMEventTargetHelper::DisconnectFromOwner(); |
michael@0 | 256 | } |
michael@0 | 257 | |
michael@0 | 258 | ///////////////////////////////////////////// |
michael@0 | 259 | |
michael@0 | 260 | NS_INTERFACE_MAP_BEGIN(nsXMLHttpRequestUpload) |
michael@0 | 261 | NS_INTERFACE_MAP_ENTRY(nsIXMLHttpRequestUpload) |
michael@0 | 262 | NS_INTERFACE_MAP_END_INHERITING(nsXHREventTarget) |
michael@0 | 263 | |
michael@0 | 264 | NS_IMPL_ADDREF_INHERITED(nsXMLHttpRequestUpload, nsXHREventTarget) |
michael@0 | 265 | NS_IMPL_RELEASE_INHERITED(nsXMLHttpRequestUpload, nsXHREventTarget) |
michael@0 | 266 | |
michael@0 | 267 | /* virtual */ JSObject* |
michael@0 | 268 | nsXMLHttpRequestUpload::WrapObject(JSContext* aCx) |
michael@0 | 269 | { |
michael@0 | 270 | return XMLHttpRequestUploadBinding::Wrap(aCx, this); |
michael@0 | 271 | } |
michael@0 | 272 | |
michael@0 | 273 | ///////////////////////////////////////////// |
michael@0 | 274 | // |
michael@0 | 275 | // |
michael@0 | 276 | ///////////////////////////////////////////// |
michael@0 | 277 | |
michael@0 | 278 | bool |
michael@0 | 279 | nsXMLHttpRequest::sDontWarnAboutSyncXHR = false; |
michael@0 | 280 | |
michael@0 | 281 | nsXMLHttpRequest::nsXMLHttpRequest() |
michael@0 | 282 | : mResponseBodyDecodedPos(0), |
michael@0 | 283 | mResponseType(XML_HTTP_RESPONSE_TYPE_DEFAULT), |
michael@0 | 284 | mRequestObserver(nullptr), mState(XML_HTTP_REQUEST_UNSENT), |
michael@0 | 285 | mUploadTransferred(0), mUploadTotal(0), mUploadComplete(true), |
michael@0 | 286 | mProgressSinceLastProgressEvent(false), |
michael@0 | 287 | mRequestSentTime(0), mTimeoutMilliseconds(0), |
michael@0 | 288 | mErrorLoad(false), mWaitingForOnStopRequest(false), |
michael@0 | 289 | mProgressTimerIsActive(false), |
michael@0 | 290 | mIsHtml(false), |
michael@0 | 291 | mWarnAboutSyncHtml(false), |
michael@0 | 292 | mLoadLengthComputable(false), mLoadTotal(0), |
michael@0 | 293 | mIsSystem(false), |
michael@0 | 294 | mIsAnon(false), |
michael@0 | 295 | mFirstStartRequestSeen(false), |
michael@0 | 296 | mInLoadProgressEvent(false), |
michael@0 | 297 | mResultJSON(JSVAL_VOID), |
michael@0 | 298 | mResultArrayBuffer(nullptr), |
michael@0 | 299 | mXPCOMifier(nullptr) |
michael@0 | 300 | { |
michael@0 | 301 | SetIsDOMBinding(); |
michael@0 | 302 | #ifdef DEBUG |
michael@0 | 303 | StaticAssertions(); |
michael@0 | 304 | #endif |
michael@0 | 305 | } |
michael@0 | 306 | |
michael@0 | 307 | nsXMLHttpRequest::~nsXMLHttpRequest() |
michael@0 | 308 | { |
michael@0 | 309 | mState |= XML_HTTP_REQUEST_DELETED; |
michael@0 | 310 | |
michael@0 | 311 | if (mState & (XML_HTTP_REQUEST_SENT | |
michael@0 | 312 | XML_HTTP_REQUEST_LOADING)) { |
michael@0 | 313 | Abort(); |
michael@0 | 314 | } |
michael@0 | 315 | |
michael@0 | 316 | NS_ABORT_IF_FALSE(!(mState & XML_HTTP_REQUEST_SYNCLOOPING), "we rather crash than hang"); |
michael@0 | 317 | mState &= ~XML_HTTP_REQUEST_SYNCLOOPING; |
michael@0 | 318 | |
michael@0 | 319 | mResultJSON = JSVAL_VOID; |
michael@0 | 320 | mResultArrayBuffer = nullptr; |
michael@0 | 321 | mozilla::DropJSObjects(this); |
michael@0 | 322 | } |
michael@0 | 323 | |
michael@0 | 324 | void |
michael@0 | 325 | nsXMLHttpRequest::RootJSResultObjects() |
michael@0 | 326 | { |
michael@0 | 327 | mozilla::HoldJSObjects(this); |
michael@0 | 328 | } |
michael@0 | 329 | |
michael@0 | 330 | /** |
michael@0 | 331 | * This Init method is called from the factory constructor. |
michael@0 | 332 | */ |
michael@0 | 333 | nsresult |
michael@0 | 334 | nsXMLHttpRequest::Init() |
michael@0 | 335 | { |
michael@0 | 336 | nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); |
michael@0 | 337 | nsCOMPtr<nsIPrincipal> subjectPrincipal; |
michael@0 | 338 | if (secMan) { |
michael@0 | 339 | secMan->GetSystemPrincipal(getter_AddRefs(subjectPrincipal)); |
michael@0 | 340 | } |
michael@0 | 341 | NS_ENSURE_STATE(subjectPrincipal); |
michael@0 | 342 | |
michael@0 | 343 | // Instead of grabbing some random global from the context stack, |
michael@0 | 344 | // let's use the default one (junk scope) for now. |
michael@0 | 345 | // We should move away from this Init... |
michael@0 | 346 | nsCOMPtr<nsIGlobalObject> global = xpc::GetJunkScopeGlobal(); |
michael@0 | 347 | NS_ENSURE_TRUE(global, NS_ERROR_FAILURE); |
michael@0 | 348 | Construct(subjectPrincipal, global); |
michael@0 | 349 | return NS_OK; |
michael@0 | 350 | } |
michael@0 | 351 | |
michael@0 | 352 | /** |
michael@0 | 353 | * This Init method should only be called by C++ consumers. |
michael@0 | 354 | */ |
michael@0 | 355 | NS_IMETHODIMP |
michael@0 | 356 | nsXMLHttpRequest::Init(nsIPrincipal* aPrincipal, |
michael@0 | 357 | nsIScriptContext* aScriptContext, |
michael@0 | 358 | nsIGlobalObject* aGlobalObject, |
michael@0 | 359 | nsIURI* aBaseURI) |
michael@0 | 360 | { |
michael@0 | 361 | NS_ENSURE_ARG_POINTER(aPrincipal); |
michael@0 | 362 | |
michael@0 | 363 | if (nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aGlobalObject)) { |
michael@0 | 364 | if (win->IsOuterWindow()) { |
michael@0 | 365 | // Must be bound to inner window, innerize if necessary. |
michael@0 | 366 | nsCOMPtr<nsIGlobalObject> inner = do_QueryInterface( |
michael@0 | 367 | win->GetCurrentInnerWindow()); |
michael@0 | 368 | aGlobalObject = inner.get(); |
michael@0 | 369 | } |
michael@0 | 370 | } |
michael@0 | 371 | |
michael@0 | 372 | Construct(aPrincipal, aGlobalObject, aBaseURI); |
michael@0 | 373 | return NS_OK; |
michael@0 | 374 | } |
michael@0 | 375 | |
michael@0 | 376 | void |
michael@0 | 377 | nsXMLHttpRequest::InitParameters(bool aAnon, bool aSystem) |
michael@0 | 378 | { |
michael@0 | 379 | if (!aAnon && !aSystem) { |
michael@0 | 380 | return; |
michael@0 | 381 | } |
michael@0 | 382 | |
michael@0 | 383 | // Check for permissions. |
michael@0 | 384 | nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(GetOwner()); |
michael@0 | 385 | if (!window || !window->GetDocShell()) { |
michael@0 | 386 | return; |
michael@0 | 387 | } |
michael@0 | 388 | |
michael@0 | 389 | // Chrome is always allowed access, so do the permission check only |
michael@0 | 390 | // for non-chrome pages. |
michael@0 | 391 | if (!IsSystemXHR() && aSystem) { |
michael@0 | 392 | nsCOMPtr<nsIDocument> doc = window->GetExtantDoc(); |
michael@0 | 393 | if (!doc) { |
michael@0 | 394 | return; |
michael@0 | 395 | } |
michael@0 | 396 | |
michael@0 | 397 | nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal(); |
michael@0 | 398 | nsCOMPtr<nsIPermissionManager> permMgr = |
michael@0 | 399 | do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); |
michael@0 | 400 | if (!permMgr) |
michael@0 | 401 | return; |
michael@0 | 402 | |
michael@0 | 403 | uint32_t permission; |
michael@0 | 404 | nsresult rv = |
michael@0 | 405 | permMgr->TestPermissionFromPrincipal(principal, "systemXHR", &permission); |
michael@0 | 406 | if (NS_FAILED(rv) || permission != nsIPermissionManager::ALLOW_ACTION) { |
michael@0 | 407 | return; |
michael@0 | 408 | } |
michael@0 | 409 | } |
michael@0 | 410 | |
michael@0 | 411 | SetParameters(aAnon, aSystem); |
michael@0 | 412 | } |
michael@0 | 413 | |
michael@0 | 414 | void |
michael@0 | 415 | nsXMLHttpRequest::ResetResponse() |
michael@0 | 416 | { |
michael@0 | 417 | mResponseXML = nullptr; |
michael@0 | 418 | mResponseBody.Truncate(); |
michael@0 | 419 | mResponseText.Truncate(); |
michael@0 | 420 | mResponseBlob = nullptr; |
michael@0 | 421 | mDOMFile = nullptr; |
michael@0 | 422 | mBlobSet = nullptr; |
michael@0 | 423 | mResultArrayBuffer = nullptr; |
michael@0 | 424 | mArrayBufferBuilder.reset(); |
michael@0 | 425 | mResultJSON = JSVAL_VOID; |
michael@0 | 426 | mLoadTransferred = 0; |
michael@0 | 427 | mResponseBodyDecodedPos = 0; |
michael@0 | 428 | } |
michael@0 | 429 | |
michael@0 | 430 | void |
michael@0 | 431 | nsXMLHttpRequest::SetRequestObserver(nsIRequestObserver* aObserver) |
michael@0 | 432 | { |
michael@0 | 433 | mRequestObserver = aObserver; |
michael@0 | 434 | } |
michael@0 | 435 | |
michael@0 | 436 | NS_IMPL_CYCLE_COLLECTION_CLASS(nsXMLHttpRequest) |
michael@0 | 437 | |
michael@0 | 438 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsXMLHttpRequest) |
michael@0 | 439 | bool isBlack = tmp->IsBlack(); |
michael@0 | 440 | if (isBlack || tmp->mWaitingForOnStopRequest) { |
michael@0 | 441 | if (tmp->mListenerManager) { |
michael@0 | 442 | tmp->mListenerManager->MarkForCC(); |
michael@0 | 443 | } |
michael@0 | 444 | if (!isBlack && tmp->PreservingWrapper()) { |
michael@0 | 445 | // This marks the wrapper black. |
michael@0 | 446 | tmp->GetWrapper(); |
michael@0 | 447 | } |
michael@0 | 448 | return true; |
michael@0 | 449 | } |
michael@0 | 450 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END |
michael@0 | 451 | |
michael@0 | 452 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsXMLHttpRequest) |
michael@0 | 453 | return tmp-> |
michael@0 | 454 | IsBlackAndDoesNotNeedTracing(static_cast<DOMEventTargetHelper*>(tmp)); |
michael@0 | 455 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END |
michael@0 | 456 | |
michael@0 | 457 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsXMLHttpRequest) |
michael@0 | 458 | return tmp->IsBlack(); |
michael@0 | 459 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END |
michael@0 | 460 | |
michael@0 | 461 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsXMLHttpRequest, |
michael@0 | 462 | nsXHREventTarget) |
michael@0 | 463 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext) |
michael@0 | 464 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel) |
michael@0 | 465 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseXML) |
michael@0 | 466 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCORSPreflightChannel) |
michael@0 | 467 | |
michael@0 | 468 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXMLParserStreamListener) |
michael@0 | 469 | |
michael@0 | 470 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannelEventSink) |
michael@0 | 471 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressEventSink) |
michael@0 | 472 | |
michael@0 | 473 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload) |
michael@0 | 474 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
michael@0 | 475 | |
michael@0 | 476 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsXMLHttpRequest, |
michael@0 | 477 | nsXHREventTarget) |
michael@0 | 478 | tmp->mResultArrayBuffer = nullptr; |
michael@0 | 479 | tmp->mArrayBufferBuilder.reset(); |
michael@0 | 480 | tmp->mResultJSON = JSVAL_VOID; |
michael@0 | 481 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext) |
michael@0 | 482 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel) |
michael@0 | 483 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseXML) |
michael@0 | 484 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mCORSPreflightChannel) |
michael@0 | 485 | |
michael@0 | 486 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mXMLParserStreamListener) |
michael@0 | 487 | |
michael@0 | 488 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannelEventSink) |
michael@0 | 489 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressEventSink) |
michael@0 | 490 | |
michael@0 | 491 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload) |
michael@0 | 492 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
michael@0 | 493 | |
michael@0 | 494 | NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsXMLHttpRequest, |
michael@0 | 495 | nsXHREventTarget) |
michael@0 | 496 | NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer) |
michael@0 | 497 | NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mResultJSON) |
michael@0 | 498 | NS_IMPL_CYCLE_COLLECTION_TRACE_END |
michael@0 | 499 | |
michael@0 | 500 | // QueryInterface implementation for nsXMLHttpRequest |
michael@0 | 501 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsXMLHttpRequest) |
michael@0 | 502 | NS_INTERFACE_MAP_ENTRY(nsIXMLHttpRequest) |
michael@0 | 503 | NS_INTERFACE_MAP_ENTRY(nsIJSXMLHttpRequest) |
michael@0 | 504 | NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) |
michael@0 | 505 | NS_INTERFACE_MAP_ENTRY(nsIStreamListener) |
michael@0 | 506 | NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink) |
michael@0 | 507 | NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink) |
michael@0 | 508 | NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) |
michael@0 | 509 | NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) |
michael@0 | 510 | NS_INTERFACE_MAP_ENTRY(nsITimerCallback) |
michael@0 | 511 | NS_INTERFACE_MAP_ENTRY(nsISizeOfEventTarget) |
michael@0 | 512 | NS_INTERFACE_MAP_END_INHERITING(nsXHREventTarget) |
michael@0 | 513 | |
michael@0 | 514 | NS_IMPL_ADDREF_INHERITED(nsXMLHttpRequest, nsXHREventTarget) |
michael@0 | 515 | NS_IMPL_RELEASE_INHERITED(nsXMLHttpRequest, nsXHREventTarget) |
michael@0 | 516 | |
michael@0 | 517 | NS_IMPL_EVENT_HANDLER(nsXMLHttpRequest, readystatechange) |
michael@0 | 518 | |
michael@0 | 519 | void |
michael@0 | 520 | nsXMLHttpRequest::DisconnectFromOwner() |
michael@0 | 521 | { |
michael@0 | 522 | nsXHREventTarget::DisconnectFromOwner(); |
michael@0 | 523 | Abort(); |
michael@0 | 524 | } |
michael@0 | 525 | |
michael@0 | 526 | size_t |
michael@0 | 527 | nsXMLHttpRequest::SizeOfEventTargetIncludingThis( |
michael@0 | 528 | MallocSizeOf aMallocSizeOf) const |
michael@0 | 529 | { |
michael@0 | 530 | size_t n = aMallocSizeOf(this); |
michael@0 | 531 | n += mResponseBody.SizeOfExcludingThisIfUnshared(aMallocSizeOf); |
michael@0 | 532 | |
michael@0 | 533 | // Why is this safe? Because no-one else will report this string. The |
michael@0 | 534 | // other possible sharers of this string are as follows. |
michael@0 | 535 | // |
michael@0 | 536 | // - The JS engine could hold copies if the JS code holds references, e.g. |
michael@0 | 537 | // |var text = XHR.responseText|. However, those references will be via JS |
michael@0 | 538 | // external strings, for which the JS memory reporter does *not* report the |
michael@0 | 539 | // chars. |
michael@0 | 540 | // |
michael@0 | 541 | // - Binary extensions, but they're *extremely* unlikely to do any memory |
michael@0 | 542 | // reporting. |
michael@0 | 543 | // |
michael@0 | 544 | n += mResponseText.SizeOfExcludingThisEvenIfShared(aMallocSizeOf); |
michael@0 | 545 | |
michael@0 | 546 | return n; |
michael@0 | 547 | |
michael@0 | 548 | // Measurement of the following members may be added later if DMD finds it is |
michael@0 | 549 | // worthwhile: |
michael@0 | 550 | // - lots |
michael@0 | 551 | } |
michael@0 | 552 | |
michael@0 | 553 | /* readonly attribute nsIChannel channel; */ |
michael@0 | 554 | NS_IMETHODIMP |
michael@0 | 555 | nsXMLHttpRequest::GetChannel(nsIChannel **aChannel) |
michael@0 | 556 | { |
michael@0 | 557 | NS_ENSURE_ARG_POINTER(aChannel); |
michael@0 | 558 | NS_IF_ADDREF(*aChannel = mChannel); |
michael@0 | 559 | |
michael@0 | 560 | return NS_OK; |
michael@0 | 561 | } |
michael@0 | 562 | |
michael@0 | 563 | static void LogMessage(const char* aWarning, nsPIDOMWindow* aWindow) |
michael@0 | 564 | { |
michael@0 | 565 | nsCOMPtr<nsIDocument> doc; |
michael@0 | 566 | if (aWindow) { |
michael@0 | 567 | doc = aWindow->GetExtantDoc(); |
michael@0 | 568 | } |
michael@0 | 569 | nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, |
michael@0 | 570 | NS_LITERAL_CSTRING("DOM"), doc, |
michael@0 | 571 | nsContentUtils::eDOM_PROPERTIES, |
michael@0 | 572 | aWarning); |
michael@0 | 573 | } |
michael@0 | 574 | |
michael@0 | 575 | /* readonly attribute nsIDOMDocument responseXML; */ |
michael@0 | 576 | NS_IMETHODIMP |
michael@0 | 577 | nsXMLHttpRequest::GetResponseXML(nsIDOMDocument **aResponseXML) |
michael@0 | 578 | { |
michael@0 | 579 | ErrorResult rv; |
michael@0 | 580 | nsIDocument* responseXML = GetResponseXML(rv); |
michael@0 | 581 | if (rv.Failed()) { |
michael@0 | 582 | return rv.ErrorCode(); |
michael@0 | 583 | } |
michael@0 | 584 | |
michael@0 | 585 | if (!responseXML) { |
michael@0 | 586 | *aResponseXML = nullptr; |
michael@0 | 587 | return NS_OK; |
michael@0 | 588 | } |
michael@0 | 589 | |
michael@0 | 590 | return CallQueryInterface(responseXML, aResponseXML); |
michael@0 | 591 | } |
michael@0 | 592 | |
michael@0 | 593 | nsIDocument* |
michael@0 | 594 | nsXMLHttpRequest::GetResponseXML(ErrorResult& aRv) |
michael@0 | 595 | { |
michael@0 | 596 | if (mResponseType != XML_HTTP_RESPONSE_TYPE_DEFAULT && |
michael@0 | 597 | mResponseType != XML_HTTP_RESPONSE_TYPE_DOCUMENT) { |
michael@0 | 598 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
michael@0 | 599 | return nullptr; |
michael@0 | 600 | } |
michael@0 | 601 | if (mWarnAboutSyncHtml) { |
michael@0 | 602 | mWarnAboutSyncHtml = false; |
michael@0 | 603 | LogMessage("HTMLSyncXHRWarning", GetOwner()); |
michael@0 | 604 | } |
michael@0 | 605 | return (XML_HTTP_REQUEST_DONE & mState) ? mResponseXML : nullptr; |
michael@0 | 606 | } |
michael@0 | 607 | |
michael@0 | 608 | /* |
michael@0 | 609 | * This piece copied from XMLDocument, we try to get the charset |
michael@0 | 610 | * from HTTP headers. |
michael@0 | 611 | */ |
michael@0 | 612 | nsresult |
michael@0 | 613 | nsXMLHttpRequest::DetectCharset() |
michael@0 | 614 | { |
michael@0 | 615 | mResponseCharset.Truncate(); |
michael@0 | 616 | mDecoder = nullptr; |
michael@0 | 617 | |
michael@0 | 618 | if (mResponseType != XML_HTTP_RESPONSE_TYPE_DEFAULT && |
michael@0 | 619 | mResponseType != XML_HTTP_RESPONSE_TYPE_TEXT && |
michael@0 | 620 | mResponseType != XML_HTTP_RESPONSE_TYPE_JSON && |
michael@0 | 621 | mResponseType != XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT) { |
michael@0 | 622 | return NS_OK; |
michael@0 | 623 | } |
michael@0 | 624 | |
michael@0 | 625 | nsAutoCString charsetVal; |
michael@0 | 626 | bool ok = mChannel && |
michael@0 | 627 | NS_SUCCEEDED(mChannel->GetContentCharset(charsetVal)) && |
michael@0 | 628 | EncodingUtils::FindEncodingForLabel(charsetVal, mResponseCharset); |
michael@0 | 629 | if (!ok || mResponseCharset.IsEmpty()) { |
michael@0 | 630 | // MS documentation states UTF-8 is default for responseText |
michael@0 | 631 | mResponseCharset.AssignLiteral("UTF-8"); |
michael@0 | 632 | } |
michael@0 | 633 | |
michael@0 | 634 | if (mResponseType == XML_HTTP_RESPONSE_TYPE_JSON && |
michael@0 | 635 | !mResponseCharset.EqualsLiteral("UTF-8")) { |
michael@0 | 636 | // The XHR spec says only UTF-8 is supported for responseType == "json" |
michael@0 | 637 | LogMessage("JSONCharsetWarning", GetOwner()); |
michael@0 | 638 | mResponseCharset.AssignLiteral("UTF-8"); |
michael@0 | 639 | } |
michael@0 | 640 | |
michael@0 | 641 | mDecoder = EncodingUtils::DecoderForEncoding(mResponseCharset); |
michael@0 | 642 | |
michael@0 | 643 | return NS_OK; |
michael@0 | 644 | } |
michael@0 | 645 | |
michael@0 | 646 | nsresult |
michael@0 | 647 | nsXMLHttpRequest::AppendToResponseText(const char * aSrcBuffer, |
michael@0 | 648 | uint32_t aSrcBufferLen) |
michael@0 | 649 | { |
michael@0 | 650 | NS_ENSURE_STATE(mDecoder); |
michael@0 | 651 | |
michael@0 | 652 | int32_t destBufferLen; |
michael@0 | 653 | nsresult rv = mDecoder->GetMaxLength(aSrcBuffer, aSrcBufferLen, |
michael@0 | 654 | &destBufferLen); |
michael@0 | 655 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 656 | |
michael@0 | 657 | if (!mResponseText.SetCapacity(mResponseText.Length() + destBufferLen, fallible_t())) { |
michael@0 | 658 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 659 | } |
michael@0 | 660 | |
michael@0 | 661 | char16_t* destBuffer = mResponseText.BeginWriting() + mResponseText.Length(); |
michael@0 | 662 | |
michael@0 | 663 | int32_t totalChars = mResponseText.Length(); |
michael@0 | 664 | |
michael@0 | 665 | // This code here is basically a copy of a similar thing in |
michael@0 | 666 | // nsScanner::Append(const char* aBuffer, uint32_t aLen). |
michael@0 | 667 | int32_t srclen = (int32_t)aSrcBufferLen; |
michael@0 | 668 | int32_t destlen = (int32_t)destBufferLen; |
michael@0 | 669 | rv = mDecoder->Convert(aSrcBuffer, |
michael@0 | 670 | &srclen, |
michael@0 | 671 | destBuffer, |
michael@0 | 672 | &destlen); |
michael@0 | 673 | MOZ_ASSERT(NS_SUCCEEDED(rv)); |
michael@0 | 674 | |
michael@0 | 675 | totalChars += destlen; |
michael@0 | 676 | |
michael@0 | 677 | mResponseText.SetLength(totalChars); |
michael@0 | 678 | |
michael@0 | 679 | return NS_OK; |
michael@0 | 680 | } |
michael@0 | 681 | |
michael@0 | 682 | /* readonly attribute AString responseText; */ |
michael@0 | 683 | NS_IMETHODIMP |
michael@0 | 684 | nsXMLHttpRequest::GetResponseText(nsAString& aResponseText) |
michael@0 | 685 | { |
michael@0 | 686 | ErrorResult rv; |
michael@0 | 687 | nsString responseText; |
michael@0 | 688 | GetResponseText(responseText, rv); |
michael@0 | 689 | aResponseText = responseText; |
michael@0 | 690 | return rv.ErrorCode(); |
michael@0 | 691 | } |
michael@0 | 692 | |
michael@0 | 693 | void |
michael@0 | 694 | nsXMLHttpRequest::GetResponseText(nsString& aResponseText, ErrorResult& aRv) |
michael@0 | 695 | { |
michael@0 | 696 | aResponseText.Truncate(); |
michael@0 | 697 | |
michael@0 | 698 | if (mResponseType != XML_HTTP_RESPONSE_TYPE_DEFAULT && |
michael@0 | 699 | mResponseType != XML_HTTP_RESPONSE_TYPE_TEXT && |
michael@0 | 700 | mResponseType != XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT) { |
michael@0 | 701 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
michael@0 | 702 | return; |
michael@0 | 703 | } |
michael@0 | 704 | |
michael@0 | 705 | if (mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT && |
michael@0 | 706 | !mInLoadProgressEvent) { |
michael@0 | 707 | aResponseText.SetIsVoid(true); |
michael@0 | 708 | return; |
michael@0 | 709 | } |
michael@0 | 710 | |
michael@0 | 711 | if (!(mState & (XML_HTTP_REQUEST_DONE | XML_HTTP_REQUEST_LOADING))) { |
michael@0 | 712 | return; |
michael@0 | 713 | } |
michael@0 | 714 | |
michael@0 | 715 | // We only decode text lazily if we're also parsing to a doc. |
michael@0 | 716 | // Also, if we've decoded all current data already, then no need to decode |
michael@0 | 717 | // more. |
michael@0 | 718 | if (!mResponseXML || |
michael@0 | 719 | mResponseBodyDecodedPos == mResponseBody.Length()) { |
michael@0 | 720 | aResponseText = mResponseText; |
michael@0 | 721 | return; |
michael@0 | 722 | } |
michael@0 | 723 | |
michael@0 | 724 | if (mResponseCharset != mResponseXML->GetDocumentCharacterSet()) { |
michael@0 | 725 | mResponseCharset = mResponseXML->GetDocumentCharacterSet(); |
michael@0 | 726 | mResponseText.Truncate(); |
michael@0 | 727 | mResponseBodyDecodedPos = 0; |
michael@0 | 728 | mDecoder = EncodingUtils::DecoderForEncoding(mResponseCharset); |
michael@0 | 729 | } |
michael@0 | 730 | |
michael@0 | 731 | NS_ASSERTION(mResponseBodyDecodedPos < mResponseBody.Length(), |
michael@0 | 732 | "Unexpected mResponseBodyDecodedPos"); |
michael@0 | 733 | aRv = AppendToResponseText(mResponseBody.get() + mResponseBodyDecodedPos, |
michael@0 | 734 | mResponseBody.Length() - mResponseBodyDecodedPos); |
michael@0 | 735 | if (aRv.Failed()) { |
michael@0 | 736 | return; |
michael@0 | 737 | } |
michael@0 | 738 | |
michael@0 | 739 | mResponseBodyDecodedPos = mResponseBody.Length(); |
michael@0 | 740 | |
michael@0 | 741 | if (mState & XML_HTTP_REQUEST_DONE) { |
michael@0 | 742 | // Free memory buffer which we no longer need |
michael@0 | 743 | mResponseBody.Truncate(); |
michael@0 | 744 | mResponseBodyDecodedPos = 0; |
michael@0 | 745 | } |
michael@0 | 746 | |
michael@0 | 747 | aResponseText = mResponseText; |
michael@0 | 748 | } |
michael@0 | 749 | |
michael@0 | 750 | nsresult |
michael@0 | 751 | nsXMLHttpRequest::CreateResponseParsedJSON(JSContext* aCx) |
michael@0 | 752 | { |
michael@0 | 753 | if (!aCx) { |
michael@0 | 754 | return NS_ERROR_FAILURE; |
michael@0 | 755 | } |
michael@0 | 756 | RootJSResultObjects(); |
michael@0 | 757 | |
michael@0 | 758 | // The Unicode converter has already zapped the BOM if there was one |
michael@0 | 759 | JS::Rooted<JS::Value> value(aCx); |
michael@0 | 760 | if (!JS_ParseJSON(aCx, |
michael@0 | 761 | static_cast<const jschar*>(mResponseText.get()), mResponseText.Length(), |
michael@0 | 762 | &value)) { |
michael@0 | 763 | return NS_ERROR_FAILURE; |
michael@0 | 764 | } |
michael@0 | 765 | |
michael@0 | 766 | mResultJSON = value; |
michael@0 | 767 | return NS_OK; |
michael@0 | 768 | } |
michael@0 | 769 | |
michael@0 | 770 | void |
michael@0 | 771 | nsXMLHttpRequest::CreatePartialBlob() |
michael@0 | 772 | { |
michael@0 | 773 | if (mDOMFile) { |
michael@0 | 774 | if (mLoadTotal == mLoadTransferred) { |
michael@0 | 775 | mResponseBlob = mDOMFile; |
michael@0 | 776 | } else { |
michael@0 | 777 | mResponseBlob = |
michael@0 | 778 | mDOMFile->CreateSlice(0, mLoadTransferred, EmptyString()); |
michael@0 | 779 | } |
michael@0 | 780 | return; |
michael@0 | 781 | } |
michael@0 | 782 | |
michael@0 | 783 | // mBlobSet can be null if the request has been canceled |
michael@0 | 784 | if (!mBlobSet) { |
michael@0 | 785 | return; |
michael@0 | 786 | } |
michael@0 | 787 | |
michael@0 | 788 | nsAutoCString contentType; |
michael@0 | 789 | if (mLoadTotal == mLoadTransferred) { |
michael@0 | 790 | mChannel->GetContentType(contentType); |
michael@0 | 791 | } |
michael@0 | 792 | |
michael@0 | 793 | mResponseBlob = mBlobSet->GetBlobInternal(contentType); |
michael@0 | 794 | } |
michael@0 | 795 | |
michael@0 | 796 | /* attribute AString responseType; */ |
michael@0 | 797 | NS_IMETHODIMP nsXMLHttpRequest::GetResponseType(nsAString& aResponseType) |
michael@0 | 798 | { |
michael@0 | 799 | switch (mResponseType) { |
michael@0 | 800 | case XML_HTTP_RESPONSE_TYPE_DEFAULT: |
michael@0 | 801 | aResponseType.Truncate(); |
michael@0 | 802 | break; |
michael@0 | 803 | case XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER: |
michael@0 | 804 | aResponseType.AssignLiteral("arraybuffer"); |
michael@0 | 805 | break; |
michael@0 | 806 | case XML_HTTP_RESPONSE_TYPE_BLOB: |
michael@0 | 807 | aResponseType.AssignLiteral("blob"); |
michael@0 | 808 | break; |
michael@0 | 809 | case XML_HTTP_RESPONSE_TYPE_DOCUMENT: |
michael@0 | 810 | aResponseType.AssignLiteral("document"); |
michael@0 | 811 | break; |
michael@0 | 812 | case XML_HTTP_RESPONSE_TYPE_TEXT: |
michael@0 | 813 | aResponseType.AssignLiteral("text"); |
michael@0 | 814 | break; |
michael@0 | 815 | case XML_HTTP_RESPONSE_TYPE_JSON: |
michael@0 | 816 | aResponseType.AssignLiteral("json"); |
michael@0 | 817 | break; |
michael@0 | 818 | case XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT: |
michael@0 | 819 | aResponseType.AssignLiteral("moz-chunked-text"); |
michael@0 | 820 | break; |
michael@0 | 821 | case XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER: |
michael@0 | 822 | aResponseType.AssignLiteral("moz-chunked-arraybuffer"); |
michael@0 | 823 | break; |
michael@0 | 824 | case XML_HTTP_RESPONSE_TYPE_MOZ_BLOB: |
michael@0 | 825 | aResponseType.AssignLiteral("moz-blob"); |
michael@0 | 826 | break; |
michael@0 | 827 | default: |
michael@0 | 828 | NS_ERROR("Should not happen"); |
michael@0 | 829 | } |
michael@0 | 830 | |
michael@0 | 831 | return NS_OK; |
michael@0 | 832 | } |
michael@0 | 833 | |
michael@0 | 834 | #ifdef DEBUG |
michael@0 | 835 | void |
michael@0 | 836 | nsXMLHttpRequest::StaticAssertions() |
michael@0 | 837 | { |
michael@0 | 838 | #define ASSERT_ENUM_EQUAL(_lc, _uc) \ |
michael@0 | 839 | static_assert(\ |
michael@0 | 840 | static_cast<int>(XMLHttpRequestResponseType::_lc) \ |
michael@0 | 841 | == XML_HTTP_RESPONSE_TYPE_ ## _uc, \ |
michael@0 | 842 | #_uc " should match") |
michael@0 | 843 | |
michael@0 | 844 | ASSERT_ENUM_EQUAL(_empty, DEFAULT); |
michael@0 | 845 | ASSERT_ENUM_EQUAL(Arraybuffer, ARRAYBUFFER); |
michael@0 | 846 | ASSERT_ENUM_EQUAL(Blob, BLOB); |
michael@0 | 847 | ASSERT_ENUM_EQUAL(Document, DOCUMENT); |
michael@0 | 848 | ASSERT_ENUM_EQUAL(Json, JSON); |
michael@0 | 849 | ASSERT_ENUM_EQUAL(Text, TEXT); |
michael@0 | 850 | ASSERT_ENUM_EQUAL(Moz_chunked_text, CHUNKED_TEXT); |
michael@0 | 851 | ASSERT_ENUM_EQUAL(Moz_chunked_arraybuffer, CHUNKED_ARRAYBUFFER); |
michael@0 | 852 | ASSERT_ENUM_EQUAL(Moz_blob, MOZ_BLOB); |
michael@0 | 853 | #undef ASSERT_ENUM_EQUAL |
michael@0 | 854 | } |
michael@0 | 855 | #endif |
michael@0 | 856 | |
michael@0 | 857 | /* attribute AString responseType; */ |
michael@0 | 858 | NS_IMETHODIMP nsXMLHttpRequest::SetResponseType(const nsAString& aResponseType) |
michael@0 | 859 | { |
michael@0 | 860 | nsXMLHttpRequest::ResponseTypeEnum responseType; |
michael@0 | 861 | if (aResponseType.IsEmpty()) { |
michael@0 | 862 | responseType = XML_HTTP_RESPONSE_TYPE_DEFAULT; |
michael@0 | 863 | } else if (aResponseType.EqualsLiteral("arraybuffer")) { |
michael@0 | 864 | responseType = XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER; |
michael@0 | 865 | } else if (aResponseType.EqualsLiteral("blob")) { |
michael@0 | 866 | responseType = XML_HTTP_RESPONSE_TYPE_BLOB; |
michael@0 | 867 | } else if (aResponseType.EqualsLiteral("document")) { |
michael@0 | 868 | responseType = XML_HTTP_RESPONSE_TYPE_DOCUMENT; |
michael@0 | 869 | } else if (aResponseType.EqualsLiteral("text")) { |
michael@0 | 870 | responseType = XML_HTTP_RESPONSE_TYPE_TEXT; |
michael@0 | 871 | } else if (aResponseType.EqualsLiteral("json")) { |
michael@0 | 872 | responseType = XML_HTTP_RESPONSE_TYPE_JSON; |
michael@0 | 873 | } else if (aResponseType.EqualsLiteral("moz-chunked-text")) { |
michael@0 | 874 | responseType = XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT; |
michael@0 | 875 | } else if (aResponseType.EqualsLiteral("moz-chunked-arraybuffer")) { |
michael@0 | 876 | responseType = XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER; |
michael@0 | 877 | } else if (aResponseType.EqualsLiteral("moz-blob")) { |
michael@0 | 878 | responseType = XML_HTTP_RESPONSE_TYPE_MOZ_BLOB; |
michael@0 | 879 | } else { |
michael@0 | 880 | return NS_OK; |
michael@0 | 881 | } |
michael@0 | 882 | |
michael@0 | 883 | ErrorResult rv; |
michael@0 | 884 | SetResponseType(responseType, rv); |
michael@0 | 885 | return rv.ErrorCode(); |
michael@0 | 886 | } |
michael@0 | 887 | |
michael@0 | 888 | void |
michael@0 | 889 | nsXMLHttpRequest::SetResponseType(XMLHttpRequestResponseType aType, |
michael@0 | 890 | ErrorResult& aRv) |
michael@0 | 891 | { |
michael@0 | 892 | SetResponseType(ResponseTypeEnum(static_cast<int>(aType)), aRv); |
michael@0 | 893 | } |
michael@0 | 894 | |
michael@0 | 895 | void |
michael@0 | 896 | nsXMLHttpRequest::SetResponseType(nsXMLHttpRequest::ResponseTypeEnum aResponseType, |
michael@0 | 897 | ErrorResult& aRv) |
michael@0 | 898 | { |
michael@0 | 899 | // If the state is not OPENED or HEADERS_RECEIVED raise an |
michael@0 | 900 | // INVALID_STATE_ERR exception and terminate these steps. |
michael@0 | 901 | if (!(mState & (XML_HTTP_REQUEST_OPENED | XML_HTTP_REQUEST_SENT | |
michael@0 | 902 | XML_HTTP_REQUEST_HEADERS_RECEIVED))) { |
michael@0 | 903 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
michael@0 | 904 | return; |
michael@0 | 905 | } |
michael@0 | 906 | |
michael@0 | 907 | // sync request is not allowed setting responseType in window context |
michael@0 | 908 | if (HasOrHasHadOwner() && |
michael@0 | 909 | !(mState & (XML_HTTP_REQUEST_UNSENT | XML_HTTP_REQUEST_ASYNC))) { |
michael@0 | 910 | LogMessage("ResponseTypeSyncXHRWarning", GetOwner()); |
michael@0 | 911 | aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); |
michael@0 | 912 | return; |
michael@0 | 913 | } |
michael@0 | 914 | |
michael@0 | 915 | if (!(mState & XML_HTTP_REQUEST_ASYNC) && |
michael@0 | 916 | (aResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT || |
michael@0 | 917 | aResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER)) { |
michael@0 | 918 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
michael@0 | 919 | return; |
michael@0 | 920 | } |
michael@0 | 921 | |
michael@0 | 922 | // Set the responseType attribute's value to the given value. |
michael@0 | 923 | mResponseType = aResponseType; |
michael@0 | 924 | |
michael@0 | 925 | } |
michael@0 | 926 | |
michael@0 | 927 | /* readonly attribute jsval response; */ |
michael@0 | 928 | NS_IMETHODIMP |
michael@0 | 929 | nsXMLHttpRequest::GetResponse(JSContext *aCx, JS::MutableHandle<JS::Value> aResult) |
michael@0 | 930 | { |
michael@0 | 931 | ErrorResult rv; |
michael@0 | 932 | GetResponse(aCx, aResult, rv); |
michael@0 | 933 | return rv.ErrorCode(); |
michael@0 | 934 | } |
michael@0 | 935 | |
michael@0 | 936 | void |
michael@0 | 937 | nsXMLHttpRequest::GetResponse(JSContext* aCx, |
michael@0 | 938 | JS::MutableHandle<JS::Value> aResponse, |
michael@0 | 939 | ErrorResult& aRv) |
michael@0 | 940 | { |
michael@0 | 941 | switch (mResponseType) { |
michael@0 | 942 | case XML_HTTP_RESPONSE_TYPE_DEFAULT: |
michael@0 | 943 | case XML_HTTP_RESPONSE_TYPE_TEXT: |
michael@0 | 944 | case XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT: |
michael@0 | 945 | { |
michael@0 | 946 | nsString str; |
michael@0 | 947 | aRv = GetResponseText(str); |
michael@0 | 948 | if (aRv.Failed()) { |
michael@0 | 949 | return; |
michael@0 | 950 | } |
michael@0 | 951 | if (!xpc::StringToJsval(aCx, str, aResponse)) { |
michael@0 | 952 | aRv.Throw(NS_ERROR_OUT_OF_MEMORY); |
michael@0 | 953 | } |
michael@0 | 954 | return; |
michael@0 | 955 | } |
michael@0 | 956 | |
michael@0 | 957 | case XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER: |
michael@0 | 958 | case XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER: |
michael@0 | 959 | { |
michael@0 | 960 | if (!(mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER && |
michael@0 | 961 | mState & XML_HTTP_REQUEST_DONE) && |
michael@0 | 962 | !(mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER && |
michael@0 | 963 | mInLoadProgressEvent)) { |
michael@0 | 964 | aResponse.setNull(); |
michael@0 | 965 | return; |
michael@0 | 966 | } |
michael@0 | 967 | |
michael@0 | 968 | if (!mResultArrayBuffer) { |
michael@0 | 969 | RootJSResultObjects(); |
michael@0 | 970 | |
michael@0 | 971 | mResultArrayBuffer = mArrayBufferBuilder.getArrayBuffer(aCx); |
michael@0 | 972 | if (!mResultArrayBuffer) { |
michael@0 | 973 | aRv.Throw(NS_ERROR_OUT_OF_MEMORY); |
michael@0 | 974 | return; |
michael@0 | 975 | } |
michael@0 | 976 | } |
michael@0 | 977 | JS::ExposeObjectToActiveJS(mResultArrayBuffer); |
michael@0 | 978 | aResponse.setObject(*mResultArrayBuffer); |
michael@0 | 979 | return; |
michael@0 | 980 | } |
michael@0 | 981 | case XML_HTTP_RESPONSE_TYPE_BLOB: |
michael@0 | 982 | case XML_HTTP_RESPONSE_TYPE_MOZ_BLOB: |
michael@0 | 983 | { |
michael@0 | 984 | if (!(mState & XML_HTTP_REQUEST_DONE)) { |
michael@0 | 985 | if (mResponseType != XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) { |
michael@0 | 986 | aResponse.setNull(); |
michael@0 | 987 | return; |
michael@0 | 988 | } |
michael@0 | 989 | |
michael@0 | 990 | if (!mResponseBlob) { |
michael@0 | 991 | CreatePartialBlob(); |
michael@0 | 992 | } |
michael@0 | 993 | } |
michael@0 | 994 | |
michael@0 | 995 | if (!mResponseBlob) { |
michael@0 | 996 | aResponse.setNull(); |
michael@0 | 997 | return; |
michael@0 | 998 | } |
michael@0 | 999 | |
michael@0 | 1000 | aRv = nsContentUtils::WrapNative(aCx, mResponseBlob, aResponse); |
michael@0 | 1001 | return; |
michael@0 | 1002 | } |
michael@0 | 1003 | case XML_HTTP_RESPONSE_TYPE_DOCUMENT: |
michael@0 | 1004 | { |
michael@0 | 1005 | if (!(mState & XML_HTTP_REQUEST_DONE) || !mResponseXML) { |
michael@0 | 1006 | aResponse.setNull(); |
michael@0 | 1007 | return; |
michael@0 | 1008 | } |
michael@0 | 1009 | |
michael@0 | 1010 | aRv = nsContentUtils::WrapNative(aCx, mResponseXML, aResponse); |
michael@0 | 1011 | return; |
michael@0 | 1012 | } |
michael@0 | 1013 | case XML_HTTP_RESPONSE_TYPE_JSON: |
michael@0 | 1014 | { |
michael@0 | 1015 | if (!(mState & XML_HTTP_REQUEST_DONE)) { |
michael@0 | 1016 | aResponse.setNull(); |
michael@0 | 1017 | return; |
michael@0 | 1018 | } |
michael@0 | 1019 | |
michael@0 | 1020 | if (mResultJSON.isUndefined()) { |
michael@0 | 1021 | aRv = CreateResponseParsedJSON(aCx); |
michael@0 | 1022 | mResponseText.Truncate(); |
michael@0 | 1023 | if (aRv.Failed()) { |
michael@0 | 1024 | // Per spec, errors aren't propagated. null is returned instead. |
michael@0 | 1025 | aRv = NS_OK; |
michael@0 | 1026 | // It would be nice to log the error to the console. That's hard to |
michael@0 | 1027 | // do without calling window.onerror as a side effect, though. |
michael@0 | 1028 | JS_ClearPendingException(aCx); |
michael@0 | 1029 | mResultJSON.setNull(); |
michael@0 | 1030 | } |
michael@0 | 1031 | } |
michael@0 | 1032 | JS::ExposeValueToActiveJS(mResultJSON); |
michael@0 | 1033 | aResponse.set(mResultJSON); |
michael@0 | 1034 | return; |
michael@0 | 1035 | } |
michael@0 | 1036 | default: |
michael@0 | 1037 | NS_ERROR("Should not happen"); |
michael@0 | 1038 | } |
michael@0 | 1039 | |
michael@0 | 1040 | aResponse.setNull(); |
michael@0 | 1041 | } |
michael@0 | 1042 | |
michael@0 | 1043 | /* readonly attribute unsigned long status; */ |
michael@0 | 1044 | NS_IMETHODIMP |
michael@0 | 1045 | nsXMLHttpRequest::GetStatus(uint32_t *aStatus) |
michael@0 | 1046 | { |
michael@0 | 1047 | *aStatus = Status(); |
michael@0 | 1048 | return NS_OK; |
michael@0 | 1049 | } |
michael@0 | 1050 | |
michael@0 | 1051 | uint32_t |
michael@0 | 1052 | nsXMLHttpRequest::Status() |
michael@0 | 1053 | { |
michael@0 | 1054 | if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) { |
michael@0 | 1055 | // Make sure we don't leak status information from denied cross-site |
michael@0 | 1056 | // requests. |
michael@0 | 1057 | if (mChannel) { |
michael@0 | 1058 | nsresult status; |
michael@0 | 1059 | mChannel->GetStatus(&status); |
michael@0 | 1060 | if (NS_FAILED(status)) { |
michael@0 | 1061 | return 0; |
michael@0 | 1062 | } |
michael@0 | 1063 | } |
michael@0 | 1064 | } |
michael@0 | 1065 | |
michael@0 | 1066 | uint16_t readyState; |
michael@0 | 1067 | GetReadyState(&readyState); |
michael@0 | 1068 | if (readyState == UNSENT || readyState == OPENED) { |
michael@0 | 1069 | return 0; |
michael@0 | 1070 | } |
michael@0 | 1071 | |
michael@0 | 1072 | if (mErrorLoad) { |
michael@0 | 1073 | // Let's simulate the http protocol for jar/app requests: |
michael@0 | 1074 | nsCOMPtr<nsIJARChannel> jarChannel = GetCurrentJARChannel(); |
michael@0 | 1075 | if (jarChannel) { |
michael@0 | 1076 | nsresult status; |
michael@0 | 1077 | mChannel->GetStatus(&status); |
michael@0 | 1078 | |
michael@0 | 1079 | if (status == NS_ERROR_FILE_NOT_FOUND) { |
michael@0 | 1080 | return 404; // Not Found |
michael@0 | 1081 | } else { |
michael@0 | 1082 | return 500; // Internal Error |
michael@0 | 1083 | } |
michael@0 | 1084 | } |
michael@0 | 1085 | |
michael@0 | 1086 | return 0; |
michael@0 | 1087 | } |
michael@0 | 1088 | |
michael@0 | 1089 | nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel(); |
michael@0 | 1090 | if (!httpChannel) { |
michael@0 | 1091 | |
michael@0 | 1092 | // Let's simulate the http protocol for jar/app requests: |
michael@0 | 1093 | nsCOMPtr<nsIJARChannel> jarChannel = GetCurrentJARChannel(); |
michael@0 | 1094 | if (jarChannel) { |
michael@0 | 1095 | return 200; // Ok |
michael@0 | 1096 | } |
michael@0 | 1097 | |
michael@0 | 1098 | return 0; |
michael@0 | 1099 | } |
michael@0 | 1100 | |
michael@0 | 1101 | uint32_t status; |
michael@0 | 1102 | nsresult rv = httpChannel->GetResponseStatus(&status); |
michael@0 | 1103 | if (NS_FAILED(rv)) { |
michael@0 | 1104 | status = 0; |
michael@0 | 1105 | } |
michael@0 | 1106 | |
michael@0 | 1107 | return status; |
michael@0 | 1108 | } |
michael@0 | 1109 | |
michael@0 | 1110 | IMPL_CSTRING_GETTER(GetStatusText) |
michael@0 | 1111 | void |
michael@0 | 1112 | nsXMLHttpRequest::GetStatusText(nsCString& aStatusText) |
michael@0 | 1113 | { |
michael@0 | 1114 | nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel(); |
michael@0 | 1115 | |
michael@0 | 1116 | aStatusText.Truncate(); |
michael@0 | 1117 | |
michael@0 | 1118 | if (!httpChannel) { |
michael@0 | 1119 | return; |
michael@0 | 1120 | } |
michael@0 | 1121 | |
michael@0 | 1122 | if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) { |
michael@0 | 1123 | // Make sure we don't leak status information from denied cross-site |
michael@0 | 1124 | // requests. |
michael@0 | 1125 | if (mChannel) { |
michael@0 | 1126 | nsresult status; |
michael@0 | 1127 | mChannel->GetStatus(&status); |
michael@0 | 1128 | if (NS_FAILED(status)) { |
michael@0 | 1129 | return; |
michael@0 | 1130 | } |
michael@0 | 1131 | } |
michael@0 | 1132 | } |
michael@0 | 1133 | |
michael@0 | 1134 | httpChannel->GetResponseStatusText(aStatusText); |
michael@0 | 1135 | |
michael@0 | 1136 | } |
michael@0 | 1137 | |
michael@0 | 1138 | void |
michael@0 | 1139 | nsXMLHttpRequest::CloseRequestWithError(const nsAString& aType, |
michael@0 | 1140 | const uint32_t aFlag) |
michael@0 | 1141 | { |
michael@0 | 1142 | if (mChannel) { |
michael@0 | 1143 | mChannel->Cancel(NS_BINDING_ABORTED); |
michael@0 | 1144 | } |
michael@0 | 1145 | if (mCORSPreflightChannel) { |
michael@0 | 1146 | mCORSPreflightChannel->Cancel(NS_BINDING_ABORTED); |
michael@0 | 1147 | } |
michael@0 | 1148 | if (mTimeoutTimer) { |
michael@0 | 1149 | mTimeoutTimer->Cancel(); |
michael@0 | 1150 | } |
michael@0 | 1151 | uint32_t responseLength = mResponseBody.Length(); |
michael@0 | 1152 | ResetResponse(); |
michael@0 | 1153 | mState |= aFlag; |
michael@0 | 1154 | |
michael@0 | 1155 | // If we're in the destructor, don't risk dispatching an event. |
michael@0 | 1156 | if (mState & XML_HTTP_REQUEST_DELETED) { |
michael@0 | 1157 | mState &= ~XML_HTTP_REQUEST_SYNCLOOPING; |
michael@0 | 1158 | return; |
michael@0 | 1159 | } |
michael@0 | 1160 | |
michael@0 | 1161 | if (!(mState & (XML_HTTP_REQUEST_UNSENT | |
michael@0 | 1162 | XML_HTTP_REQUEST_OPENED | |
michael@0 | 1163 | XML_HTTP_REQUEST_DONE))) { |
michael@0 | 1164 | ChangeState(XML_HTTP_REQUEST_DONE, true); |
michael@0 | 1165 | |
michael@0 | 1166 | if (!(mState & XML_HTTP_REQUEST_SYNCLOOPING)) { |
michael@0 | 1167 | DispatchProgressEvent(this, aType, mLoadLengthComputable, responseLength, |
michael@0 | 1168 | mLoadTotal); |
michael@0 | 1169 | if (mUpload && !mUploadComplete) { |
michael@0 | 1170 | mUploadComplete = true; |
michael@0 | 1171 | DispatchProgressEvent(mUpload, aType, true, mUploadTransferred, |
michael@0 | 1172 | mUploadTotal); |
michael@0 | 1173 | } |
michael@0 | 1174 | } |
michael@0 | 1175 | } |
michael@0 | 1176 | |
michael@0 | 1177 | // The ChangeState call above calls onreadystatechange handlers which |
michael@0 | 1178 | // if they load a new url will cause nsXMLHttpRequest::Open to clear |
michael@0 | 1179 | // the abort state bit. If this occurs we're not uninitialized (bug 361773). |
michael@0 | 1180 | if (mState & XML_HTTP_REQUEST_ABORTED) { |
michael@0 | 1181 | ChangeState(XML_HTTP_REQUEST_UNSENT, false); // IE seems to do it |
michael@0 | 1182 | } |
michael@0 | 1183 | |
michael@0 | 1184 | mState &= ~XML_HTTP_REQUEST_SYNCLOOPING; |
michael@0 | 1185 | } |
michael@0 | 1186 | |
michael@0 | 1187 | /* void abort (); */ |
michael@0 | 1188 | void |
michael@0 | 1189 | nsXMLHttpRequest::Abort() |
michael@0 | 1190 | { |
michael@0 | 1191 | CloseRequestWithError(NS_LITERAL_STRING(ABORT_STR), XML_HTTP_REQUEST_ABORTED); |
michael@0 | 1192 | } |
michael@0 | 1193 | |
michael@0 | 1194 | NS_IMETHODIMP |
michael@0 | 1195 | nsXMLHttpRequest::SlowAbort() |
michael@0 | 1196 | { |
michael@0 | 1197 | Abort(); |
michael@0 | 1198 | return NS_OK; |
michael@0 | 1199 | } |
michael@0 | 1200 | |
michael@0 | 1201 | /*Method that checks if it is safe to expose a header value to the client. |
michael@0 | 1202 | It is used to check what headers are exposed for CORS requests.*/ |
michael@0 | 1203 | bool |
michael@0 | 1204 | nsXMLHttpRequest::IsSafeHeader(const nsACString& header, nsIHttpChannel* httpChannel) |
michael@0 | 1205 | { |
michael@0 | 1206 | // See bug #380418. Hide "Set-Cookie" headers from non-chrome scripts. |
michael@0 | 1207 | if (!IsSystemXHR() && |
michael@0 | 1208 | (header.LowerCaseEqualsASCII("set-cookie") || |
michael@0 | 1209 | header.LowerCaseEqualsASCII("set-cookie2"))) { |
michael@0 | 1210 | NS_WARNING("blocked access to response header"); |
michael@0 | 1211 | return false; |
michael@0 | 1212 | } |
michael@0 | 1213 | // if this is not a CORS call all headers are safe |
michael@0 | 1214 | if (!(mState & XML_HTTP_REQUEST_USE_XSITE_AC)){ |
michael@0 | 1215 | return true; |
michael@0 | 1216 | } |
michael@0 | 1217 | // Check for dangerous headers |
michael@0 | 1218 | // Make sure we don't leak header information from denied cross-site |
michael@0 | 1219 | // requests. |
michael@0 | 1220 | if (mChannel) { |
michael@0 | 1221 | nsresult status; |
michael@0 | 1222 | mChannel->GetStatus(&status); |
michael@0 | 1223 | if (NS_FAILED(status)) { |
michael@0 | 1224 | return false; |
michael@0 | 1225 | } |
michael@0 | 1226 | } |
michael@0 | 1227 | const char* kCrossOriginSafeHeaders[] = { |
michael@0 | 1228 | "cache-control", "content-language", "content-type", "expires", |
michael@0 | 1229 | "last-modified", "pragma" |
michael@0 | 1230 | }; |
michael@0 | 1231 | for (uint32_t i = 0; i < ArrayLength(kCrossOriginSafeHeaders); ++i) { |
michael@0 | 1232 | if (header.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) { |
michael@0 | 1233 | return true; |
michael@0 | 1234 | } |
michael@0 | 1235 | } |
michael@0 | 1236 | nsAutoCString headerVal; |
michael@0 | 1237 | // The "Access-Control-Expose-Headers" header contains a comma separated |
michael@0 | 1238 | // list of method names. |
michael@0 | 1239 | httpChannel-> |
michael@0 | 1240 | GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Expose-Headers"), |
michael@0 | 1241 | headerVal); |
michael@0 | 1242 | nsCCharSeparatedTokenizer exposeTokens(headerVal, ','); |
michael@0 | 1243 | bool isSafe = false; |
michael@0 | 1244 | while (exposeTokens.hasMoreTokens()) { |
michael@0 | 1245 | const nsDependentCSubstring& token = exposeTokens.nextToken(); |
michael@0 | 1246 | if (token.IsEmpty()) { |
michael@0 | 1247 | continue; |
michael@0 | 1248 | } |
michael@0 | 1249 | if (!IsValidHTTPToken(token)) { |
michael@0 | 1250 | return false; |
michael@0 | 1251 | } |
michael@0 | 1252 | if (header.Equals(token, nsCaseInsensitiveCStringComparator())) { |
michael@0 | 1253 | isSafe = true; |
michael@0 | 1254 | } |
michael@0 | 1255 | } |
michael@0 | 1256 | return isSafe; |
michael@0 | 1257 | } |
michael@0 | 1258 | |
michael@0 | 1259 | /* ByteString getAllResponseHeaders(); */ |
michael@0 | 1260 | IMPL_CSTRING_GETTER(GetAllResponseHeaders) |
michael@0 | 1261 | void |
michael@0 | 1262 | nsXMLHttpRequest::GetAllResponseHeaders(nsCString& aResponseHeaders) |
michael@0 | 1263 | { |
michael@0 | 1264 | aResponseHeaders.Truncate(); |
michael@0 | 1265 | |
michael@0 | 1266 | // If the state is UNSENT or OPENED, |
michael@0 | 1267 | // return the empty string and terminate these steps. |
michael@0 | 1268 | if (mState & (XML_HTTP_REQUEST_UNSENT | |
michael@0 | 1269 | XML_HTTP_REQUEST_OPENED | XML_HTTP_REQUEST_SENT)) { |
michael@0 | 1270 | return; |
michael@0 | 1271 | } |
michael@0 | 1272 | |
michael@0 | 1273 | if (nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel()) { |
michael@0 | 1274 | nsRefPtr<nsHeaderVisitor> visitor = new nsHeaderVisitor(this, httpChannel); |
michael@0 | 1275 | if (NS_SUCCEEDED(httpChannel->VisitResponseHeaders(visitor))) { |
michael@0 | 1276 | aResponseHeaders = visitor->Headers(); |
michael@0 | 1277 | } |
michael@0 | 1278 | return; |
michael@0 | 1279 | } |
michael@0 | 1280 | |
michael@0 | 1281 | if (!mChannel) { |
michael@0 | 1282 | return; |
michael@0 | 1283 | } |
michael@0 | 1284 | |
michael@0 | 1285 | // Even non-http channels supply content type. |
michael@0 | 1286 | nsAutoCString value; |
michael@0 | 1287 | if (NS_SUCCEEDED(mChannel->GetContentType(value))) { |
michael@0 | 1288 | aResponseHeaders.AppendLiteral("Content-Type: "); |
michael@0 | 1289 | aResponseHeaders.Append(value); |
michael@0 | 1290 | if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) && !value.IsEmpty()) { |
michael@0 | 1291 | aResponseHeaders.AppendLiteral(";charset="); |
michael@0 | 1292 | aResponseHeaders.Append(value); |
michael@0 | 1293 | } |
michael@0 | 1294 | aResponseHeaders.AppendLiteral("\r\n"); |
michael@0 | 1295 | } |
michael@0 | 1296 | |
michael@0 | 1297 | int64_t length; |
michael@0 | 1298 | if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) { |
michael@0 | 1299 | aResponseHeaders.AppendLiteral("Content-Length: "); |
michael@0 | 1300 | aResponseHeaders.AppendInt(length); |
michael@0 | 1301 | aResponseHeaders.AppendLiteral("\r\n"); |
michael@0 | 1302 | } |
michael@0 | 1303 | } |
michael@0 | 1304 | |
michael@0 | 1305 | NS_IMETHODIMP |
michael@0 | 1306 | nsXMLHttpRequest::GetResponseHeader(const nsACString& aHeader, |
michael@0 | 1307 | nsACString& aResult) |
michael@0 | 1308 | { |
michael@0 | 1309 | ErrorResult rv; |
michael@0 | 1310 | GetResponseHeader(aHeader, aResult, rv); |
michael@0 | 1311 | return rv.ErrorCode(); |
michael@0 | 1312 | } |
michael@0 | 1313 | |
michael@0 | 1314 | void |
michael@0 | 1315 | nsXMLHttpRequest::GetResponseHeader(const nsACString& header, |
michael@0 | 1316 | nsACString& _retval, ErrorResult& aRv) |
michael@0 | 1317 | { |
michael@0 | 1318 | _retval.SetIsVoid(true); |
michael@0 | 1319 | |
michael@0 | 1320 | nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel(); |
michael@0 | 1321 | |
michael@0 | 1322 | if (!httpChannel) { |
michael@0 | 1323 | // If the state is UNSENT or OPENED, |
michael@0 | 1324 | // return null and terminate these steps. |
michael@0 | 1325 | if (mState & (XML_HTTP_REQUEST_UNSENT | |
michael@0 | 1326 | XML_HTTP_REQUEST_OPENED | XML_HTTP_REQUEST_SENT)) { |
michael@0 | 1327 | return; |
michael@0 | 1328 | } |
michael@0 | 1329 | |
michael@0 | 1330 | // Even non-http channels supply content type and content length. |
michael@0 | 1331 | // Remember we don't leak header information from denied cross-site |
michael@0 | 1332 | // requests. |
michael@0 | 1333 | nsresult status; |
michael@0 | 1334 | if (!mChannel || |
michael@0 | 1335 | NS_FAILED(mChannel->GetStatus(&status)) || |
michael@0 | 1336 | NS_FAILED(status)) { |
michael@0 | 1337 | return; |
michael@0 | 1338 | } |
michael@0 | 1339 | |
michael@0 | 1340 | // Content Type: |
michael@0 | 1341 | if (header.LowerCaseEqualsASCII("content-type")) { |
michael@0 | 1342 | if (NS_FAILED(mChannel->GetContentType(_retval))) { |
michael@0 | 1343 | // Means no content type |
michael@0 | 1344 | _retval.SetIsVoid(true); |
michael@0 | 1345 | return; |
michael@0 | 1346 | } |
michael@0 | 1347 | |
michael@0 | 1348 | nsCString value; |
michael@0 | 1349 | if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) && |
michael@0 | 1350 | !value.IsEmpty()) { |
michael@0 | 1351 | _retval.Append(";charset="); |
michael@0 | 1352 | _retval.Append(value); |
michael@0 | 1353 | } |
michael@0 | 1354 | } |
michael@0 | 1355 | |
michael@0 | 1356 | // Content Length: |
michael@0 | 1357 | else if (header.LowerCaseEqualsASCII("content-length")) { |
michael@0 | 1358 | int64_t length; |
michael@0 | 1359 | if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) { |
michael@0 | 1360 | _retval.AppendInt(length); |
michael@0 | 1361 | } |
michael@0 | 1362 | } |
michael@0 | 1363 | |
michael@0 | 1364 | return; |
michael@0 | 1365 | } |
michael@0 | 1366 | |
michael@0 | 1367 | // Check for dangerous headers |
michael@0 | 1368 | if (!IsSafeHeader(header, httpChannel)) { |
michael@0 | 1369 | return; |
michael@0 | 1370 | } |
michael@0 | 1371 | |
michael@0 | 1372 | aRv = httpChannel->GetResponseHeader(header, _retval); |
michael@0 | 1373 | if (aRv.ErrorCode() == NS_ERROR_NOT_AVAILABLE) { |
michael@0 | 1374 | // Means no header |
michael@0 | 1375 | _retval.SetIsVoid(true); |
michael@0 | 1376 | aRv = NS_OK; |
michael@0 | 1377 | } |
michael@0 | 1378 | } |
michael@0 | 1379 | |
michael@0 | 1380 | already_AddRefed<nsILoadGroup> |
michael@0 | 1381 | nsXMLHttpRequest::GetLoadGroup() const |
michael@0 | 1382 | { |
michael@0 | 1383 | if (mState & XML_HTTP_REQUEST_BACKGROUND) { |
michael@0 | 1384 | return nullptr; |
michael@0 | 1385 | } |
michael@0 | 1386 | |
michael@0 | 1387 | nsresult rv = NS_ERROR_FAILURE; |
michael@0 | 1388 | nsIScriptContext* sc = |
michael@0 | 1389 | const_cast<nsXMLHttpRequest*>(this)->GetContextForEventHandlers(&rv); |
michael@0 | 1390 | nsCOMPtr<nsIDocument> doc = |
michael@0 | 1391 | nsContentUtils::GetDocumentFromScriptContext(sc); |
michael@0 | 1392 | if (doc) { |
michael@0 | 1393 | return doc->GetDocumentLoadGroup(); |
michael@0 | 1394 | } |
michael@0 | 1395 | |
michael@0 | 1396 | return nullptr; |
michael@0 | 1397 | } |
michael@0 | 1398 | |
michael@0 | 1399 | nsresult |
michael@0 | 1400 | nsXMLHttpRequest::CreateReadystatechangeEvent(nsIDOMEvent** aDOMEvent) |
michael@0 | 1401 | { |
michael@0 | 1402 | nsresult rv = EventDispatcher::CreateEvent(this, nullptr, nullptr, |
michael@0 | 1403 | NS_LITERAL_STRING("Events"), |
michael@0 | 1404 | aDOMEvent); |
michael@0 | 1405 | if (NS_FAILED(rv)) { |
michael@0 | 1406 | return rv; |
michael@0 | 1407 | } |
michael@0 | 1408 | |
michael@0 | 1409 | (*aDOMEvent)->InitEvent(NS_LITERAL_STRING(READYSTATE_STR), |
michael@0 | 1410 | false, false); |
michael@0 | 1411 | |
michael@0 | 1412 | // We assume anyone who managed to call CreateReadystatechangeEvent is trusted |
michael@0 | 1413 | (*aDOMEvent)->SetTrusted(true); |
michael@0 | 1414 | |
michael@0 | 1415 | return NS_OK; |
michael@0 | 1416 | } |
michael@0 | 1417 | |
michael@0 | 1418 | void |
michael@0 | 1419 | nsXMLHttpRequest::DispatchProgressEvent(DOMEventTargetHelper* aTarget, |
michael@0 | 1420 | const nsAString& aType, |
michael@0 | 1421 | bool aLengthComputable, |
michael@0 | 1422 | uint64_t aLoaded, uint64_t aTotal) |
michael@0 | 1423 | { |
michael@0 | 1424 | NS_ASSERTION(aTarget, "null target"); |
michael@0 | 1425 | NS_ASSERTION(!aType.IsEmpty(), "missing event type"); |
michael@0 | 1426 | |
michael@0 | 1427 | if (NS_FAILED(CheckInnerWindowCorrectness()) || |
michael@0 | 1428 | (!AllowUploadProgress() && aTarget == mUpload)) { |
michael@0 | 1429 | return; |
michael@0 | 1430 | } |
michael@0 | 1431 | |
michael@0 | 1432 | bool dispatchLoadend = aType.EqualsLiteral(LOAD_STR) || |
michael@0 | 1433 | aType.EqualsLiteral(ERROR_STR) || |
michael@0 | 1434 | aType.EqualsLiteral(TIMEOUT_STR) || |
michael@0 | 1435 | aType.EqualsLiteral(ABORT_STR); |
michael@0 | 1436 | |
michael@0 | 1437 | nsCOMPtr<nsIDOMEvent> event; |
michael@0 | 1438 | nsresult rv = NS_NewDOMProgressEvent(getter_AddRefs(event), this, |
michael@0 | 1439 | nullptr, nullptr); |
michael@0 | 1440 | if (NS_FAILED(rv)) { |
michael@0 | 1441 | return; |
michael@0 | 1442 | } |
michael@0 | 1443 | |
michael@0 | 1444 | nsCOMPtr<nsIDOMProgressEvent> progress = do_QueryInterface(event); |
michael@0 | 1445 | if (!progress) { |
michael@0 | 1446 | return; |
michael@0 | 1447 | } |
michael@0 | 1448 | |
michael@0 | 1449 | progress->InitProgressEvent(aType, false, false, aLengthComputable, |
michael@0 | 1450 | aLoaded, (aTotal == UINT64_MAX) ? 0 : aTotal); |
michael@0 | 1451 | |
michael@0 | 1452 | event->SetTrusted(true); |
michael@0 | 1453 | |
michael@0 | 1454 | aTarget->DispatchDOMEvent(nullptr, event, nullptr, nullptr); |
michael@0 | 1455 | |
michael@0 | 1456 | if (dispatchLoadend) { |
michael@0 | 1457 | DispatchProgressEvent(aTarget, NS_LITERAL_STRING(LOADEND_STR), |
michael@0 | 1458 | aLengthComputable, aLoaded, aTotal); |
michael@0 | 1459 | } |
michael@0 | 1460 | } |
michael@0 | 1461 | |
michael@0 | 1462 | already_AddRefed<nsIHttpChannel> |
michael@0 | 1463 | nsXMLHttpRequest::GetCurrentHttpChannel() |
michael@0 | 1464 | { |
michael@0 | 1465 | nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel); |
michael@0 | 1466 | return httpChannel.forget(); |
michael@0 | 1467 | } |
michael@0 | 1468 | |
michael@0 | 1469 | already_AddRefed<nsIJARChannel> |
michael@0 | 1470 | nsXMLHttpRequest::GetCurrentJARChannel() |
michael@0 | 1471 | { |
michael@0 | 1472 | nsCOMPtr<nsIJARChannel> appChannel = do_QueryInterface(mChannel); |
michael@0 | 1473 | return appChannel.forget(); |
michael@0 | 1474 | } |
michael@0 | 1475 | |
michael@0 | 1476 | bool |
michael@0 | 1477 | nsXMLHttpRequest::IsSystemXHR() |
michael@0 | 1478 | { |
michael@0 | 1479 | return mIsSystem || nsContentUtils::IsSystemPrincipal(mPrincipal); |
michael@0 | 1480 | } |
michael@0 | 1481 | |
michael@0 | 1482 | nsresult |
michael@0 | 1483 | nsXMLHttpRequest::CheckChannelForCrossSiteRequest(nsIChannel* aChannel) |
michael@0 | 1484 | { |
michael@0 | 1485 | // A system XHR (chrome code or a web app with the right permission) can |
michael@0 | 1486 | // always perform cross-site requests. In the web app case, however, we |
michael@0 | 1487 | // must still check for protected URIs like file:///. |
michael@0 | 1488 | if (IsSystemXHR()) { |
michael@0 | 1489 | if (!nsContentUtils::IsSystemPrincipal(mPrincipal)) { |
michael@0 | 1490 | nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager(); |
michael@0 | 1491 | nsCOMPtr<nsIURI> uri; |
michael@0 | 1492 | aChannel->GetOriginalURI(getter_AddRefs(uri)); |
michael@0 | 1493 | return secMan->CheckLoadURIWithPrincipal( |
michael@0 | 1494 | mPrincipal, uri, nsIScriptSecurityManager::STANDARD); |
michael@0 | 1495 | } |
michael@0 | 1496 | return NS_OK; |
michael@0 | 1497 | } |
michael@0 | 1498 | |
michael@0 | 1499 | // If this is a same-origin request or the channel's URI inherits |
michael@0 | 1500 | // its principal, it's allowed. |
michael@0 | 1501 | if (nsContentUtils::CheckMayLoad(mPrincipal, aChannel, true)) { |
michael@0 | 1502 | return NS_OK; |
michael@0 | 1503 | } |
michael@0 | 1504 | |
michael@0 | 1505 | // This is a cross-site request |
michael@0 | 1506 | mState |= XML_HTTP_REQUEST_USE_XSITE_AC; |
michael@0 | 1507 | |
michael@0 | 1508 | // Check if we need to do a preflight request. |
michael@0 | 1509 | nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); |
michael@0 | 1510 | NS_ENSURE_TRUE(httpChannel, NS_ERROR_DOM_BAD_URI); |
michael@0 | 1511 | |
michael@0 | 1512 | nsAutoCString method; |
michael@0 | 1513 | httpChannel->GetRequestMethod(method); |
michael@0 | 1514 | if (!mCORSUnsafeHeaders.IsEmpty() || |
michael@0 | 1515 | (mUpload && mUpload->HasListeners()) || |
michael@0 | 1516 | (!method.LowerCaseEqualsLiteral("get") && |
michael@0 | 1517 | !method.LowerCaseEqualsLiteral("post") && |
michael@0 | 1518 | !method.LowerCaseEqualsLiteral("head"))) { |
michael@0 | 1519 | mState |= XML_HTTP_REQUEST_NEED_AC_PREFLIGHT; |
michael@0 | 1520 | } |
michael@0 | 1521 | |
michael@0 | 1522 | return NS_OK; |
michael@0 | 1523 | } |
michael@0 | 1524 | |
michael@0 | 1525 | NS_IMETHODIMP |
michael@0 | 1526 | nsXMLHttpRequest::Open(const nsACString& method, const nsACString& url, |
michael@0 | 1527 | bool async, const nsAString& user, |
michael@0 | 1528 | const nsAString& password, uint8_t optional_argc) |
michael@0 | 1529 | { |
michael@0 | 1530 | if (!optional_argc) { |
michael@0 | 1531 | // No optional arguments were passed in. Default async to true. |
michael@0 | 1532 | async = true; |
michael@0 | 1533 | } |
michael@0 | 1534 | Optional<nsAString> realUser; |
michael@0 | 1535 | if (optional_argc > 1) { |
michael@0 | 1536 | realUser = &user; |
michael@0 | 1537 | } |
michael@0 | 1538 | Optional<nsAString> realPassword; |
michael@0 | 1539 | if (optional_argc > 2) { |
michael@0 | 1540 | realPassword = &password; |
michael@0 | 1541 | } |
michael@0 | 1542 | return Open(method, url, async, realUser, realPassword); |
michael@0 | 1543 | } |
michael@0 | 1544 | |
michael@0 | 1545 | nsresult |
michael@0 | 1546 | nsXMLHttpRequest::Open(const nsACString& inMethod, const nsACString& url, |
michael@0 | 1547 | bool async, const Optional<nsAString>& user, |
michael@0 | 1548 | const Optional<nsAString>& password) |
michael@0 | 1549 | { |
michael@0 | 1550 | NS_ENSURE_ARG(!inMethod.IsEmpty()); |
michael@0 | 1551 | |
michael@0 | 1552 | if (!async && !DontWarnAboutSyncXHR() && GetOwner() && |
michael@0 | 1553 | GetOwner()->GetExtantDoc()) { |
michael@0 | 1554 | GetOwner()->GetExtantDoc()->WarnOnceAbout(nsIDocument::eSyncXMLHttpRequest); |
michael@0 | 1555 | } |
michael@0 | 1556 | |
michael@0 | 1557 | Telemetry::Accumulate(Telemetry::XMLHTTPREQUEST_ASYNC_OR_SYNC, |
michael@0 | 1558 | async ? 0 : 1); |
michael@0 | 1559 | |
michael@0 | 1560 | NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1561 | |
michael@0 | 1562 | // Disallow HTTP/1.1 TRACE method (see bug 302489) |
michael@0 | 1563 | // and MS IIS equivalent TRACK (see bug 381264) |
michael@0 | 1564 | // and CONNECT |
michael@0 | 1565 | if (inMethod.LowerCaseEqualsLiteral("trace") || |
michael@0 | 1566 | inMethod.LowerCaseEqualsLiteral("connect") || |
michael@0 | 1567 | inMethod.LowerCaseEqualsLiteral("track")) { |
michael@0 | 1568 | return NS_ERROR_DOM_SECURITY_ERR; |
michael@0 | 1569 | } |
michael@0 | 1570 | |
michael@0 | 1571 | nsAutoCString method; |
michael@0 | 1572 | // GET, POST, DELETE, HEAD, OPTIONS, PUT methods normalized to upper case |
michael@0 | 1573 | if (inMethod.LowerCaseEqualsLiteral("get")) { |
michael@0 | 1574 | method.Assign(NS_LITERAL_CSTRING("GET")); |
michael@0 | 1575 | } else if (inMethod.LowerCaseEqualsLiteral("post")) { |
michael@0 | 1576 | method.Assign(NS_LITERAL_CSTRING("POST")); |
michael@0 | 1577 | } else if (inMethod.LowerCaseEqualsLiteral("delete")) { |
michael@0 | 1578 | method.Assign(NS_LITERAL_CSTRING("DELETE")); |
michael@0 | 1579 | } else if (inMethod.LowerCaseEqualsLiteral("head")) { |
michael@0 | 1580 | method.Assign(NS_LITERAL_CSTRING("HEAD")); |
michael@0 | 1581 | } else if (inMethod.LowerCaseEqualsLiteral("options")) { |
michael@0 | 1582 | method.Assign(NS_LITERAL_CSTRING("OPTIONS")); |
michael@0 | 1583 | } else if (inMethod.LowerCaseEqualsLiteral("put")) { |
michael@0 | 1584 | method.Assign(NS_LITERAL_CSTRING("PUT")); |
michael@0 | 1585 | } else { |
michael@0 | 1586 | method = inMethod; // other methods are not normalized |
michael@0 | 1587 | } |
michael@0 | 1588 | |
michael@0 | 1589 | // sync request is not allowed using withCredential or responseType |
michael@0 | 1590 | // in window context |
michael@0 | 1591 | if (!async && HasOrHasHadOwner() && |
michael@0 | 1592 | (mState & XML_HTTP_REQUEST_AC_WITH_CREDENTIALS || |
michael@0 | 1593 | mTimeoutMilliseconds || |
michael@0 | 1594 | mResponseType != XML_HTTP_RESPONSE_TYPE_DEFAULT)) { |
michael@0 | 1595 | if (mState & XML_HTTP_REQUEST_AC_WITH_CREDENTIALS) { |
michael@0 | 1596 | LogMessage("WithCredentialsSyncXHRWarning", GetOwner()); |
michael@0 | 1597 | } |
michael@0 | 1598 | if (mTimeoutMilliseconds) { |
michael@0 | 1599 | LogMessage("TimeoutSyncXHRWarning", GetOwner()); |
michael@0 | 1600 | } |
michael@0 | 1601 | if (mResponseType != XML_HTTP_RESPONSE_TYPE_DEFAULT) { |
michael@0 | 1602 | LogMessage("ResponseTypeSyncXHRWarning", GetOwner()); |
michael@0 | 1603 | } |
michael@0 | 1604 | return NS_ERROR_DOM_INVALID_ACCESS_ERR; |
michael@0 | 1605 | } |
michael@0 | 1606 | |
michael@0 | 1607 | nsresult rv; |
michael@0 | 1608 | nsCOMPtr<nsIURI> uri; |
michael@0 | 1609 | |
michael@0 | 1610 | if (mState & (XML_HTTP_REQUEST_OPENED | |
michael@0 | 1611 | XML_HTTP_REQUEST_HEADERS_RECEIVED | |
michael@0 | 1612 | XML_HTTP_REQUEST_LOADING | |
michael@0 | 1613 | XML_HTTP_REQUEST_SENT)) { |
michael@0 | 1614 | // IE aborts as well |
michael@0 | 1615 | Abort(); |
michael@0 | 1616 | |
michael@0 | 1617 | // XXX We should probably send a warning to the JS console |
michael@0 | 1618 | // that load was aborted and event listeners were cleared |
michael@0 | 1619 | // since this looks like a situation that could happen |
michael@0 | 1620 | // by accident and you could spend a lot of time wondering |
michael@0 | 1621 | // why things didn't work. |
michael@0 | 1622 | } |
michael@0 | 1623 | |
michael@0 | 1624 | // Unset any pre-existing aborted and timed-out states. |
michael@0 | 1625 | mState &= ~XML_HTTP_REQUEST_ABORTED & ~XML_HTTP_REQUEST_TIMED_OUT; |
michael@0 | 1626 | |
michael@0 | 1627 | if (async) { |
michael@0 | 1628 | mState |= XML_HTTP_REQUEST_ASYNC; |
michael@0 | 1629 | } else { |
michael@0 | 1630 | mState &= ~XML_HTTP_REQUEST_ASYNC; |
michael@0 | 1631 | } |
michael@0 | 1632 | |
michael@0 | 1633 | nsIScriptContext* sc = GetContextForEventHandlers(&rv); |
michael@0 | 1634 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1635 | nsCOMPtr<nsIDocument> doc = |
michael@0 | 1636 | nsContentUtils::GetDocumentFromScriptContext(sc); |
michael@0 | 1637 | |
michael@0 | 1638 | nsCOMPtr<nsIURI> baseURI; |
michael@0 | 1639 | if (mBaseURI) { |
michael@0 | 1640 | baseURI = mBaseURI; |
michael@0 | 1641 | } |
michael@0 | 1642 | else if (doc) { |
michael@0 | 1643 | baseURI = doc->GetBaseURI(); |
michael@0 | 1644 | } |
michael@0 | 1645 | |
michael@0 | 1646 | rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, baseURI); |
michael@0 | 1647 | if (NS_FAILED(rv)) return rv; |
michael@0 | 1648 | |
michael@0 | 1649 | rv = CheckInnerWindowCorrectness(); |
michael@0 | 1650 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1651 | int16_t shouldLoad = nsIContentPolicy::ACCEPT; |
michael@0 | 1652 | rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_XMLHTTPREQUEST, |
michael@0 | 1653 | uri, |
michael@0 | 1654 | mPrincipal, |
michael@0 | 1655 | doc, |
michael@0 | 1656 | EmptyCString(), //mime guess |
michael@0 | 1657 | nullptr, //extra |
michael@0 | 1658 | &shouldLoad, |
michael@0 | 1659 | nsContentUtils::GetContentPolicy(), |
michael@0 | 1660 | nsContentUtils::GetSecurityManager()); |
michael@0 | 1661 | if (NS_FAILED(rv)) return rv; |
michael@0 | 1662 | if (NS_CP_REJECTED(shouldLoad)) { |
michael@0 | 1663 | // Disallowed by content policy |
michael@0 | 1664 | return NS_ERROR_CONTENT_BLOCKED; |
michael@0 | 1665 | } |
michael@0 | 1666 | |
michael@0 | 1667 | // XXXbz this is wrong: we should only be looking at whether |
michael@0 | 1668 | // user/password were passed, not at the values! See bug 759624. |
michael@0 | 1669 | if (user.WasPassed() && !user.Value().IsEmpty()) { |
michael@0 | 1670 | nsAutoCString userpass; |
michael@0 | 1671 | CopyUTF16toUTF8(user.Value(), userpass); |
michael@0 | 1672 | if (password.WasPassed() && !password.Value().IsEmpty()) { |
michael@0 | 1673 | userpass.Append(':'); |
michael@0 | 1674 | AppendUTF16toUTF8(password.Value(), userpass); |
michael@0 | 1675 | } |
michael@0 | 1676 | uri->SetUserPass(userpass); |
michael@0 | 1677 | } |
michael@0 | 1678 | |
michael@0 | 1679 | // Clear our record of previously set headers so future header set |
michael@0 | 1680 | // operations will merge/override correctly. |
michael@0 | 1681 | mAlreadySetHeaders.Clear(); |
michael@0 | 1682 | |
michael@0 | 1683 | // When we are called from JS we can find the load group for the page, |
michael@0 | 1684 | // and add ourselves to it. This way any pending requests |
michael@0 | 1685 | // will be automatically aborted if the user leaves the page. |
michael@0 | 1686 | nsCOMPtr<nsILoadGroup> loadGroup = GetLoadGroup(); |
michael@0 | 1687 | |
michael@0 | 1688 | // get Content Security Policy from principal to pass into channel |
michael@0 | 1689 | nsCOMPtr<nsIChannelPolicy> channelPolicy; |
michael@0 | 1690 | nsCOMPtr<nsIContentSecurityPolicy> csp; |
michael@0 | 1691 | rv = mPrincipal->GetCsp(getter_AddRefs(csp)); |
michael@0 | 1692 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1693 | if (csp) { |
michael@0 | 1694 | channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1"); |
michael@0 | 1695 | channelPolicy->SetContentSecurityPolicy(csp); |
michael@0 | 1696 | channelPolicy->SetLoadType(nsIContentPolicy::TYPE_XMLHTTPREQUEST); |
michael@0 | 1697 | } |
michael@0 | 1698 | rv = NS_NewChannel(getter_AddRefs(mChannel), |
michael@0 | 1699 | uri, |
michael@0 | 1700 | nullptr, // ioService |
michael@0 | 1701 | loadGroup, |
michael@0 | 1702 | nullptr, // callbacks |
michael@0 | 1703 | nsIRequest::LOAD_BACKGROUND, |
michael@0 | 1704 | channelPolicy); |
michael@0 | 1705 | if (NS_FAILED(rv)) return rv; |
michael@0 | 1706 | |
michael@0 | 1707 | mState &= ~(XML_HTTP_REQUEST_USE_XSITE_AC | |
michael@0 | 1708 | XML_HTTP_REQUEST_NEED_AC_PREFLIGHT); |
michael@0 | 1709 | |
michael@0 | 1710 | nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel)); |
michael@0 | 1711 | if (httpChannel) { |
michael@0 | 1712 | rv = httpChannel->SetRequestMethod(method); |
michael@0 | 1713 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1714 | |
michael@0 | 1715 | // Set the initiator type |
michael@0 | 1716 | nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel)); |
michael@0 | 1717 | if (timedChannel) { |
michael@0 | 1718 | timedChannel->SetInitiatorType(NS_LITERAL_STRING("xmlhttprequest")); |
michael@0 | 1719 | } |
michael@0 | 1720 | } |
michael@0 | 1721 | |
michael@0 | 1722 | ChangeState(XML_HTTP_REQUEST_OPENED); |
michael@0 | 1723 | |
michael@0 | 1724 | return rv; |
michael@0 | 1725 | } |
michael@0 | 1726 | |
michael@0 | 1727 | /* |
michael@0 | 1728 | * "Copy" from a stream. |
michael@0 | 1729 | */ |
michael@0 | 1730 | NS_METHOD |
michael@0 | 1731 | nsXMLHttpRequest::StreamReaderFunc(nsIInputStream* in, |
michael@0 | 1732 | void* closure, |
michael@0 | 1733 | const char* fromRawSegment, |
michael@0 | 1734 | uint32_t toOffset, |
michael@0 | 1735 | uint32_t count, |
michael@0 | 1736 | uint32_t *writeCount) |
michael@0 | 1737 | { |
michael@0 | 1738 | nsXMLHttpRequest* xmlHttpRequest = static_cast<nsXMLHttpRequest*>(closure); |
michael@0 | 1739 | if (!xmlHttpRequest || !writeCount) { |
michael@0 | 1740 | NS_WARNING("XMLHttpRequest cannot read from stream: no closure or writeCount"); |
michael@0 | 1741 | return NS_ERROR_FAILURE; |
michael@0 | 1742 | } |
michael@0 | 1743 | |
michael@0 | 1744 | nsresult rv = NS_OK; |
michael@0 | 1745 | |
michael@0 | 1746 | if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB || |
michael@0 | 1747 | xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) { |
michael@0 | 1748 | if (!xmlHttpRequest->mDOMFile) { |
michael@0 | 1749 | if (!xmlHttpRequest->mBlobSet) { |
michael@0 | 1750 | xmlHttpRequest->mBlobSet = new BlobSet(); |
michael@0 | 1751 | } |
michael@0 | 1752 | rv = xmlHttpRequest->mBlobSet->AppendVoidPtr(fromRawSegment, count); |
michael@0 | 1753 | } |
michael@0 | 1754 | // Clear the cache so that the blob size is updated. |
michael@0 | 1755 | if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) { |
michael@0 | 1756 | xmlHttpRequest->mResponseBlob = nullptr; |
michael@0 | 1757 | } |
michael@0 | 1758 | } else if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER || |
michael@0 | 1759 | xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER) { |
michael@0 | 1760 | // get the initial capacity to something reasonable to avoid a bunch of reallocs right |
michael@0 | 1761 | // at the start |
michael@0 | 1762 | if (xmlHttpRequest->mArrayBufferBuilder.capacity() == 0) |
michael@0 | 1763 | xmlHttpRequest->mArrayBufferBuilder.setCapacity(PR_MAX(count, XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE)); |
michael@0 | 1764 | |
michael@0 | 1765 | xmlHttpRequest->mArrayBufferBuilder.append(reinterpret_cast<const uint8_t*>(fromRawSegment), count, |
michael@0 | 1766 | XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH); |
michael@0 | 1767 | } else if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_DEFAULT && |
michael@0 | 1768 | xmlHttpRequest->mResponseXML) { |
michael@0 | 1769 | // Copy for our own use |
michael@0 | 1770 | uint32_t previousLength = xmlHttpRequest->mResponseBody.Length(); |
michael@0 | 1771 | xmlHttpRequest->mResponseBody.Append(fromRawSegment,count); |
michael@0 | 1772 | if (count > 0 && xmlHttpRequest->mResponseBody.Length() == previousLength) { |
michael@0 | 1773 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 1774 | } |
michael@0 | 1775 | } else if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_DEFAULT || |
michael@0 | 1776 | xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_TEXT || |
michael@0 | 1777 | xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_JSON || |
michael@0 | 1778 | xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT) { |
michael@0 | 1779 | NS_ASSERTION(!xmlHttpRequest->mResponseXML, |
michael@0 | 1780 | "We shouldn't be parsing a doc here"); |
michael@0 | 1781 | xmlHttpRequest->AppendToResponseText(fromRawSegment, count); |
michael@0 | 1782 | } |
michael@0 | 1783 | |
michael@0 | 1784 | if (xmlHttpRequest->mState & XML_HTTP_REQUEST_PARSEBODY) { |
michael@0 | 1785 | // Give the same data to the parser. |
michael@0 | 1786 | |
michael@0 | 1787 | // We need to wrap the data in a new lightweight stream and pass that |
michael@0 | 1788 | // to the parser, because calling ReadSegments() recursively on the same |
michael@0 | 1789 | // stream is not supported. |
michael@0 | 1790 | nsCOMPtr<nsIInputStream> copyStream; |
michael@0 | 1791 | rv = NS_NewByteInputStream(getter_AddRefs(copyStream), fromRawSegment, count); |
michael@0 | 1792 | |
michael@0 | 1793 | if (NS_SUCCEEDED(rv) && xmlHttpRequest->mXMLParserStreamListener) { |
michael@0 | 1794 | NS_ASSERTION(copyStream, "NS_NewByteInputStream lied"); |
michael@0 | 1795 | nsresult parsingResult = xmlHttpRequest->mXMLParserStreamListener |
michael@0 | 1796 | ->OnDataAvailable(xmlHttpRequest->mChannel, |
michael@0 | 1797 | xmlHttpRequest->mContext, |
michael@0 | 1798 | copyStream, toOffset, count); |
michael@0 | 1799 | |
michael@0 | 1800 | // No use to continue parsing if we failed here, but we |
michael@0 | 1801 | // should still finish reading the stream |
michael@0 | 1802 | if (NS_FAILED(parsingResult)) { |
michael@0 | 1803 | xmlHttpRequest->mState &= ~XML_HTTP_REQUEST_PARSEBODY; |
michael@0 | 1804 | } |
michael@0 | 1805 | } |
michael@0 | 1806 | } |
michael@0 | 1807 | |
michael@0 | 1808 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 1809 | *writeCount = count; |
michael@0 | 1810 | } else { |
michael@0 | 1811 | *writeCount = 0; |
michael@0 | 1812 | } |
michael@0 | 1813 | |
michael@0 | 1814 | return rv; |
michael@0 | 1815 | } |
michael@0 | 1816 | |
michael@0 | 1817 | bool nsXMLHttpRequest::CreateDOMFile(nsIRequest *request) |
michael@0 | 1818 | { |
michael@0 | 1819 | nsCOMPtr<nsIFile> file; |
michael@0 | 1820 | nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(request); |
michael@0 | 1821 | if (fc) { |
michael@0 | 1822 | fc->GetFile(getter_AddRefs(file)); |
michael@0 | 1823 | } |
michael@0 | 1824 | |
michael@0 | 1825 | if (!file) |
michael@0 | 1826 | return false; |
michael@0 | 1827 | |
michael@0 | 1828 | nsAutoCString contentType; |
michael@0 | 1829 | mChannel->GetContentType(contentType); |
michael@0 | 1830 | |
michael@0 | 1831 | mDOMFile = |
michael@0 | 1832 | new nsDOMFileFile(file, EmptyString(), NS_ConvertASCIItoUTF16(contentType)); |
michael@0 | 1833 | mBlobSet = nullptr; |
michael@0 | 1834 | NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty"); |
michael@0 | 1835 | return true; |
michael@0 | 1836 | } |
michael@0 | 1837 | |
michael@0 | 1838 | NS_IMETHODIMP |
michael@0 | 1839 | nsXMLHttpRequest::OnDataAvailable(nsIRequest *request, |
michael@0 | 1840 | nsISupports *ctxt, |
michael@0 | 1841 | nsIInputStream *inStr, |
michael@0 | 1842 | uint64_t sourceOffset, |
michael@0 | 1843 | uint32_t count) |
michael@0 | 1844 | { |
michael@0 | 1845 | NS_ENSURE_ARG_POINTER(inStr); |
michael@0 | 1846 | |
michael@0 | 1847 | NS_ABORT_IF_FALSE(mContext.get() == ctxt,"start context different from OnDataAvailable context"); |
michael@0 | 1848 | |
michael@0 | 1849 | mProgressSinceLastProgressEvent = true; |
michael@0 | 1850 | |
michael@0 | 1851 | bool cancelable = false; |
michael@0 | 1852 | if ((mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB || |
michael@0 | 1853 | mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) && !mDOMFile) { |
michael@0 | 1854 | cancelable = CreateDOMFile(request); |
michael@0 | 1855 | // The nsIStreamListener contract mandates us |
michael@0 | 1856 | // to read from the stream before returning. |
michael@0 | 1857 | } |
michael@0 | 1858 | |
michael@0 | 1859 | uint32_t totalRead; |
michael@0 | 1860 | nsresult rv = inStr->ReadSegments(nsXMLHttpRequest::StreamReaderFunc, |
michael@0 | 1861 | (void*)this, count, &totalRead); |
michael@0 | 1862 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1863 | |
michael@0 | 1864 | if (cancelable) { |
michael@0 | 1865 | // We don't have to read from the local file for the blob response |
michael@0 | 1866 | mDOMFile->GetSize(&mLoadTransferred); |
michael@0 | 1867 | ChangeState(XML_HTTP_REQUEST_LOADING); |
michael@0 | 1868 | return request->Cancel(NS_OK); |
michael@0 | 1869 | } |
michael@0 | 1870 | |
michael@0 | 1871 | mLoadTransferred += totalRead; |
michael@0 | 1872 | |
michael@0 | 1873 | ChangeState(XML_HTTP_REQUEST_LOADING); |
michael@0 | 1874 | |
michael@0 | 1875 | MaybeDispatchProgressEvents(false); |
michael@0 | 1876 | |
michael@0 | 1877 | return NS_OK; |
michael@0 | 1878 | } |
michael@0 | 1879 | |
michael@0 | 1880 | /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */ |
michael@0 | 1881 | NS_IMETHODIMP |
michael@0 | 1882 | nsXMLHttpRequest::OnStartRequest(nsIRequest *request, nsISupports *ctxt) |
michael@0 | 1883 | { |
michael@0 | 1884 | PROFILER_LABEL("nsXMLHttpRequest", "OnStartRequest"); |
michael@0 | 1885 | nsresult rv = NS_OK; |
michael@0 | 1886 | if (!mFirstStartRequestSeen && mRequestObserver) { |
michael@0 | 1887 | mFirstStartRequestSeen = true; |
michael@0 | 1888 | mRequestObserver->OnStartRequest(request, ctxt); |
michael@0 | 1889 | } |
michael@0 | 1890 | |
michael@0 | 1891 | if (request != mChannel) { |
michael@0 | 1892 | // Can this still happen? |
michael@0 | 1893 | return NS_OK; |
michael@0 | 1894 | } |
michael@0 | 1895 | |
michael@0 | 1896 | // Don't do anything if we have been aborted |
michael@0 | 1897 | if (mState & XML_HTTP_REQUEST_UNSENT) |
michael@0 | 1898 | return NS_OK; |
michael@0 | 1899 | |
michael@0 | 1900 | /* Apparently, Abort() should set XML_HTTP_REQUEST_UNSENT. See bug 361773. |
michael@0 | 1901 | XHR2 spec says this is correct. */ |
michael@0 | 1902 | if (mState & XML_HTTP_REQUEST_ABORTED) { |
michael@0 | 1903 | NS_ERROR("Ugh, still getting data on an aborted XMLHttpRequest!"); |
michael@0 | 1904 | |
michael@0 | 1905 | return NS_ERROR_UNEXPECTED; |
michael@0 | 1906 | } |
michael@0 | 1907 | |
michael@0 | 1908 | // Don't do anything if we have timed out. |
michael@0 | 1909 | if (mState & XML_HTTP_REQUEST_TIMED_OUT) { |
michael@0 | 1910 | return NS_OK; |
michael@0 | 1911 | } |
michael@0 | 1912 | |
michael@0 | 1913 | nsCOMPtr<nsIChannel> channel(do_QueryInterface(request)); |
michael@0 | 1914 | NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED); |
michael@0 | 1915 | |
michael@0 | 1916 | nsCOMPtr<nsIPrincipal> documentPrincipal; |
michael@0 | 1917 | if (IsSystemXHR()) { |
michael@0 | 1918 | // Don't give this document the system principal. We need to keep track of |
michael@0 | 1919 | // mPrincipal being system because we use it for various security checks |
michael@0 | 1920 | // that should be passing, but the document data shouldn't get a system |
michael@0 | 1921 | // principal. |
michael@0 | 1922 | nsresult rv; |
michael@0 | 1923 | documentPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); |
michael@0 | 1924 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1925 | } else { |
michael@0 | 1926 | documentPrincipal = mPrincipal; |
michael@0 | 1927 | } |
michael@0 | 1928 | |
michael@0 | 1929 | channel->SetOwner(documentPrincipal); |
michael@0 | 1930 | |
michael@0 | 1931 | nsresult status; |
michael@0 | 1932 | request->GetStatus(&status); |
michael@0 | 1933 | mErrorLoad = mErrorLoad || NS_FAILED(status); |
michael@0 | 1934 | |
michael@0 | 1935 | if (mUpload && !mUploadComplete && !mErrorLoad && |
michael@0 | 1936 | (mState & XML_HTTP_REQUEST_ASYNC)) { |
michael@0 | 1937 | if (mProgressTimerIsActive) { |
michael@0 | 1938 | mProgressTimerIsActive = false; |
michael@0 | 1939 | mProgressNotifier->Cancel(); |
michael@0 | 1940 | } |
michael@0 | 1941 | if (mUploadTransferred < mUploadTotal) { |
michael@0 | 1942 | mUploadTransferred = mUploadTotal; |
michael@0 | 1943 | mProgressSinceLastProgressEvent = true; |
michael@0 | 1944 | mUploadLengthComputable = true; |
michael@0 | 1945 | MaybeDispatchProgressEvents(true); |
michael@0 | 1946 | } |
michael@0 | 1947 | mUploadComplete = true; |
michael@0 | 1948 | DispatchProgressEvent(mUpload, NS_LITERAL_STRING(LOAD_STR), |
michael@0 | 1949 | true, mUploadTotal, mUploadTotal); |
michael@0 | 1950 | } |
michael@0 | 1951 | |
michael@0 | 1952 | mContext = ctxt; |
michael@0 | 1953 | mState |= XML_HTTP_REQUEST_PARSEBODY; |
michael@0 | 1954 | ChangeState(XML_HTTP_REQUEST_HEADERS_RECEIVED); |
michael@0 | 1955 | |
michael@0 | 1956 | ResetResponse(); |
michael@0 | 1957 | |
michael@0 | 1958 | if (!mOverrideMimeType.IsEmpty()) { |
michael@0 | 1959 | channel->SetContentType(NS_ConvertUTF16toUTF8(mOverrideMimeType)); |
michael@0 | 1960 | } |
michael@0 | 1961 | |
michael@0 | 1962 | DetectCharset(); |
michael@0 | 1963 | |
michael@0 | 1964 | // Set up arraybuffer |
michael@0 | 1965 | if (mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER && NS_SUCCEEDED(status)) { |
michael@0 | 1966 | int64_t contentLength; |
michael@0 | 1967 | rv = channel->GetContentLength(&contentLength); |
michael@0 | 1968 | if (NS_SUCCEEDED(rv) && |
michael@0 | 1969 | contentLength > 0 && |
michael@0 | 1970 | contentLength < XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE) { |
michael@0 | 1971 | mArrayBufferBuilder.setCapacity(static_cast<int32_t>(contentLength)); |
michael@0 | 1972 | } |
michael@0 | 1973 | } |
michael@0 | 1974 | |
michael@0 | 1975 | // Set up responseXML |
michael@0 | 1976 | bool parseBody = mResponseType == XML_HTTP_RESPONSE_TYPE_DEFAULT || |
michael@0 | 1977 | mResponseType == XML_HTTP_RESPONSE_TYPE_DOCUMENT; |
michael@0 | 1978 | nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel)); |
michael@0 | 1979 | if (parseBody && httpChannel) { |
michael@0 | 1980 | nsAutoCString method; |
michael@0 | 1981 | httpChannel->GetRequestMethod(method); |
michael@0 | 1982 | parseBody = !method.EqualsLiteral("HEAD"); |
michael@0 | 1983 | } |
michael@0 | 1984 | |
michael@0 | 1985 | mIsHtml = false; |
michael@0 | 1986 | mWarnAboutSyncHtml = false; |
michael@0 | 1987 | if (parseBody && NS_SUCCEEDED(status)) { |
michael@0 | 1988 | // We can gain a huge performance win by not even trying to |
michael@0 | 1989 | // parse non-XML data. This also protects us from the situation |
michael@0 | 1990 | // where we have an XML document and sink, but HTML (or other) |
michael@0 | 1991 | // parser, which can produce unreliable results. |
michael@0 | 1992 | nsAutoCString type; |
michael@0 | 1993 | channel->GetContentType(type); |
michael@0 | 1994 | |
michael@0 | 1995 | if ((mResponseType == XML_HTTP_RESPONSE_TYPE_DOCUMENT) && |
michael@0 | 1996 | type.EqualsLiteral("text/html")) { |
michael@0 | 1997 | // HTML parsing is only supported for responseType == "document" to |
michael@0 | 1998 | // avoid running the parser and, worse, populating responseXML for |
michael@0 | 1999 | // legacy users of XHR who use responseType == "" for retrieving the |
michael@0 | 2000 | // responseText of text/html resources. This legacy case is so common |
michael@0 | 2001 | // that it's not useful to emit a warning about it. |
michael@0 | 2002 | if (!(mState & XML_HTTP_REQUEST_ASYNC)) { |
michael@0 | 2003 | // We don't make cool new features available in the bad synchronous |
michael@0 | 2004 | // mode. The synchronous mode is for legacy only. |
michael@0 | 2005 | mWarnAboutSyncHtml = true; |
michael@0 | 2006 | mState &= ~XML_HTTP_REQUEST_PARSEBODY; |
michael@0 | 2007 | } else { |
michael@0 | 2008 | mIsHtml = true; |
michael@0 | 2009 | } |
michael@0 | 2010 | } else if (type.Find("xml") == kNotFound) { |
michael@0 | 2011 | mState &= ~XML_HTTP_REQUEST_PARSEBODY; |
michael@0 | 2012 | } |
michael@0 | 2013 | } else { |
michael@0 | 2014 | // The request failed, so we shouldn't be parsing anyway |
michael@0 | 2015 | mState &= ~XML_HTTP_REQUEST_PARSEBODY; |
michael@0 | 2016 | } |
michael@0 | 2017 | |
michael@0 | 2018 | if (mState & XML_HTTP_REQUEST_PARSEBODY) { |
michael@0 | 2019 | nsCOMPtr<nsIURI> baseURI, docURI; |
michael@0 | 2020 | rv = mChannel->GetURI(getter_AddRefs(docURI)); |
michael@0 | 2021 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2022 | baseURI = docURI; |
michael@0 | 2023 | |
michael@0 | 2024 | nsIScriptContext* sc = GetContextForEventHandlers(&rv); |
michael@0 | 2025 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2026 | nsCOMPtr<nsIDocument> doc = |
michael@0 | 2027 | nsContentUtils::GetDocumentFromScriptContext(sc); |
michael@0 | 2028 | nsCOMPtr<nsIURI> chromeXHRDocURI, chromeXHRDocBaseURI; |
michael@0 | 2029 | if (doc) { |
michael@0 | 2030 | chromeXHRDocURI = doc->GetDocumentURI(); |
michael@0 | 2031 | chromeXHRDocBaseURI = doc->GetBaseURI(); |
michael@0 | 2032 | } |
michael@0 | 2033 | |
michael@0 | 2034 | // Create an empty document from it. Here we have to cheat a little bit... |
michael@0 | 2035 | // Setting the base URI to |baseURI| won't work if the document has a null |
michael@0 | 2036 | // principal, so use mPrincipal when creating the document, then reset the |
michael@0 | 2037 | // principal. |
michael@0 | 2038 | const nsAString& emptyStr = EmptyString(); |
michael@0 | 2039 | nsCOMPtr<nsIDOMDocument> responseDoc; |
michael@0 | 2040 | nsIGlobalObject* global = DOMEventTargetHelper::GetParentObject(); |
michael@0 | 2041 | rv = NS_NewDOMDocument(getter_AddRefs(responseDoc), |
michael@0 | 2042 | emptyStr, emptyStr, nullptr, docURI, |
michael@0 | 2043 | baseURI, mPrincipal, true, global, |
michael@0 | 2044 | mIsHtml ? DocumentFlavorHTML : |
michael@0 | 2045 | DocumentFlavorLegacyGuess); |
michael@0 | 2046 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2047 | mResponseXML = do_QueryInterface(responseDoc); |
michael@0 | 2048 | mResponseXML->SetPrincipal(documentPrincipal); |
michael@0 | 2049 | mResponseXML->SetChromeXHRDocURI(chromeXHRDocURI); |
michael@0 | 2050 | mResponseXML->SetChromeXHRDocBaseURI(chromeXHRDocBaseURI); |
michael@0 | 2051 | |
michael@0 | 2052 | if (nsContentUtils::IsSystemPrincipal(mPrincipal)) { |
michael@0 | 2053 | mResponseXML->ForceEnableXULXBL(); |
michael@0 | 2054 | } |
michael@0 | 2055 | |
michael@0 | 2056 | if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) { |
michael@0 | 2057 | nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(mResponseXML); |
michael@0 | 2058 | if (htmlDoc) { |
michael@0 | 2059 | htmlDoc->DisableCookieAccess(); |
michael@0 | 2060 | } |
michael@0 | 2061 | } |
michael@0 | 2062 | |
michael@0 | 2063 | nsCOMPtr<nsIStreamListener> listener; |
michael@0 | 2064 | nsCOMPtr<nsILoadGroup> loadGroup; |
michael@0 | 2065 | channel->GetLoadGroup(getter_AddRefs(loadGroup)); |
michael@0 | 2066 | |
michael@0 | 2067 | rv = mResponseXML->StartDocumentLoad(kLoadAsData, channel, loadGroup, |
michael@0 | 2068 | nullptr, getter_AddRefs(listener), |
michael@0 | 2069 | !(mState & XML_HTTP_REQUEST_USE_XSITE_AC)); |
michael@0 | 2070 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2071 | |
michael@0 | 2072 | mXMLParserStreamListener = listener; |
michael@0 | 2073 | rv = mXMLParserStreamListener->OnStartRequest(request, ctxt); |
michael@0 | 2074 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2075 | } |
michael@0 | 2076 | |
michael@0 | 2077 | // We won't get any progress events anyway if we didn't have progress |
michael@0 | 2078 | // events when starting the request - so maybe no need to start timer here. |
michael@0 | 2079 | if (NS_SUCCEEDED(rv) && |
michael@0 | 2080 | (mState & XML_HTTP_REQUEST_ASYNC) && |
michael@0 | 2081 | HasListenersFor(nsGkAtoms::onprogress)) { |
michael@0 | 2082 | StartProgressEventTimer(); |
michael@0 | 2083 | } |
michael@0 | 2084 | |
michael@0 | 2085 | return NS_OK; |
michael@0 | 2086 | } |
michael@0 | 2087 | |
michael@0 | 2088 | /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status, in wstring statusArg); */ |
michael@0 | 2089 | NS_IMETHODIMP |
michael@0 | 2090 | nsXMLHttpRequest::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status) |
michael@0 | 2091 | { |
michael@0 | 2092 | PROFILER_LABEL("content", "nsXMLHttpRequest::OnStopRequest"); |
michael@0 | 2093 | if (request != mChannel) { |
michael@0 | 2094 | // Can this still happen? |
michael@0 | 2095 | return NS_OK; |
michael@0 | 2096 | } |
michael@0 | 2097 | |
michael@0 | 2098 | mWaitingForOnStopRequest = false; |
michael@0 | 2099 | |
michael@0 | 2100 | if (mRequestObserver) { |
michael@0 | 2101 | NS_ASSERTION(mFirstStartRequestSeen, "Inconsistent state!"); |
michael@0 | 2102 | mFirstStartRequestSeen = false; |
michael@0 | 2103 | mRequestObserver->OnStopRequest(request, ctxt, status); |
michael@0 | 2104 | } |
michael@0 | 2105 | |
michael@0 | 2106 | // make sure to notify the listener if we were aborted |
michael@0 | 2107 | // XXX in fact, why don't we do the cleanup below in this case?? |
michael@0 | 2108 | // XML_HTTP_REQUEST_UNSENT is for abort calls. See OnStartRequest above. |
michael@0 | 2109 | if ((mState & XML_HTTP_REQUEST_UNSENT) || |
michael@0 | 2110 | (mState & XML_HTTP_REQUEST_TIMED_OUT)) { |
michael@0 | 2111 | if (mXMLParserStreamListener) |
michael@0 | 2112 | (void) mXMLParserStreamListener->OnStopRequest(request, ctxt, status); |
michael@0 | 2113 | return NS_OK; |
michael@0 | 2114 | } |
michael@0 | 2115 | |
michael@0 | 2116 | // Is this good enough here? |
michael@0 | 2117 | if (mState & XML_HTTP_REQUEST_PARSEBODY && mXMLParserStreamListener) { |
michael@0 | 2118 | mXMLParserStreamListener->OnStopRequest(request, ctxt, status); |
michael@0 | 2119 | } |
michael@0 | 2120 | |
michael@0 | 2121 | mXMLParserStreamListener = nullptr; |
michael@0 | 2122 | mContext = nullptr; |
michael@0 | 2123 | |
michael@0 | 2124 | // If we're received data since the last progress event, make sure to fire |
michael@0 | 2125 | // an event for it, except in the HTML case, defer the last progress event |
michael@0 | 2126 | // until the parser is done. |
michael@0 | 2127 | if (!mIsHtml) { |
michael@0 | 2128 | MaybeDispatchProgressEvents(true); |
michael@0 | 2129 | } |
michael@0 | 2130 | |
michael@0 | 2131 | if (NS_SUCCEEDED(status) && |
michael@0 | 2132 | (mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB || |
michael@0 | 2133 | mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB)) { |
michael@0 | 2134 | if (!mDOMFile) { |
michael@0 | 2135 | CreateDOMFile(request); |
michael@0 | 2136 | } |
michael@0 | 2137 | if (mDOMFile) { |
michael@0 | 2138 | mResponseBlob = mDOMFile; |
michael@0 | 2139 | mDOMFile = nullptr; |
michael@0 | 2140 | } else { |
michael@0 | 2141 | // mBlobSet can be null if the channel is non-file non-cacheable |
michael@0 | 2142 | // and if the response length is zero. |
michael@0 | 2143 | if (!mBlobSet) { |
michael@0 | 2144 | mBlobSet = new BlobSet(); |
michael@0 | 2145 | } |
michael@0 | 2146 | // Smaller files may be written in cache map instead of separate files. |
michael@0 | 2147 | // Also, no-store response cannot be written in persistent cache. |
michael@0 | 2148 | nsAutoCString contentType; |
michael@0 | 2149 | mChannel->GetContentType(contentType); |
michael@0 | 2150 | mResponseBlob = mBlobSet->GetBlobInternal(contentType); |
michael@0 | 2151 | mBlobSet = nullptr; |
michael@0 | 2152 | } |
michael@0 | 2153 | NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty"); |
michael@0 | 2154 | NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty"); |
michael@0 | 2155 | } else if (NS_SUCCEEDED(status) && |
michael@0 | 2156 | (mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER || |
michael@0 | 2157 | mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER)) { |
michael@0 | 2158 | // set the capacity down to the actual length, to realloc back |
michael@0 | 2159 | // down to the actual size |
michael@0 | 2160 | if (!mArrayBufferBuilder.setCapacity(mArrayBufferBuilder.length())) { |
michael@0 | 2161 | // this should never happen! |
michael@0 | 2162 | status = NS_ERROR_UNEXPECTED; |
michael@0 | 2163 | } |
michael@0 | 2164 | } |
michael@0 | 2165 | |
michael@0 | 2166 | nsCOMPtr<nsIChannel> channel(do_QueryInterface(request)); |
michael@0 | 2167 | NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED); |
michael@0 | 2168 | |
michael@0 | 2169 | channel->SetNotificationCallbacks(nullptr); |
michael@0 | 2170 | mNotificationCallbacks = nullptr; |
michael@0 | 2171 | mChannelEventSink = nullptr; |
michael@0 | 2172 | mProgressEventSink = nullptr; |
michael@0 | 2173 | |
michael@0 | 2174 | mState &= ~XML_HTTP_REQUEST_SYNCLOOPING; |
michael@0 | 2175 | |
michael@0 | 2176 | if (NS_FAILED(status)) { |
michael@0 | 2177 | // This can happen if the server is unreachable. Other possible |
michael@0 | 2178 | // reasons are that the user leaves the page or hits the ESC key. |
michael@0 | 2179 | |
michael@0 | 2180 | mErrorLoad = true; |
michael@0 | 2181 | mResponseXML = nullptr; |
michael@0 | 2182 | } |
michael@0 | 2183 | |
michael@0 | 2184 | // If we're uninitialized at this point, we encountered an error |
michael@0 | 2185 | // earlier and listeners have already been notified. Also we do |
michael@0 | 2186 | // not want to do this if we already completed. |
michael@0 | 2187 | if (mState & (XML_HTTP_REQUEST_UNSENT | |
michael@0 | 2188 | XML_HTTP_REQUEST_DONE)) { |
michael@0 | 2189 | return NS_OK; |
michael@0 | 2190 | } |
michael@0 | 2191 | |
michael@0 | 2192 | if (!mResponseXML) { |
michael@0 | 2193 | ChangeStateToDone(); |
michael@0 | 2194 | return NS_OK; |
michael@0 | 2195 | } |
michael@0 | 2196 | if (mIsHtml) { |
michael@0 | 2197 | NS_ASSERTION(!(mState & XML_HTTP_REQUEST_SYNCLOOPING), |
michael@0 | 2198 | "We weren't supposed to support HTML parsing with XHR!"); |
michael@0 | 2199 | nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(mResponseXML); |
michael@0 | 2200 | EventListenerManager* manager = |
michael@0 | 2201 | eventTarget->GetOrCreateListenerManager(); |
michael@0 | 2202 | manager->AddEventListenerByType(new nsXHRParseEndListener(this), |
michael@0 | 2203 | NS_LITERAL_STRING("DOMContentLoaded"), |
michael@0 | 2204 | TrustedEventsAtSystemGroupBubble()); |
michael@0 | 2205 | return NS_OK; |
michael@0 | 2206 | } |
michael@0 | 2207 | // We might have been sent non-XML data. If that was the case, |
michael@0 | 2208 | // we should null out the document member. The idea in this |
michael@0 | 2209 | // check here is that if there is no document element it is not |
michael@0 | 2210 | // an XML document. We might need a fancier check... |
michael@0 | 2211 | if (!mResponseXML->GetRootElement()) { |
michael@0 | 2212 | mResponseXML = nullptr; |
michael@0 | 2213 | } |
michael@0 | 2214 | ChangeStateToDone(); |
michael@0 | 2215 | return NS_OK; |
michael@0 | 2216 | } |
michael@0 | 2217 | |
michael@0 | 2218 | void |
michael@0 | 2219 | nsXMLHttpRequest::ChangeStateToDone() |
michael@0 | 2220 | { |
michael@0 | 2221 | if (mIsHtml) { |
michael@0 | 2222 | // In the HTML case, this has to be deferred, because the parser doesn't |
michael@0 | 2223 | // do it's job synchronously. |
michael@0 | 2224 | MaybeDispatchProgressEvents(true); |
michael@0 | 2225 | } |
michael@0 | 2226 | |
michael@0 | 2227 | ChangeState(XML_HTTP_REQUEST_DONE, true); |
michael@0 | 2228 | if (mTimeoutTimer) { |
michael@0 | 2229 | mTimeoutTimer->Cancel(); |
michael@0 | 2230 | } |
michael@0 | 2231 | |
michael@0 | 2232 | NS_NAMED_LITERAL_STRING(errorStr, ERROR_STR); |
michael@0 | 2233 | NS_NAMED_LITERAL_STRING(loadStr, LOAD_STR); |
michael@0 | 2234 | DispatchProgressEvent(this, |
michael@0 | 2235 | mErrorLoad ? errorStr : loadStr, |
michael@0 | 2236 | !mErrorLoad, |
michael@0 | 2237 | mLoadTransferred, |
michael@0 | 2238 | mErrorLoad ? 0 : mLoadTransferred); |
michael@0 | 2239 | if (mErrorLoad && mUpload && !mUploadComplete) { |
michael@0 | 2240 | DispatchProgressEvent(mUpload, errorStr, true, |
michael@0 | 2241 | mUploadTransferred, mUploadTotal); |
michael@0 | 2242 | } |
michael@0 | 2243 | |
michael@0 | 2244 | if (mErrorLoad) { |
michael@0 | 2245 | // By nulling out channel here we make it so that Send() can test |
michael@0 | 2246 | // for that and throw. Also calling the various status |
michael@0 | 2247 | // methods/members will not throw. |
michael@0 | 2248 | // This matches what IE does. |
michael@0 | 2249 | mChannel = nullptr; |
michael@0 | 2250 | mCORSPreflightChannel = nullptr; |
michael@0 | 2251 | } |
michael@0 | 2252 | } |
michael@0 | 2253 | |
michael@0 | 2254 | NS_IMETHODIMP |
michael@0 | 2255 | nsXMLHttpRequest::SendAsBinary(const nsAString &aBody) |
michael@0 | 2256 | { |
michael@0 | 2257 | ErrorResult rv; |
michael@0 | 2258 | SendAsBinary(aBody, rv); |
michael@0 | 2259 | return rv.ErrorCode(); |
michael@0 | 2260 | } |
michael@0 | 2261 | |
michael@0 | 2262 | void |
michael@0 | 2263 | nsXMLHttpRequest::SendAsBinary(const nsAString &aBody, |
michael@0 | 2264 | ErrorResult& aRv) |
michael@0 | 2265 | { |
michael@0 | 2266 | char *data = static_cast<char*>(NS_Alloc(aBody.Length() + 1)); |
michael@0 | 2267 | if (!data) { |
michael@0 | 2268 | aRv.Throw(NS_ERROR_OUT_OF_MEMORY); |
michael@0 | 2269 | return; |
michael@0 | 2270 | } |
michael@0 | 2271 | |
michael@0 | 2272 | if (GetOwner() && GetOwner()->GetExtantDoc()) { |
michael@0 | 2273 | GetOwner()->GetExtantDoc()->WarnOnceAbout(nsIDocument::eSendAsBinary); |
michael@0 | 2274 | } |
michael@0 | 2275 | |
michael@0 | 2276 | nsAString::const_iterator iter, end; |
michael@0 | 2277 | aBody.BeginReading(iter); |
michael@0 | 2278 | aBody.EndReading(end); |
michael@0 | 2279 | char *p = data; |
michael@0 | 2280 | while (iter != end) { |
michael@0 | 2281 | if (*iter & 0xFF00) { |
michael@0 | 2282 | NS_Free(data); |
michael@0 | 2283 | aRv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR); |
michael@0 | 2284 | return; |
michael@0 | 2285 | } |
michael@0 | 2286 | *p++ = static_cast<char>(*iter++); |
michael@0 | 2287 | } |
michael@0 | 2288 | *p = '\0'; |
michael@0 | 2289 | |
michael@0 | 2290 | nsCOMPtr<nsIInputStream> stream; |
michael@0 | 2291 | aRv = NS_NewByteInputStream(getter_AddRefs(stream), data, aBody.Length(), |
michael@0 | 2292 | NS_ASSIGNMENT_ADOPT); |
michael@0 | 2293 | if (aRv.Failed()) { |
michael@0 | 2294 | NS_Free(data); |
michael@0 | 2295 | return; |
michael@0 | 2296 | } |
michael@0 | 2297 | |
michael@0 | 2298 | nsCOMPtr<nsIWritableVariant> variant = new nsVariant(); |
michael@0 | 2299 | |
michael@0 | 2300 | aRv = variant->SetAsISupports(stream); |
michael@0 | 2301 | if (aRv.Failed()) { |
michael@0 | 2302 | return; |
michael@0 | 2303 | } |
michael@0 | 2304 | |
michael@0 | 2305 | aRv = Send(variant); |
michael@0 | 2306 | } |
michael@0 | 2307 | |
michael@0 | 2308 | static nsresult |
michael@0 | 2309 | GetRequestBody(nsIDOMDocument* aDoc, nsIInputStream** aResult, |
michael@0 | 2310 | uint64_t* aContentLength, nsACString& aContentType, |
michael@0 | 2311 | nsACString& aCharset) |
michael@0 | 2312 | { |
michael@0 | 2313 | aContentType.AssignLiteral("application/xml"); |
michael@0 | 2314 | nsAutoString inputEncoding; |
michael@0 | 2315 | aDoc->GetInputEncoding(inputEncoding); |
michael@0 | 2316 | if (!DOMStringIsNull(inputEncoding)) { |
michael@0 | 2317 | CopyUTF16toUTF8(inputEncoding, aCharset); |
michael@0 | 2318 | } |
michael@0 | 2319 | else { |
michael@0 | 2320 | aCharset.AssignLiteral("UTF-8"); |
michael@0 | 2321 | } |
michael@0 | 2322 | |
michael@0 | 2323 | // Serialize to a stream so that the encoding used will |
michael@0 | 2324 | // match the document's. |
michael@0 | 2325 | nsresult rv; |
michael@0 | 2326 | nsCOMPtr<nsIDOMSerializer> serializer = |
michael@0 | 2327 | do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID, &rv); |
michael@0 | 2328 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2329 | |
michael@0 | 2330 | nsCOMPtr<nsIStorageStream> storStream; |
michael@0 | 2331 | rv = NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storStream)); |
michael@0 | 2332 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2333 | |
michael@0 | 2334 | nsCOMPtr<nsIOutputStream> output; |
michael@0 | 2335 | rv = storStream->GetOutputStream(0, getter_AddRefs(output)); |
michael@0 | 2336 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2337 | |
michael@0 | 2338 | // Make sure to use the encoding we'll send |
michael@0 | 2339 | rv = serializer->SerializeToStream(aDoc, output, aCharset); |
michael@0 | 2340 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2341 | |
michael@0 | 2342 | output->Close(); |
michael@0 | 2343 | |
michael@0 | 2344 | uint32_t length; |
michael@0 | 2345 | rv = storStream->GetLength(&length); |
michael@0 | 2346 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2347 | *aContentLength = length; |
michael@0 | 2348 | |
michael@0 | 2349 | return storStream->NewInputStream(0, aResult); |
michael@0 | 2350 | } |
michael@0 | 2351 | |
michael@0 | 2352 | static nsresult |
michael@0 | 2353 | GetRequestBody(const nsAString& aString, nsIInputStream** aResult, |
michael@0 | 2354 | uint64_t* aContentLength, nsACString& aContentType, |
michael@0 | 2355 | nsACString& aCharset) |
michael@0 | 2356 | { |
michael@0 | 2357 | aContentType.AssignLiteral("text/plain"); |
michael@0 | 2358 | aCharset.AssignLiteral("UTF-8"); |
michael@0 | 2359 | |
michael@0 | 2360 | nsCString converted = NS_ConvertUTF16toUTF8(aString); |
michael@0 | 2361 | *aContentLength = converted.Length(); |
michael@0 | 2362 | return NS_NewCStringInputStream(aResult, converted); |
michael@0 | 2363 | } |
michael@0 | 2364 | |
michael@0 | 2365 | static nsresult |
michael@0 | 2366 | GetRequestBody(nsIInputStream* aStream, nsIInputStream** aResult, |
michael@0 | 2367 | uint64_t* aContentLength, nsACString& aContentType, |
michael@0 | 2368 | nsACString& aCharset) |
michael@0 | 2369 | { |
michael@0 | 2370 | aContentType.AssignLiteral("text/plain"); |
michael@0 | 2371 | aCharset.Truncate(); |
michael@0 | 2372 | |
michael@0 | 2373 | nsresult rv = aStream->Available(aContentLength); |
michael@0 | 2374 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2375 | |
michael@0 | 2376 | NS_ADDREF(*aResult = aStream); |
michael@0 | 2377 | |
michael@0 | 2378 | return NS_OK; |
michael@0 | 2379 | } |
michael@0 | 2380 | |
michael@0 | 2381 | static nsresult |
michael@0 | 2382 | GetRequestBody(nsIXHRSendable* aSendable, nsIInputStream** aResult, uint64_t* aContentLength, |
michael@0 | 2383 | nsACString& aContentType, nsACString& aCharset) |
michael@0 | 2384 | { |
michael@0 | 2385 | return aSendable->GetSendInfo(aResult, aContentLength, aContentType, aCharset); |
michael@0 | 2386 | } |
michael@0 | 2387 | |
michael@0 | 2388 | // Used for array buffers and array buffer views |
michael@0 | 2389 | static nsresult |
michael@0 | 2390 | GetRequestBody(const uint8_t* aData, uint32_t aDataLength, |
michael@0 | 2391 | nsIInputStream** aResult, uint64_t* aContentLength, |
michael@0 | 2392 | nsACString& aContentType, nsACString& aCharset) |
michael@0 | 2393 | { |
michael@0 | 2394 | aContentType.SetIsVoid(true); |
michael@0 | 2395 | aCharset.Truncate(); |
michael@0 | 2396 | |
michael@0 | 2397 | *aContentLength = aDataLength; |
michael@0 | 2398 | const char* data = reinterpret_cast<const char*>(aData); |
michael@0 | 2399 | |
michael@0 | 2400 | nsCOMPtr<nsIInputStream> stream; |
michael@0 | 2401 | nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), data, aDataLength, |
michael@0 | 2402 | NS_ASSIGNMENT_COPY); |
michael@0 | 2403 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2404 | |
michael@0 | 2405 | stream.forget(aResult); |
michael@0 | 2406 | |
michael@0 | 2407 | return NS_OK; |
michael@0 | 2408 | } |
michael@0 | 2409 | |
michael@0 | 2410 | static nsresult |
michael@0 | 2411 | GetRequestBody(nsIVariant* aBody, nsIInputStream** aResult, uint64_t* aContentLength, |
michael@0 | 2412 | nsACString& aContentType, nsACString& aCharset) |
michael@0 | 2413 | { |
michael@0 | 2414 | *aResult = nullptr; |
michael@0 | 2415 | |
michael@0 | 2416 | uint16_t dataType; |
michael@0 | 2417 | nsresult rv = aBody->GetDataType(&dataType); |
michael@0 | 2418 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2419 | |
michael@0 | 2420 | if (dataType == nsIDataType::VTYPE_INTERFACE || |
michael@0 | 2421 | dataType == nsIDataType::VTYPE_INTERFACE_IS) { |
michael@0 | 2422 | nsCOMPtr<nsISupports> supports; |
michael@0 | 2423 | nsID *iid; |
michael@0 | 2424 | rv = aBody->GetAsInterface(&iid, getter_AddRefs(supports)); |
michael@0 | 2425 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2426 | |
michael@0 | 2427 | nsMemory::Free(iid); |
michael@0 | 2428 | |
michael@0 | 2429 | // document? |
michael@0 | 2430 | nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(supports); |
michael@0 | 2431 | if (doc) { |
michael@0 | 2432 | return GetRequestBody(doc, aResult, aContentLength, aContentType, aCharset); |
michael@0 | 2433 | } |
michael@0 | 2434 | |
michael@0 | 2435 | // nsISupportsString? |
michael@0 | 2436 | nsCOMPtr<nsISupportsString> wstr = do_QueryInterface(supports); |
michael@0 | 2437 | if (wstr) { |
michael@0 | 2438 | nsAutoString string; |
michael@0 | 2439 | wstr->GetData(string); |
michael@0 | 2440 | |
michael@0 | 2441 | return GetRequestBody(string, aResult, aContentLength, aContentType, aCharset); |
michael@0 | 2442 | } |
michael@0 | 2443 | |
michael@0 | 2444 | // nsIInputStream? |
michael@0 | 2445 | nsCOMPtr<nsIInputStream> stream = do_QueryInterface(supports); |
michael@0 | 2446 | if (stream) { |
michael@0 | 2447 | return GetRequestBody(stream, aResult, aContentLength, aContentType, aCharset); |
michael@0 | 2448 | } |
michael@0 | 2449 | |
michael@0 | 2450 | // nsIXHRSendable? |
michael@0 | 2451 | nsCOMPtr<nsIXHRSendable> sendable = do_QueryInterface(supports); |
michael@0 | 2452 | if (sendable) { |
michael@0 | 2453 | return GetRequestBody(sendable, aResult, aContentLength, aContentType, aCharset); |
michael@0 | 2454 | } |
michael@0 | 2455 | |
michael@0 | 2456 | // ArrayBuffer? |
michael@0 | 2457 | AutoSafeJSContext cx; |
michael@0 | 2458 | JS::Rooted<JS::Value> realVal(cx); |
michael@0 | 2459 | |
michael@0 | 2460 | nsresult rv = aBody->GetAsJSVal(&realVal); |
michael@0 | 2461 | if (NS_SUCCEEDED(rv) && !JSVAL_IS_PRIMITIVE(realVal)) { |
michael@0 | 2462 | JS::Rooted<JSObject*> obj(cx, JSVAL_TO_OBJECT(realVal)); |
michael@0 | 2463 | if (JS_IsArrayBufferObject(obj)) { |
michael@0 | 2464 | ArrayBuffer buf(obj); |
michael@0 | 2465 | buf.ComputeLengthAndData(); |
michael@0 | 2466 | return GetRequestBody(buf.Data(), buf.Length(), aResult, |
michael@0 | 2467 | aContentLength, aContentType, aCharset); |
michael@0 | 2468 | } |
michael@0 | 2469 | } |
michael@0 | 2470 | } |
michael@0 | 2471 | else if (dataType == nsIDataType::VTYPE_VOID || |
michael@0 | 2472 | dataType == nsIDataType::VTYPE_EMPTY) { |
michael@0 | 2473 | // Makes us act as if !aBody, don't upload anything |
michael@0 | 2474 | aContentType.AssignLiteral("text/plain"); |
michael@0 | 2475 | aCharset.AssignLiteral("UTF-8"); |
michael@0 | 2476 | *aContentLength = 0; |
michael@0 | 2477 | |
michael@0 | 2478 | return NS_OK; |
michael@0 | 2479 | } |
michael@0 | 2480 | |
michael@0 | 2481 | char16_t* data = nullptr; |
michael@0 | 2482 | uint32_t len = 0; |
michael@0 | 2483 | rv = aBody->GetAsWStringWithSize(&len, &data); |
michael@0 | 2484 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2485 | |
michael@0 | 2486 | nsString string; |
michael@0 | 2487 | string.Adopt(data, len); |
michael@0 | 2488 | |
michael@0 | 2489 | return GetRequestBody(string, aResult, aContentLength, aContentType, aCharset); |
michael@0 | 2490 | } |
michael@0 | 2491 | |
michael@0 | 2492 | /* static */ |
michael@0 | 2493 | nsresult |
michael@0 | 2494 | nsXMLHttpRequest::GetRequestBody(nsIVariant* aVariant, |
michael@0 | 2495 | const Nullable<RequestBody>& aBody, |
michael@0 | 2496 | nsIInputStream** aResult, |
michael@0 | 2497 | uint64_t* aContentLength, |
michael@0 | 2498 | nsACString& aContentType, nsACString& aCharset) |
michael@0 | 2499 | { |
michael@0 | 2500 | if (aVariant) { |
michael@0 | 2501 | return ::GetRequestBody(aVariant, aResult, aContentLength, aContentType, aCharset); |
michael@0 | 2502 | } |
michael@0 | 2503 | |
michael@0 | 2504 | const RequestBody& body = aBody.Value(); |
michael@0 | 2505 | RequestBody::Value value = body.GetValue(); |
michael@0 | 2506 | switch (body.GetType()) { |
michael@0 | 2507 | case nsXMLHttpRequest::RequestBody::ArrayBuffer: |
michael@0 | 2508 | { |
michael@0 | 2509 | const ArrayBuffer* buffer = value.mArrayBuffer; |
michael@0 | 2510 | buffer->ComputeLengthAndData(); |
michael@0 | 2511 | return ::GetRequestBody(buffer->Data(), buffer->Length(), aResult, |
michael@0 | 2512 | aContentLength, aContentType, aCharset); |
michael@0 | 2513 | } |
michael@0 | 2514 | case nsXMLHttpRequest::RequestBody::ArrayBufferView: |
michael@0 | 2515 | { |
michael@0 | 2516 | const ArrayBufferView* view = value.mArrayBufferView; |
michael@0 | 2517 | view->ComputeLengthAndData(); |
michael@0 | 2518 | return ::GetRequestBody(view->Data(), view->Length(), aResult, |
michael@0 | 2519 | aContentLength, aContentType, aCharset); |
michael@0 | 2520 | } |
michael@0 | 2521 | case nsXMLHttpRequest::RequestBody::Blob: |
michael@0 | 2522 | { |
michael@0 | 2523 | nsresult rv; |
michael@0 | 2524 | nsCOMPtr<nsIXHRSendable> sendable = do_QueryInterface(value.mBlob, &rv); |
michael@0 | 2525 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2526 | |
michael@0 | 2527 | return ::GetRequestBody(sendable, aResult, aContentLength, aContentType, aCharset); |
michael@0 | 2528 | } |
michael@0 | 2529 | case nsXMLHttpRequest::RequestBody::Document: |
michael@0 | 2530 | { |
michael@0 | 2531 | nsCOMPtr<nsIDOMDocument> document = do_QueryInterface(value.mDocument); |
michael@0 | 2532 | return ::GetRequestBody(document, aResult, aContentLength, aContentType, aCharset); |
michael@0 | 2533 | } |
michael@0 | 2534 | case nsXMLHttpRequest::RequestBody::DOMString: |
michael@0 | 2535 | { |
michael@0 | 2536 | return ::GetRequestBody(*value.mString, aResult, aContentLength, |
michael@0 | 2537 | aContentType, aCharset); |
michael@0 | 2538 | } |
michael@0 | 2539 | case nsXMLHttpRequest::RequestBody::FormData: |
michael@0 | 2540 | { |
michael@0 | 2541 | MOZ_ASSERT(value.mFormData); |
michael@0 | 2542 | return ::GetRequestBody(value.mFormData, aResult, aContentLength, |
michael@0 | 2543 | aContentType, aCharset); |
michael@0 | 2544 | } |
michael@0 | 2545 | case nsXMLHttpRequest::RequestBody::InputStream: |
michael@0 | 2546 | { |
michael@0 | 2547 | return ::GetRequestBody(value.mStream, aResult, aContentLength, |
michael@0 | 2548 | aContentType, aCharset); |
michael@0 | 2549 | } |
michael@0 | 2550 | default: |
michael@0 | 2551 | { |
michael@0 | 2552 | return NS_ERROR_FAILURE; |
michael@0 | 2553 | } |
michael@0 | 2554 | } |
michael@0 | 2555 | |
michael@0 | 2556 | NS_NOTREACHED("Default cases exist for a reason"); |
michael@0 | 2557 | return NS_OK; |
michael@0 | 2558 | } |
michael@0 | 2559 | |
michael@0 | 2560 | /* void send (in nsIVariant aBody); */ |
michael@0 | 2561 | NS_IMETHODIMP |
michael@0 | 2562 | nsXMLHttpRequest::Send(nsIVariant *aBody) |
michael@0 | 2563 | { |
michael@0 | 2564 | return Send(aBody, Nullable<RequestBody>()); |
michael@0 | 2565 | } |
michael@0 | 2566 | |
michael@0 | 2567 | nsresult |
michael@0 | 2568 | nsXMLHttpRequest::Send(nsIVariant* aVariant, const Nullable<RequestBody>& aBody) |
michael@0 | 2569 | { |
michael@0 | 2570 | NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED); |
michael@0 | 2571 | |
michael@0 | 2572 | nsresult rv = CheckInnerWindowCorrectness(); |
michael@0 | 2573 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2574 | |
michael@0 | 2575 | // Return error if we're already processing a request |
michael@0 | 2576 | if (XML_HTTP_REQUEST_SENT & mState) { |
michael@0 | 2577 | return NS_ERROR_FAILURE; |
michael@0 | 2578 | } |
michael@0 | 2579 | |
michael@0 | 2580 | // Make sure we've been opened |
michael@0 | 2581 | if (!mChannel || !(XML_HTTP_REQUEST_OPENED & mState)) { |
michael@0 | 2582 | return NS_ERROR_NOT_INITIALIZED; |
michael@0 | 2583 | } |
michael@0 | 2584 | |
michael@0 | 2585 | |
michael@0 | 2586 | // nsIRequest::LOAD_BACKGROUND prevents throbber from becoming active, which |
michael@0 | 2587 | // in turn keeps STOP button from becoming active. If the consumer passed in |
michael@0 | 2588 | // a progress event handler we must load with nsIRequest::LOAD_NORMAL or |
michael@0 | 2589 | // necko won't generate any progress notifications. |
michael@0 | 2590 | if (HasListenersFor(nsGkAtoms::onprogress) || |
michael@0 | 2591 | (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress))) { |
michael@0 | 2592 | nsLoadFlags loadFlags; |
michael@0 | 2593 | mChannel->GetLoadFlags(&loadFlags); |
michael@0 | 2594 | loadFlags &= ~nsIRequest::LOAD_BACKGROUND; |
michael@0 | 2595 | loadFlags |= nsIRequest::LOAD_NORMAL; |
michael@0 | 2596 | mChannel->SetLoadFlags(loadFlags); |
michael@0 | 2597 | } |
michael@0 | 2598 | |
michael@0 | 2599 | // XXX We should probably send a warning to the JS console |
michael@0 | 2600 | // if there are no event listeners set and we are doing |
michael@0 | 2601 | // an asynchronous call. |
michael@0 | 2602 | |
michael@0 | 2603 | // Ignore argument if method is GET, there is no point in trying to |
michael@0 | 2604 | // upload anything |
michael@0 | 2605 | nsAutoCString method; |
michael@0 | 2606 | nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel)); |
michael@0 | 2607 | |
michael@0 | 2608 | if (httpChannel) { |
michael@0 | 2609 | httpChannel->GetRequestMethod(method); // If GET, method name will be uppercase |
michael@0 | 2610 | |
michael@0 | 2611 | if (!IsSystemXHR()) { |
michael@0 | 2612 | // Get the referrer for the request. |
michael@0 | 2613 | // |
michael@0 | 2614 | // If it weren't for history.push/replaceState, we could just use the |
michael@0 | 2615 | // principal's URI here. But since we want changes to the URI effected |
michael@0 | 2616 | // by push/replaceState to be reflected in the XHR referrer, we have to |
michael@0 | 2617 | // be more clever. |
michael@0 | 2618 | // |
michael@0 | 2619 | // If the document's original URI (before any push/replaceStates) matches |
michael@0 | 2620 | // our principal, then we use the document's current URI (after |
michael@0 | 2621 | // push/replaceStates). Otherwise (if the document is, say, a data: |
michael@0 | 2622 | // URI), we just use the principal's URI. |
michael@0 | 2623 | |
michael@0 | 2624 | nsCOMPtr<nsIURI> principalURI; |
michael@0 | 2625 | mPrincipal->GetURI(getter_AddRefs(principalURI)); |
michael@0 | 2626 | |
michael@0 | 2627 | nsIScriptContext* sc = GetContextForEventHandlers(&rv); |
michael@0 | 2628 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2629 | nsCOMPtr<nsIDocument> doc = |
michael@0 | 2630 | nsContentUtils::GetDocumentFromScriptContext(sc); |
michael@0 | 2631 | |
michael@0 | 2632 | nsCOMPtr<nsIURI> docCurURI; |
michael@0 | 2633 | nsCOMPtr<nsIURI> docOrigURI; |
michael@0 | 2634 | if (doc) { |
michael@0 | 2635 | docCurURI = doc->GetDocumentURI(); |
michael@0 | 2636 | docOrigURI = doc->GetOriginalURI(); |
michael@0 | 2637 | } |
michael@0 | 2638 | |
michael@0 | 2639 | nsCOMPtr<nsIURI> referrerURI; |
michael@0 | 2640 | |
michael@0 | 2641 | if (principalURI && docCurURI && docOrigURI) { |
michael@0 | 2642 | bool equal = false; |
michael@0 | 2643 | principalURI->Equals(docOrigURI, &equal); |
michael@0 | 2644 | if (equal) { |
michael@0 | 2645 | referrerURI = docCurURI; |
michael@0 | 2646 | } |
michael@0 | 2647 | } |
michael@0 | 2648 | |
michael@0 | 2649 | if (!referrerURI) |
michael@0 | 2650 | referrerURI = principalURI; |
michael@0 | 2651 | |
michael@0 | 2652 | httpChannel->SetReferrer(referrerURI); |
michael@0 | 2653 | } |
michael@0 | 2654 | |
michael@0 | 2655 | // Some extensions override the http protocol handler and provide their own |
michael@0 | 2656 | // implementation. The channels returned from that implementation doesn't |
michael@0 | 2657 | // seem to always implement the nsIUploadChannel2 interface, presumably |
michael@0 | 2658 | // because it's a new interface. |
michael@0 | 2659 | // Eventually we should remove this and simply require that http channels |
michael@0 | 2660 | // implement the new interface. |
michael@0 | 2661 | // See bug 529041 |
michael@0 | 2662 | nsCOMPtr<nsIUploadChannel2> uploadChannel2 = |
michael@0 | 2663 | do_QueryInterface(httpChannel); |
michael@0 | 2664 | if (!uploadChannel2) { |
michael@0 | 2665 | nsCOMPtr<nsIConsoleService> consoleService = |
michael@0 | 2666 | do_GetService(NS_CONSOLESERVICE_CONTRACTID); |
michael@0 | 2667 | if (consoleService) { |
michael@0 | 2668 | consoleService->LogStringMessage(NS_LITERAL_STRING( |
michael@0 | 2669 | "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." |
michael@0 | 2670 | ).get()); |
michael@0 | 2671 | } |
michael@0 | 2672 | } |
michael@0 | 2673 | } |
michael@0 | 2674 | |
michael@0 | 2675 | mUploadTransferred = 0; |
michael@0 | 2676 | mUploadTotal = 0; |
michael@0 | 2677 | // By default we don't have any upload, so mark upload complete. |
michael@0 | 2678 | mUploadComplete = true; |
michael@0 | 2679 | mErrorLoad = false; |
michael@0 | 2680 | mLoadLengthComputable = false; |
michael@0 | 2681 | mLoadTotal = 0; |
michael@0 | 2682 | if ((aVariant || !aBody.IsNull()) && httpChannel && |
michael@0 | 2683 | !method.LowerCaseEqualsLiteral("get") && |
michael@0 | 2684 | !method.LowerCaseEqualsLiteral("head")) { |
michael@0 | 2685 | |
michael@0 | 2686 | nsAutoCString charset; |
michael@0 | 2687 | nsAutoCString defaultContentType; |
michael@0 | 2688 | nsCOMPtr<nsIInputStream> postDataStream; |
michael@0 | 2689 | |
michael@0 | 2690 | rv = GetRequestBody(aVariant, aBody, getter_AddRefs(postDataStream), |
michael@0 | 2691 | &mUploadTotal, defaultContentType, charset); |
michael@0 | 2692 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2693 | |
michael@0 | 2694 | if (postDataStream) { |
michael@0 | 2695 | // If no content type header was set by the client, we set it to |
michael@0 | 2696 | // application/xml. |
michael@0 | 2697 | nsAutoCString contentType; |
michael@0 | 2698 | if (NS_FAILED(httpChannel-> |
michael@0 | 2699 | GetRequestHeader(NS_LITERAL_CSTRING("Content-Type"), |
michael@0 | 2700 | contentType)) || |
michael@0 | 2701 | contentType.IsEmpty()) { |
michael@0 | 2702 | contentType = defaultContentType; |
michael@0 | 2703 | } |
michael@0 | 2704 | |
michael@0 | 2705 | // We don't want to set a charset for streams. |
michael@0 | 2706 | if (!charset.IsEmpty()) { |
michael@0 | 2707 | nsAutoCString specifiedCharset; |
michael@0 | 2708 | bool haveCharset; |
michael@0 | 2709 | int32_t charsetStart, charsetEnd; |
michael@0 | 2710 | rv = NS_ExtractCharsetFromContentType(contentType, specifiedCharset, |
michael@0 | 2711 | &haveCharset, &charsetStart, |
michael@0 | 2712 | &charsetEnd); |
michael@0 | 2713 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 2714 | // special case: the extracted charset is quoted with single quotes |
michael@0 | 2715 | // -- for the purpose of preserving what was set we want to handle |
michael@0 | 2716 | // them as delimiters (although they aren't really) |
michael@0 | 2717 | if (specifiedCharset.Length() >= 2 && |
michael@0 | 2718 | specifiedCharset.First() == '\'' && |
michael@0 | 2719 | specifiedCharset.Last() == '\'') { |
michael@0 | 2720 | specifiedCharset = Substring(specifiedCharset, 1, |
michael@0 | 2721 | specifiedCharset.Length() - 2); |
michael@0 | 2722 | } |
michael@0 | 2723 | |
michael@0 | 2724 | // If the content-type the page set already has a charset parameter, |
michael@0 | 2725 | // and it's the same charset, up to case, as |charset|, just send the |
michael@0 | 2726 | // page-set content-type header. Apparently at least |
michael@0 | 2727 | // google-web-toolkit is broken and relies on the exact case of its |
michael@0 | 2728 | // charset parameter, which makes things break if we use |charset| |
michael@0 | 2729 | // (which is always a fully resolved charset per our charset alias |
michael@0 | 2730 | // table, hence might be differently cased). |
michael@0 | 2731 | if (!specifiedCharset.Equals(charset, |
michael@0 | 2732 | nsCaseInsensitiveCStringComparator())) { |
michael@0 | 2733 | nsAutoCString newCharset("; charset="); |
michael@0 | 2734 | newCharset.Append(charset); |
michael@0 | 2735 | contentType.Replace(charsetStart, charsetEnd - charsetStart, |
michael@0 | 2736 | newCharset); |
michael@0 | 2737 | } |
michael@0 | 2738 | } |
michael@0 | 2739 | } |
michael@0 | 2740 | |
michael@0 | 2741 | // If necessary, wrap the stream in a buffered stream so as to guarantee |
michael@0 | 2742 | // support for our upload when calling ExplicitSetUploadStream. |
michael@0 | 2743 | if (!NS_InputStreamIsBuffered(postDataStream)) { |
michael@0 | 2744 | nsCOMPtr<nsIInputStream> bufferedStream; |
michael@0 | 2745 | rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), |
michael@0 | 2746 | postDataStream, |
michael@0 | 2747 | 4096); |
michael@0 | 2748 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2749 | |
michael@0 | 2750 | postDataStream = bufferedStream; |
michael@0 | 2751 | } |
michael@0 | 2752 | |
michael@0 | 2753 | mUploadComplete = false; |
michael@0 | 2754 | |
michael@0 | 2755 | // We want to use a newer version of the upload channel that won't |
michael@0 | 2756 | // ignore the necessary headers for an empty Content-Type. |
michael@0 | 2757 | nsCOMPtr<nsIUploadChannel2> uploadChannel2(do_QueryInterface(httpChannel)); |
michael@0 | 2758 | // This assertion will fire if buggy extensions are installed |
michael@0 | 2759 | NS_ASSERTION(uploadChannel2, "http must support nsIUploadChannel2"); |
michael@0 | 2760 | if (uploadChannel2) { |
michael@0 | 2761 | uploadChannel2->ExplicitSetUploadStream(postDataStream, contentType, |
michael@0 | 2762 | mUploadTotal, method, false); |
michael@0 | 2763 | } |
michael@0 | 2764 | else { |
michael@0 | 2765 | // http channel doesn't support the new nsIUploadChannel2. Emulate |
michael@0 | 2766 | // as best we can using nsIUploadChannel |
michael@0 | 2767 | if (contentType.IsEmpty()) { |
michael@0 | 2768 | contentType.AssignLiteral("application/octet-stream"); |
michael@0 | 2769 | } |
michael@0 | 2770 | nsCOMPtr<nsIUploadChannel> uploadChannel = |
michael@0 | 2771 | do_QueryInterface(httpChannel); |
michael@0 | 2772 | uploadChannel->SetUploadStream(postDataStream, contentType, mUploadTotal); |
michael@0 | 2773 | // Reset the method to its original value |
michael@0 | 2774 | httpChannel->SetRequestMethod(method); |
michael@0 | 2775 | } |
michael@0 | 2776 | } |
michael@0 | 2777 | } |
michael@0 | 2778 | |
michael@0 | 2779 | if (httpChannel) { |
michael@0 | 2780 | nsAutoCString contentTypeHeader; |
michael@0 | 2781 | rv = httpChannel->GetRequestHeader(NS_LITERAL_CSTRING("Content-Type"), |
michael@0 | 2782 | contentTypeHeader); |
michael@0 | 2783 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 2784 | nsAutoCString contentType, charset; |
michael@0 | 2785 | rv = NS_ParseContentType(contentTypeHeader, contentType, charset); |
michael@0 | 2786 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2787 | |
michael@0 | 2788 | if (!contentType.LowerCaseEqualsLiteral("text/plain") && |
michael@0 | 2789 | !contentType.LowerCaseEqualsLiteral("application/x-www-form-urlencoded") && |
michael@0 | 2790 | !contentType.LowerCaseEqualsLiteral("multipart/form-data")) { |
michael@0 | 2791 | mCORSUnsafeHeaders.AppendElement(NS_LITERAL_CSTRING("Content-Type")); |
michael@0 | 2792 | } |
michael@0 | 2793 | } |
michael@0 | 2794 | } |
michael@0 | 2795 | |
michael@0 | 2796 | ResetResponse(); |
michael@0 | 2797 | |
michael@0 | 2798 | rv = CheckChannelForCrossSiteRequest(mChannel); |
michael@0 | 2799 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2800 | |
michael@0 | 2801 | bool withCredentials = !!(mState & XML_HTTP_REQUEST_AC_WITH_CREDENTIALS); |
michael@0 | 2802 | |
michael@0 | 2803 | // Hook us up to listen to redirects and the like |
michael@0 | 2804 | mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks)); |
michael@0 | 2805 | mChannel->SetNotificationCallbacks(this); |
michael@0 | 2806 | |
michael@0 | 2807 | // Blocking gets are common enough out of XHR that we should mark |
michael@0 | 2808 | // the channel slow by default for pipeline purposes |
michael@0 | 2809 | AddLoadFlags(mChannel, nsIRequest::INHIBIT_PIPELINE); |
michael@0 | 2810 | |
michael@0 | 2811 | nsCOMPtr<nsIHttpChannelInternal> |
michael@0 | 2812 | internalHttpChannel(do_QueryInterface(mChannel)); |
michael@0 | 2813 | if (internalHttpChannel) { |
michael@0 | 2814 | // we never let XHR be blocked by head CSS/JS loads to avoid |
michael@0 | 2815 | // potential deadlock where server generation of CSS/JS requires |
michael@0 | 2816 | // an XHR signal. |
michael@0 | 2817 | internalHttpChannel->SetLoadUnblocked(true); |
michael@0 | 2818 | |
michael@0 | 2819 | // Disable Necko-internal response timeouts. |
michael@0 | 2820 | internalHttpChannel->SetResponseTimeoutEnabled(false); |
michael@0 | 2821 | } |
michael@0 | 2822 | |
michael@0 | 2823 | nsCOMPtr<nsIStreamListener> listener = this; |
michael@0 | 2824 | if (!IsSystemXHR()) { |
michael@0 | 2825 | // Always create a nsCORSListenerProxy here even if it's |
michael@0 | 2826 | // a same-origin request right now, since it could be redirected. |
michael@0 | 2827 | nsRefPtr<nsCORSListenerProxy> corsListener = |
michael@0 | 2828 | new nsCORSListenerProxy(listener, mPrincipal, withCredentials); |
michael@0 | 2829 | rv = corsListener->Init(mChannel, true); |
michael@0 | 2830 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2831 | listener = corsListener; |
michael@0 | 2832 | } |
michael@0 | 2833 | else { |
michael@0 | 2834 | // Because of bug 682305, we can't let listener be the XHR object itself |
michael@0 | 2835 | // because JS wouldn't be able to use it. So if we haven't otherwise |
michael@0 | 2836 | // created a listener around 'this', do so now. |
michael@0 | 2837 | |
michael@0 | 2838 | listener = new nsStreamListenerWrapper(listener); |
michael@0 | 2839 | } |
michael@0 | 2840 | |
michael@0 | 2841 | if (mIsAnon) { |
michael@0 | 2842 | AddLoadFlags(mChannel, nsIRequest::LOAD_ANONYMOUS); |
michael@0 | 2843 | } |
michael@0 | 2844 | else { |
michael@0 | 2845 | AddLoadFlags(mChannel, nsIChannel::LOAD_EXPLICIT_CREDENTIALS); |
michael@0 | 2846 | } |
michael@0 | 2847 | |
michael@0 | 2848 | NS_ASSERTION(listener != this, |
michael@0 | 2849 | "Using an object as a listener that can't be exposed to JS"); |
michael@0 | 2850 | |
michael@0 | 2851 | // Bypass the network cache in cases where it makes no sense: |
michael@0 | 2852 | // POST responses are always unique, and we provide no API that would |
michael@0 | 2853 | // allow our consumers to specify a "cache key" to access old POST |
michael@0 | 2854 | // responses, so they are not worth caching. |
michael@0 | 2855 | if (method.EqualsLiteral("POST")) { |
michael@0 | 2856 | AddLoadFlags(mChannel, |
michael@0 | 2857 | nsIRequest::LOAD_BYPASS_CACHE | nsIRequest::INHIBIT_CACHING); |
michael@0 | 2858 | } |
michael@0 | 2859 | // When we are sync loading, we need to bypass the local cache when it would |
michael@0 | 2860 | // otherwise block us waiting for exclusive access to the cache. If we don't |
michael@0 | 2861 | // do this, then we could dead lock in some cases (see bug 309424). |
michael@0 | 2862 | else if (!(mState & XML_HTTP_REQUEST_ASYNC)) { |
michael@0 | 2863 | AddLoadFlags(mChannel, |
michael@0 | 2864 | nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY); |
michael@0 | 2865 | } |
michael@0 | 2866 | |
michael@0 | 2867 | // Since we expect XML data, set the type hint accordingly |
michael@0 | 2868 | // if the channel doesn't know any content type. |
michael@0 | 2869 | // This means that we always try to parse local files as XML |
michael@0 | 2870 | // ignoring return value, as this is not critical |
michael@0 | 2871 | nsAutoCString contentType; |
michael@0 | 2872 | if (NS_FAILED(mChannel->GetContentType(contentType)) || |
michael@0 | 2873 | contentType.IsEmpty() || |
michael@0 | 2874 | contentType.Equals(UNKNOWN_CONTENT_TYPE)) { |
michael@0 | 2875 | mChannel->SetContentType(NS_LITERAL_CSTRING("application/xml")); |
michael@0 | 2876 | } |
michael@0 | 2877 | |
michael@0 | 2878 | // We're about to send the request. Start our timeout. |
michael@0 | 2879 | mRequestSentTime = PR_Now(); |
michael@0 | 2880 | StartTimeoutTimer(); |
michael@0 | 2881 | |
michael@0 | 2882 | // Set up the preflight if needed |
michael@0 | 2883 | if (mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT) { |
michael@0 | 2884 | // Check to see if this initial OPTIONS request has already been cached |
michael@0 | 2885 | // in our special Access Control Cache. |
michael@0 | 2886 | |
michael@0 | 2887 | rv = NS_StartCORSPreflight(mChannel, listener, |
michael@0 | 2888 | mPrincipal, withCredentials, |
michael@0 | 2889 | mCORSUnsafeHeaders, |
michael@0 | 2890 | getter_AddRefs(mCORSPreflightChannel)); |
michael@0 | 2891 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2892 | } |
michael@0 | 2893 | else { |
michael@0 | 2894 | // Start reading from the channel |
michael@0 | 2895 | rv = mChannel->AsyncOpen(listener, nullptr); |
michael@0 | 2896 | } |
michael@0 | 2897 | |
michael@0 | 2898 | if (NS_FAILED(rv)) { |
michael@0 | 2899 | // Drop our ref to the channel to avoid cycles |
michael@0 | 2900 | mChannel = nullptr; |
michael@0 | 2901 | mCORSPreflightChannel = nullptr; |
michael@0 | 2902 | return rv; |
michael@0 | 2903 | } |
michael@0 | 2904 | |
michael@0 | 2905 | // Either AsyncOpen was called, or CORS will open the channel later. |
michael@0 | 2906 | mWaitingForOnStopRequest = true; |
michael@0 | 2907 | |
michael@0 | 2908 | // If we're synchronous, spin an event loop here and wait |
michael@0 | 2909 | if (!(mState & XML_HTTP_REQUEST_ASYNC)) { |
michael@0 | 2910 | mState |= XML_HTTP_REQUEST_SYNCLOOPING; |
michael@0 | 2911 | |
michael@0 | 2912 | nsCOMPtr<nsIDocument> suspendedDoc; |
michael@0 | 2913 | nsCOMPtr<nsIRunnable> resumeTimeoutRunnable; |
michael@0 | 2914 | if (GetOwner()) { |
michael@0 | 2915 | nsCOMPtr<nsIDOMWindow> topWindow; |
michael@0 | 2916 | if (NS_SUCCEEDED(GetOwner()->GetTop(getter_AddRefs(topWindow)))) { |
michael@0 | 2917 | nsCOMPtr<nsPIDOMWindow> suspendedWindow(do_QueryInterface(topWindow)); |
michael@0 | 2918 | if (suspendedWindow && |
michael@0 | 2919 | (suspendedWindow = suspendedWindow->GetCurrentInnerWindow())) { |
michael@0 | 2920 | suspendedDoc = suspendedWindow->GetExtantDoc(); |
michael@0 | 2921 | if (suspendedDoc) { |
michael@0 | 2922 | suspendedDoc->SuppressEventHandling(nsIDocument::eEvents); |
michael@0 | 2923 | } |
michael@0 | 2924 | suspendedWindow->SuspendTimeouts(1, false); |
michael@0 | 2925 | resumeTimeoutRunnable = new nsResumeTimeoutsEvent(suspendedWindow); |
michael@0 | 2926 | } |
michael@0 | 2927 | } |
michael@0 | 2928 | } |
michael@0 | 2929 | |
michael@0 | 2930 | ChangeState(XML_HTTP_REQUEST_SENT); |
michael@0 | 2931 | |
michael@0 | 2932 | { |
michael@0 | 2933 | nsAutoSyncOperation sync(suspendedDoc); |
michael@0 | 2934 | // Note, calling ChangeState may have cleared |
michael@0 | 2935 | // XML_HTTP_REQUEST_SYNCLOOPING flag. |
michael@0 | 2936 | nsIThread *thread = NS_GetCurrentThread(); |
michael@0 | 2937 | while (mState & XML_HTTP_REQUEST_SYNCLOOPING) { |
michael@0 | 2938 | if (!NS_ProcessNextEvent(thread)) { |
michael@0 | 2939 | rv = NS_ERROR_UNEXPECTED; |
michael@0 | 2940 | break; |
michael@0 | 2941 | } |
michael@0 | 2942 | } |
michael@0 | 2943 | } |
michael@0 | 2944 | |
michael@0 | 2945 | if (suspendedDoc) { |
michael@0 | 2946 | suspendedDoc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eEvents, |
michael@0 | 2947 | true); |
michael@0 | 2948 | } |
michael@0 | 2949 | |
michael@0 | 2950 | if (resumeTimeoutRunnable) { |
michael@0 | 2951 | NS_DispatchToCurrentThread(resumeTimeoutRunnable); |
michael@0 | 2952 | } |
michael@0 | 2953 | } else { |
michael@0 | 2954 | // Now that we've successfully opened the channel, we can change state. Note |
michael@0 | 2955 | // that this needs to come after the AsyncOpen() and rv check, because this |
michael@0 | 2956 | // can run script that would try to restart this request, and that could end |
michael@0 | 2957 | // up doing our AsyncOpen on a null channel if the reentered AsyncOpen fails. |
michael@0 | 2958 | ChangeState(XML_HTTP_REQUEST_SENT); |
michael@0 | 2959 | if (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress)) { |
michael@0 | 2960 | StartProgressEventTimer(); |
michael@0 | 2961 | } |
michael@0 | 2962 | DispatchProgressEvent(this, NS_LITERAL_STRING(LOADSTART_STR), false, |
michael@0 | 2963 | 0, 0); |
michael@0 | 2964 | if (mUpload && !mUploadComplete) { |
michael@0 | 2965 | DispatchProgressEvent(mUpload, NS_LITERAL_STRING(LOADSTART_STR), true, |
michael@0 | 2966 | 0, mUploadTotal); |
michael@0 | 2967 | } |
michael@0 | 2968 | } |
michael@0 | 2969 | |
michael@0 | 2970 | if (!mChannel) { |
michael@0 | 2971 | return NS_ERROR_FAILURE; |
michael@0 | 2972 | } |
michael@0 | 2973 | |
michael@0 | 2974 | return rv; |
michael@0 | 2975 | } |
michael@0 | 2976 | |
michael@0 | 2977 | /* void setRequestHeader (in ByteString header, in ByteString value); */ |
michael@0 | 2978 | // http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-setrequestheader |
michael@0 | 2979 | NS_IMETHODIMP |
michael@0 | 2980 | nsXMLHttpRequest::SetRequestHeader(const nsACString& header, |
michael@0 | 2981 | const nsACString& value) |
michael@0 | 2982 | { |
michael@0 | 2983 | // Step 1 and 2 |
michael@0 | 2984 | if (!(mState & XML_HTTP_REQUEST_OPENED)) { |
michael@0 | 2985 | return NS_ERROR_DOM_INVALID_STATE_ERR; |
michael@0 | 2986 | } |
michael@0 | 2987 | NS_ASSERTION(mChannel, "mChannel must be valid if we're OPENED."); |
michael@0 | 2988 | |
michael@0 | 2989 | // Step 3 |
michael@0 | 2990 | // Make sure we don't store an invalid header name in mCORSUnsafeHeaders |
michael@0 | 2991 | if (!IsValidHTTPToken(header)) { // XXX nsHttp::IsValidToken? |
michael@0 | 2992 | return NS_ERROR_DOM_SYNTAX_ERR; |
michael@0 | 2993 | } |
michael@0 | 2994 | |
michael@0 | 2995 | // Check that we haven't already opened the channel. We can't rely on |
michael@0 | 2996 | // the channel throwing from mChannel->SetRequestHeader since we might |
michael@0 | 2997 | // still be waiting for mCORSPreflightChannel to actually open mChannel |
michael@0 | 2998 | if (mCORSPreflightChannel) { |
michael@0 | 2999 | bool pending; |
michael@0 | 3000 | nsresult rv = mCORSPreflightChannel->IsPending(&pending); |
michael@0 | 3001 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3002 | |
michael@0 | 3003 | if (pending) { |
michael@0 | 3004 | return NS_ERROR_IN_PROGRESS; |
michael@0 | 3005 | } |
michael@0 | 3006 | } |
michael@0 | 3007 | |
michael@0 | 3008 | if (!mChannel) // open() initializes mChannel, and open() |
michael@0 | 3009 | return NS_ERROR_FAILURE; // must be called before first setRequestHeader() |
michael@0 | 3010 | |
michael@0 | 3011 | nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel); |
michael@0 | 3012 | if (!httpChannel) { |
michael@0 | 3013 | return NS_OK; |
michael@0 | 3014 | } |
michael@0 | 3015 | |
michael@0 | 3016 | // We will merge XHR headers, per the spec (secion 4.6.2) unless: |
michael@0 | 3017 | // 1 - The caller is privileged and setting an invalid header, |
michael@0 | 3018 | // or |
michael@0 | 3019 | // 2 - we have not yet explicitly set that header; this allows web |
michael@0 | 3020 | // content to override default headers the first time they set them. |
michael@0 | 3021 | bool mergeHeaders = true; |
michael@0 | 3022 | |
michael@0 | 3023 | // Prevent modification to certain HTTP headers (see bug 302263), unless |
michael@0 | 3024 | // the executing script is privileged. |
michael@0 | 3025 | bool isInvalidHeader = false; |
michael@0 | 3026 | static const char *kInvalidHeaders[] = { |
michael@0 | 3027 | "accept-charset", "accept-encoding", "access-control-request-headers", |
michael@0 | 3028 | "access-control-request-method", "connection", "content-length", |
michael@0 | 3029 | "cookie", "cookie2", "content-transfer-encoding", "date", "dnt", |
michael@0 | 3030 | "expect", "host", "keep-alive", "origin", "referer", "te", "trailer", |
michael@0 | 3031 | "transfer-encoding", "upgrade", "user-agent", "via" |
michael@0 | 3032 | }; |
michael@0 | 3033 | uint32_t i; |
michael@0 | 3034 | for (i = 0; i < ArrayLength(kInvalidHeaders); ++i) { |
michael@0 | 3035 | if (header.LowerCaseEqualsASCII(kInvalidHeaders[i])) { |
michael@0 | 3036 | isInvalidHeader = true; |
michael@0 | 3037 | break; |
michael@0 | 3038 | } |
michael@0 | 3039 | } |
michael@0 | 3040 | |
michael@0 | 3041 | if (!IsSystemXHR()) { |
michael@0 | 3042 | // Step 5: Check for dangerous headers. |
michael@0 | 3043 | if (isInvalidHeader) { |
michael@0 | 3044 | NS_WARNING("refusing to set request header"); |
michael@0 | 3045 | return NS_OK; |
michael@0 | 3046 | } |
michael@0 | 3047 | if (StringBeginsWith(header, NS_LITERAL_CSTRING("proxy-"), |
michael@0 | 3048 | nsCaseInsensitiveCStringComparator()) || |
michael@0 | 3049 | StringBeginsWith(header, NS_LITERAL_CSTRING("sec-"), |
michael@0 | 3050 | nsCaseInsensitiveCStringComparator())) { |
michael@0 | 3051 | NS_WARNING("refusing to set request header"); |
michael@0 | 3052 | return NS_OK; |
michael@0 | 3053 | } |
michael@0 | 3054 | |
michael@0 | 3055 | // Check for dangerous cross-site headers |
michael@0 | 3056 | bool safeHeader = IsSystemXHR(); |
michael@0 | 3057 | if (!safeHeader) { |
michael@0 | 3058 | // Content-Type isn't always safe, but we'll deal with it in Send() |
michael@0 | 3059 | const char *kCrossOriginSafeHeaders[] = { |
michael@0 | 3060 | "accept", "accept-language", "content-language", "content-type", |
michael@0 | 3061 | "last-event-id" |
michael@0 | 3062 | }; |
michael@0 | 3063 | for (i = 0; i < ArrayLength(kCrossOriginSafeHeaders); ++i) { |
michael@0 | 3064 | if (header.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) { |
michael@0 | 3065 | safeHeader = true; |
michael@0 | 3066 | break; |
michael@0 | 3067 | } |
michael@0 | 3068 | } |
michael@0 | 3069 | } |
michael@0 | 3070 | |
michael@0 | 3071 | if (!safeHeader) { |
michael@0 | 3072 | if (!mCORSUnsafeHeaders.Contains(header)) { |
michael@0 | 3073 | mCORSUnsafeHeaders.AppendElement(header); |
michael@0 | 3074 | } |
michael@0 | 3075 | } |
michael@0 | 3076 | } else { |
michael@0 | 3077 | // Case 1 above |
michael@0 | 3078 | if (isInvalidHeader) { |
michael@0 | 3079 | mergeHeaders = false; |
michael@0 | 3080 | } |
michael@0 | 3081 | } |
michael@0 | 3082 | |
michael@0 | 3083 | if (!mAlreadySetHeaders.Contains(header)) { |
michael@0 | 3084 | // Case 2 above |
michael@0 | 3085 | mergeHeaders = false; |
michael@0 | 3086 | } |
michael@0 | 3087 | |
michael@0 | 3088 | // Merge headers depending on what we decided above. |
michael@0 | 3089 | nsresult rv = httpChannel->SetRequestHeader(header, value, mergeHeaders); |
michael@0 | 3090 | if (rv == NS_ERROR_INVALID_ARG) { |
michael@0 | 3091 | return NS_ERROR_DOM_SYNTAX_ERR; |
michael@0 | 3092 | } |
michael@0 | 3093 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 3094 | // Remember that we've set this header, so subsequent set operations will merge values. |
michael@0 | 3095 | mAlreadySetHeaders.PutEntry(nsCString(header)); |
michael@0 | 3096 | |
michael@0 | 3097 | // We'll want to duplicate this header for any replacement channels (eg. on redirect) |
michael@0 | 3098 | RequestHeader reqHeader = { |
michael@0 | 3099 | nsCString(header), nsCString(value) |
michael@0 | 3100 | }; |
michael@0 | 3101 | mModifiedRequestHeaders.AppendElement(reqHeader); |
michael@0 | 3102 | } |
michael@0 | 3103 | return rv; |
michael@0 | 3104 | } |
michael@0 | 3105 | |
michael@0 | 3106 | /* attribute unsigned long timeout; */ |
michael@0 | 3107 | NS_IMETHODIMP |
michael@0 | 3108 | nsXMLHttpRequest::GetTimeout(uint32_t *aTimeout) |
michael@0 | 3109 | { |
michael@0 | 3110 | *aTimeout = Timeout(); |
michael@0 | 3111 | return NS_OK; |
michael@0 | 3112 | } |
michael@0 | 3113 | |
michael@0 | 3114 | NS_IMETHODIMP |
michael@0 | 3115 | nsXMLHttpRequest::SetTimeout(uint32_t aTimeout) |
michael@0 | 3116 | { |
michael@0 | 3117 | ErrorResult rv; |
michael@0 | 3118 | SetTimeout(aTimeout, rv); |
michael@0 | 3119 | return rv.ErrorCode(); |
michael@0 | 3120 | } |
michael@0 | 3121 | |
michael@0 | 3122 | void |
michael@0 | 3123 | nsXMLHttpRequest::SetTimeout(uint32_t aTimeout, ErrorResult& aRv) |
michael@0 | 3124 | { |
michael@0 | 3125 | if (!(mState & (XML_HTTP_REQUEST_ASYNC | XML_HTTP_REQUEST_UNSENT)) && |
michael@0 | 3126 | HasOrHasHadOwner()) { |
michael@0 | 3127 | /* Timeout is not supported for synchronous requests with an owning window, |
michael@0 | 3128 | per XHR2 spec. */ |
michael@0 | 3129 | LogMessage("TimeoutSyncXHRWarning", GetOwner()); |
michael@0 | 3130 | aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); |
michael@0 | 3131 | return; |
michael@0 | 3132 | } |
michael@0 | 3133 | |
michael@0 | 3134 | mTimeoutMilliseconds = aTimeout; |
michael@0 | 3135 | if (mRequestSentTime) { |
michael@0 | 3136 | StartTimeoutTimer(); |
michael@0 | 3137 | } |
michael@0 | 3138 | } |
michael@0 | 3139 | |
michael@0 | 3140 | void |
michael@0 | 3141 | nsXMLHttpRequest::StartTimeoutTimer() |
michael@0 | 3142 | { |
michael@0 | 3143 | NS_ABORT_IF_FALSE(mRequestSentTime, |
michael@0 | 3144 | "StartTimeoutTimer mustn't be called before the request was sent!"); |
michael@0 | 3145 | if (mState & XML_HTTP_REQUEST_DONE) { |
michael@0 | 3146 | // do nothing! |
michael@0 | 3147 | return; |
michael@0 | 3148 | } |
michael@0 | 3149 | |
michael@0 | 3150 | if (mTimeoutTimer) { |
michael@0 | 3151 | mTimeoutTimer->Cancel(); |
michael@0 | 3152 | } |
michael@0 | 3153 | |
michael@0 | 3154 | if (!mTimeoutMilliseconds) { |
michael@0 | 3155 | return; |
michael@0 | 3156 | } |
michael@0 | 3157 | |
michael@0 | 3158 | if (!mTimeoutTimer) { |
michael@0 | 3159 | mTimeoutTimer = do_CreateInstance(NS_TIMER_CONTRACTID); |
michael@0 | 3160 | } |
michael@0 | 3161 | uint32_t elapsed = |
michael@0 | 3162 | (uint32_t)((PR_Now() - mRequestSentTime) / PR_USEC_PER_MSEC); |
michael@0 | 3163 | mTimeoutTimer->InitWithCallback( |
michael@0 | 3164 | this, |
michael@0 | 3165 | mTimeoutMilliseconds > elapsed ? mTimeoutMilliseconds - elapsed : 0, |
michael@0 | 3166 | nsITimer::TYPE_ONE_SHOT |
michael@0 | 3167 | ); |
michael@0 | 3168 | } |
michael@0 | 3169 | |
michael@0 | 3170 | /* readonly attribute unsigned short readyState; */ |
michael@0 | 3171 | NS_IMETHODIMP |
michael@0 | 3172 | nsXMLHttpRequest::GetReadyState(uint16_t *aState) |
michael@0 | 3173 | { |
michael@0 | 3174 | *aState = ReadyState(); |
michael@0 | 3175 | return NS_OK; |
michael@0 | 3176 | } |
michael@0 | 3177 | |
michael@0 | 3178 | uint16_t |
michael@0 | 3179 | nsXMLHttpRequest::ReadyState() |
michael@0 | 3180 | { |
michael@0 | 3181 | // Translate some of our internal states for external consumers |
michael@0 | 3182 | if (mState & XML_HTTP_REQUEST_UNSENT) { |
michael@0 | 3183 | return UNSENT; |
michael@0 | 3184 | } |
michael@0 | 3185 | if (mState & (XML_HTTP_REQUEST_OPENED | XML_HTTP_REQUEST_SENT)) { |
michael@0 | 3186 | return OPENED; |
michael@0 | 3187 | } |
michael@0 | 3188 | if (mState & XML_HTTP_REQUEST_HEADERS_RECEIVED) { |
michael@0 | 3189 | return HEADERS_RECEIVED; |
michael@0 | 3190 | } |
michael@0 | 3191 | if (mState & XML_HTTP_REQUEST_LOADING) { |
michael@0 | 3192 | return LOADING; |
michael@0 | 3193 | } |
michael@0 | 3194 | MOZ_ASSERT(mState & XML_HTTP_REQUEST_DONE); |
michael@0 | 3195 | return DONE; |
michael@0 | 3196 | } |
michael@0 | 3197 | |
michael@0 | 3198 | /* void overrideMimeType(in DOMString mimetype); */ |
michael@0 | 3199 | NS_IMETHODIMP |
michael@0 | 3200 | nsXMLHttpRequest::SlowOverrideMimeType(const nsAString& aMimeType) |
michael@0 | 3201 | { |
michael@0 | 3202 | OverrideMimeType(aMimeType); |
michael@0 | 3203 | return NS_OK; |
michael@0 | 3204 | } |
michael@0 | 3205 | |
michael@0 | 3206 | /* attribute boolean mozBackgroundRequest; */ |
michael@0 | 3207 | NS_IMETHODIMP |
michael@0 | 3208 | nsXMLHttpRequest::GetMozBackgroundRequest(bool *_retval) |
michael@0 | 3209 | { |
michael@0 | 3210 | *_retval = MozBackgroundRequest(); |
michael@0 | 3211 | return NS_OK; |
michael@0 | 3212 | } |
michael@0 | 3213 | |
michael@0 | 3214 | bool |
michael@0 | 3215 | nsXMLHttpRequest::MozBackgroundRequest() |
michael@0 | 3216 | { |
michael@0 | 3217 | return !!(mState & XML_HTTP_REQUEST_BACKGROUND); |
michael@0 | 3218 | } |
michael@0 | 3219 | |
michael@0 | 3220 | NS_IMETHODIMP |
michael@0 | 3221 | nsXMLHttpRequest::SetMozBackgroundRequest(bool aMozBackgroundRequest) |
michael@0 | 3222 | { |
michael@0 | 3223 | nsresult rv = NS_OK; |
michael@0 | 3224 | SetMozBackgroundRequest(aMozBackgroundRequest, rv); |
michael@0 | 3225 | return rv; |
michael@0 | 3226 | } |
michael@0 | 3227 | |
michael@0 | 3228 | void |
michael@0 | 3229 | nsXMLHttpRequest::SetMozBackgroundRequest(bool aMozBackgroundRequest, nsresult& aRv) |
michael@0 | 3230 | { |
michael@0 | 3231 | if (!IsSystemXHR()) { |
michael@0 | 3232 | aRv = NS_ERROR_DOM_SECURITY_ERR; |
michael@0 | 3233 | return; |
michael@0 | 3234 | } |
michael@0 | 3235 | |
michael@0 | 3236 | if (!(mState & XML_HTTP_REQUEST_UNSENT)) { |
michael@0 | 3237 | // Can't change this while we're in the middle of something. |
michael@0 | 3238 | aRv = NS_ERROR_IN_PROGRESS; |
michael@0 | 3239 | return; |
michael@0 | 3240 | } |
michael@0 | 3241 | |
michael@0 | 3242 | if (aMozBackgroundRequest) { |
michael@0 | 3243 | mState |= XML_HTTP_REQUEST_BACKGROUND; |
michael@0 | 3244 | } else { |
michael@0 | 3245 | mState &= ~XML_HTTP_REQUEST_BACKGROUND; |
michael@0 | 3246 | } |
michael@0 | 3247 | } |
michael@0 | 3248 | |
michael@0 | 3249 | /* attribute boolean withCredentials; */ |
michael@0 | 3250 | NS_IMETHODIMP |
michael@0 | 3251 | nsXMLHttpRequest::GetWithCredentials(bool *_retval) |
michael@0 | 3252 | { |
michael@0 | 3253 | *_retval = WithCredentials(); |
michael@0 | 3254 | return NS_OK; |
michael@0 | 3255 | } |
michael@0 | 3256 | |
michael@0 | 3257 | bool |
michael@0 | 3258 | nsXMLHttpRequest::WithCredentials() |
michael@0 | 3259 | { |
michael@0 | 3260 | return !!(mState & XML_HTTP_REQUEST_AC_WITH_CREDENTIALS); |
michael@0 | 3261 | } |
michael@0 | 3262 | |
michael@0 | 3263 | NS_IMETHODIMP |
michael@0 | 3264 | nsXMLHttpRequest::SetWithCredentials(bool aWithCredentials) |
michael@0 | 3265 | { |
michael@0 | 3266 | ErrorResult rv; |
michael@0 | 3267 | SetWithCredentials(aWithCredentials, rv); |
michael@0 | 3268 | return rv.ErrorCode(); |
michael@0 | 3269 | } |
michael@0 | 3270 | |
michael@0 | 3271 | void |
michael@0 | 3272 | nsXMLHttpRequest::SetWithCredentials(bool aWithCredentials, ErrorResult& aRv) |
michael@0 | 3273 | { |
michael@0 | 3274 | // Return error if we're already processing a request |
michael@0 | 3275 | if (XML_HTTP_REQUEST_SENT & mState) { |
michael@0 | 3276 | aRv = NS_ERROR_FAILURE; |
michael@0 | 3277 | return; |
michael@0 | 3278 | } |
michael@0 | 3279 | |
michael@0 | 3280 | // sync request is not allowed setting withCredentials in window context |
michael@0 | 3281 | if (HasOrHasHadOwner() && |
michael@0 | 3282 | !(mState & (XML_HTTP_REQUEST_UNSENT | XML_HTTP_REQUEST_ASYNC))) { |
michael@0 | 3283 | LogMessage("WithCredentialsSyncXHRWarning", GetOwner()); |
michael@0 | 3284 | aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); |
michael@0 | 3285 | return; |
michael@0 | 3286 | } |
michael@0 | 3287 | |
michael@0 | 3288 | if (aWithCredentials) { |
michael@0 | 3289 | mState |= XML_HTTP_REQUEST_AC_WITH_CREDENTIALS; |
michael@0 | 3290 | } else { |
michael@0 | 3291 | mState &= ~XML_HTTP_REQUEST_AC_WITH_CREDENTIALS; |
michael@0 | 3292 | } |
michael@0 | 3293 | } |
michael@0 | 3294 | |
michael@0 | 3295 | nsresult |
michael@0 | 3296 | nsXMLHttpRequest::ChangeState(uint32_t aState, bool aBroadcast) |
michael@0 | 3297 | { |
michael@0 | 3298 | // If we are setting one of the mutually exclusive states, |
michael@0 | 3299 | // unset those state bits first. |
michael@0 | 3300 | if (aState & XML_HTTP_REQUEST_LOADSTATES) { |
michael@0 | 3301 | mState &= ~XML_HTTP_REQUEST_LOADSTATES; |
michael@0 | 3302 | } |
michael@0 | 3303 | mState |= aState; |
michael@0 | 3304 | nsresult rv = NS_OK; |
michael@0 | 3305 | |
michael@0 | 3306 | if (mProgressNotifier && |
michael@0 | 3307 | !(aState & (XML_HTTP_REQUEST_HEADERS_RECEIVED | XML_HTTP_REQUEST_LOADING))) { |
michael@0 | 3308 | mProgressTimerIsActive = false; |
michael@0 | 3309 | mProgressNotifier->Cancel(); |
michael@0 | 3310 | } |
michael@0 | 3311 | |
michael@0 | 3312 | if ((aState & XML_HTTP_REQUEST_LOADSTATES) && // Broadcast load states only |
michael@0 | 3313 | aState != XML_HTTP_REQUEST_SENT && // And not internal ones |
michael@0 | 3314 | aBroadcast && |
michael@0 | 3315 | (mState & XML_HTTP_REQUEST_ASYNC || |
michael@0 | 3316 | aState & XML_HTTP_REQUEST_OPENED || |
michael@0 | 3317 | aState & XML_HTTP_REQUEST_DONE)) { |
michael@0 | 3318 | nsCOMPtr<nsIDOMEvent> event; |
michael@0 | 3319 | rv = CreateReadystatechangeEvent(getter_AddRefs(event)); |
michael@0 | 3320 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3321 | |
michael@0 | 3322 | DispatchDOMEvent(nullptr, event, nullptr, nullptr); |
michael@0 | 3323 | } |
michael@0 | 3324 | |
michael@0 | 3325 | return rv; |
michael@0 | 3326 | } |
michael@0 | 3327 | |
michael@0 | 3328 | /* |
michael@0 | 3329 | * Simple helper class that just forwards the redirect callback back |
michael@0 | 3330 | * to the nsXMLHttpRequest. |
michael@0 | 3331 | */ |
michael@0 | 3332 | class AsyncVerifyRedirectCallbackForwarder MOZ_FINAL : public nsIAsyncVerifyRedirectCallback |
michael@0 | 3333 | { |
michael@0 | 3334 | public: |
michael@0 | 3335 | AsyncVerifyRedirectCallbackForwarder(nsXMLHttpRequest *xhr) |
michael@0 | 3336 | : mXHR(xhr) |
michael@0 | 3337 | { |
michael@0 | 3338 | } |
michael@0 | 3339 | |
michael@0 | 3340 | NS_DECL_CYCLE_COLLECTING_ISUPPORTS |
michael@0 | 3341 | NS_DECL_CYCLE_COLLECTION_CLASS(AsyncVerifyRedirectCallbackForwarder) |
michael@0 | 3342 | |
michael@0 | 3343 | // nsIAsyncVerifyRedirectCallback implementation |
michael@0 | 3344 | NS_IMETHOD OnRedirectVerifyCallback(nsresult result) |
michael@0 | 3345 | { |
michael@0 | 3346 | mXHR->OnRedirectVerifyCallback(result); |
michael@0 | 3347 | |
michael@0 | 3348 | return NS_OK; |
michael@0 | 3349 | } |
michael@0 | 3350 | |
michael@0 | 3351 | private: |
michael@0 | 3352 | nsRefPtr<nsXMLHttpRequest> mXHR; |
michael@0 | 3353 | }; |
michael@0 | 3354 | |
michael@0 | 3355 | NS_IMPL_CYCLE_COLLECTION(AsyncVerifyRedirectCallbackForwarder, mXHR) |
michael@0 | 3356 | |
michael@0 | 3357 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AsyncVerifyRedirectCallbackForwarder) |
michael@0 | 3358 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
michael@0 | 3359 | NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback) |
michael@0 | 3360 | NS_INTERFACE_MAP_END |
michael@0 | 3361 | |
michael@0 | 3362 | NS_IMPL_CYCLE_COLLECTING_ADDREF(AsyncVerifyRedirectCallbackForwarder) |
michael@0 | 3363 | NS_IMPL_CYCLE_COLLECTING_RELEASE(AsyncVerifyRedirectCallbackForwarder) |
michael@0 | 3364 | |
michael@0 | 3365 | |
michael@0 | 3366 | ///////////////////////////////////////////////////// |
michael@0 | 3367 | // nsIChannelEventSink methods: |
michael@0 | 3368 | // |
michael@0 | 3369 | NS_IMETHODIMP |
michael@0 | 3370 | nsXMLHttpRequest::AsyncOnChannelRedirect(nsIChannel *aOldChannel, |
michael@0 | 3371 | nsIChannel *aNewChannel, |
michael@0 | 3372 | uint32_t aFlags, |
michael@0 | 3373 | nsIAsyncVerifyRedirectCallback *callback) |
michael@0 | 3374 | { |
michael@0 | 3375 | NS_PRECONDITION(aNewChannel, "Redirect without a channel?"); |
michael@0 | 3376 | |
michael@0 | 3377 | nsresult rv; |
michael@0 | 3378 | |
michael@0 | 3379 | if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) { |
michael@0 | 3380 | rv = CheckChannelForCrossSiteRequest(aNewChannel); |
michael@0 | 3381 | if (NS_FAILED(rv)) { |
michael@0 | 3382 | NS_WARNING("nsXMLHttpRequest::OnChannelRedirect: " |
michael@0 | 3383 | "CheckChannelForCrossSiteRequest returned failure"); |
michael@0 | 3384 | return rv; |
michael@0 | 3385 | } |
michael@0 | 3386 | |
michael@0 | 3387 | // Disable redirects for preflighted cross-site requests entirely for now |
michael@0 | 3388 | // Note, do this after the call to CheckChannelForCrossSiteRequest |
michael@0 | 3389 | // to make sure that XML_HTTP_REQUEST_USE_XSITE_AC is up-to-date |
michael@0 | 3390 | if ((mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT)) { |
michael@0 | 3391 | return NS_ERROR_DOM_BAD_URI; |
michael@0 | 3392 | } |
michael@0 | 3393 | } |
michael@0 | 3394 | |
michael@0 | 3395 | // Prepare to receive callback |
michael@0 | 3396 | mRedirectCallback = callback; |
michael@0 | 3397 | mNewRedirectChannel = aNewChannel; |
michael@0 | 3398 | |
michael@0 | 3399 | if (mChannelEventSink) { |
michael@0 | 3400 | nsRefPtr<AsyncVerifyRedirectCallbackForwarder> fwd = |
michael@0 | 3401 | new AsyncVerifyRedirectCallbackForwarder(this); |
michael@0 | 3402 | |
michael@0 | 3403 | rv = mChannelEventSink->AsyncOnChannelRedirect(aOldChannel, |
michael@0 | 3404 | aNewChannel, |
michael@0 | 3405 | aFlags, fwd); |
michael@0 | 3406 | if (NS_FAILED(rv)) { |
michael@0 | 3407 | mRedirectCallback = nullptr; |
michael@0 | 3408 | mNewRedirectChannel = nullptr; |
michael@0 | 3409 | } |
michael@0 | 3410 | return rv; |
michael@0 | 3411 | } |
michael@0 | 3412 | OnRedirectVerifyCallback(NS_OK); |
michael@0 | 3413 | return NS_OK; |
michael@0 | 3414 | } |
michael@0 | 3415 | |
michael@0 | 3416 | void |
michael@0 | 3417 | nsXMLHttpRequest::OnRedirectVerifyCallback(nsresult result) |
michael@0 | 3418 | { |
michael@0 | 3419 | NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback"); |
michael@0 | 3420 | NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback"); |
michael@0 | 3421 | |
michael@0 | 3422 | if (NS_SUCCEEDED(result)) { |
michael@0 | 3423 | mChannel = mNewRedirectChannel; |
michael@0 | 3424 | |
michael@0 | 3425 | nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel)); |
michael@0 | 3426 | if (httpChannel) { |
michael@0 | 3427 | // Ensure all original headers are duplicated for the new channel (bug #553888) |
michael@0 | 3428 | for (uint32_t i = mModifiedRequestHeaders.Length(); i > 0; ) { |
michael@0 | 3429 | --i; |
michael@0 | 3430 | httpChannel->SetRequestHeader(mModifiedRequestHeaders[i].header, |
michael@0 | 3431 | mModifiedRequestHeaders[i].value, |
michael@0 | 3432 | false); |
michael@0 | 3433 | } |
michael@0 | 3434 | } |
michael@0 | 3435 | } else { |
michael@0 | 3436 | mErrorLoad = true; |
michael@0 | 3437 | } |
michael@0 | 3438 | |
michael@0 | 3439 | mNewRedirectChannel = nullptr; |
michael@0 | 3440 | |
michael@0 | 3441 | mRedirectCallback->OnRedirectVerifyCallback(result); |
michael@0 | 3442 | mRedirectCallback = nullptr; |
michael@0 | 3443 | } |
michael@0 | 3444 | |
michael@0 | 3445 | ///////////////////////////////////////////////////// |
michael@0 | 3446 | // nsIProgressEventSink methods: |
michael@0 | 3447 | // |
michael@0 | 3448 | |
michael@0 | 3449 | void |
michael@0 | 3450 | nsXMLHttpRequest::MaybeDispatchProgressEvents(bool aFinalProgress) |
michael@0 | 3451 | { |
michael@0 | 3452 | if (aFinalProgress && mProgressTimerIsActive) { |
michael@0 | 3453 | mProgressTimerIsActive = false; |
michael@0 | 3454 | mProgressNotifier->Cancel(); |
michael@0 | 3455 | } |
michael@0 | 3456 | |
michael@0 | 3457 | if (mProgressTimerIsActive || |
michael@0 | 3458 | !mProgressSinceLastProgressEvent || |
michael@0 | 3459 | mErrorLoad || |
michael@0 | 3460 | !(mState & XML_HTTP_REQUEST_ASYNC)) { |
michael@0 | 3461 | return; |
michael@0 | 3462 | } |
michael@0 | 3463 | |
michael@0 | 3464 | if (!aFinalProgress) { |
michael@0 | 3465 | StartProgressEventTimer(); |
michael@0 | 3466 | } |
michael@0 | 3467 | |
michael@0 | 3468 | // We're uploading if our state is XML_HTTP_REQUEST_OPENED or |
michael@0 | 3469 | // XML_HTTP_REQUEST_SENT |
michael@0 | 3470 | if ((XML_HTTP_REQUEST_OPENED | XML_HTTP_REQUEST_SENT) & mState) { |
michael@0 | 3471 | if (mUpload && !mUploadComplete) { |
michael@0 | 3472 | DispatchProgressEvent(mUpload, NS_LITERAL_STRING(PROGRESS_STR), |
michael@0 | 3473 | mUploadLengthComputable, mUploadTransferred, |
michael@0 | 3474 | mUploadTotal); |
michael@0 | 3475 | } |
michael@0 | 3476 | } else { |
michael@0 | 3477 | if (aFinalProgress) { |
michael@0 | 3478 | mLoadTotal = mLoadTransferred; |
michael@0 | 3479 | } |
michael@0 | 3480 | mInLoadProgressEvent = true; |
michael@0 | 3481 | DispatchProgressEvent(this, NS_LITERAL_STRING(PROGRESS_STR), |
michael@0 | 3482 | mLoadLengthComputable, mLoadTransferred, |
michael@0 | 3483 | mLoadTotal); |
michael@0 | 3484 | mInLoadProgressEvent = false; |
michael@0 | 3485 | if (mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT || |
michael@0 | 3486 | mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER) { |
michael@0 | 3487 | mResponseBody.Truncate(); |
michael@0 | 3488 | mResponseText.Truncate(); |
michael@0 | 3489 | mResultArrayBuffer = nullptr; |
michael@0 | 3490 | mArrayBufferBuilder.reset(); |
michael@0 | 3491 | } |
michael@0 | 3492 | } |
michael@0 | 3493 | |
michael@0 | 3494 | mProgressSinceLastProgressEvent = false; |
michael@0 | 3495 | } |
michael@0 | 3496 | |
michael@0 | 3497 | NS_IMETHODIMP |
michael@0 | 3498 | nsXMLHttpRequest::OnProgress(nsIRequest *aRequest, nsISupports *aContext, uint64_t aProgress, uint64_t aProgressMax) |
michael@0 | 3499 | { |
michael@0 | 3500 | // We're uploading if our state is XML_HTTP_REQUEST_OPENED or |
michael@0 | 3501 | // XML_HTTP_REQUEST_SENT |
michael@0 | 3502 | bool upload = !!((XML_HTTP_REQUEST_OPENED | XML_HTTP_REQUEST_SENT) & mState); |
michael@0 | 3503 | // When uploading, OnProgress reports also headers in aProgress and aProgressMax. |
michael@0 | 3504 | // So, try to remove the headers, if possible. |
michael@0 | 3505 | bool lengthComputable = (aProgressMax != UINT64_MAX); |
michael@0 | 3506 | if (upload) { |
michael@0 | 3507 | uint64_t loaded = aProgress; |
michael@0 | 3508 | uint64_t total = aProgressMax; |
michael@0 | 3509 | if (lengthComputable) { |
michael@0 | 3510 | uint64_t headerSize = aProgressMax - mUploadTotal; |
michael@0 | 3511 | loaded -= headerSize; |
michael@0 | 3512 | total -= headerSize; |
michael@0 | 3513 | } |
michael@0 | 3514 | mUploadLengthComputable = lengthComputable; |
michael@0 | 3515 | mUploadTransferred = loaded; |
michael@0 | 3516 | mProgressSinceLastProgressEvent = true; |
michael@0 | 3517 | |
michael@0 | 3518 | MaybeDispatchProgressEvents(false); |
michael@0 | 3519 | } else { |
michael@0 | 3520 | mLoadLengthComputable = lengthComputable; |
michael@0 | 3521 | mLoadTotal = lengthComputable ? aProgressMax : 0; |
michael@0 | 3522 | |
michael@0 | 3523 | // Don't dispatch progress events here. OnDataAvailable will take care |
michael@0 | 3524 | // of that. |
michael@0 | 3525 | } |
michael@0 | 3526 | |
michael@0 | 3527 | if (mProgressEventSink) { |
michael@0 | 3528 | mProgressEventSink->OnProgress(aRequest, aContext, aProgress, |
michael@0 | 3529 | aProgressMax); |
michael@0 | 3530 | } |
michael@0 | 3531 | |
michael@0 | 3532 | return NS_OK; |
michael@0 | 3533 | } |
michael@0 | 3534 | |
michael@0 | 3535 | NS_IMETHODIMP |
michael@0 | 3536 | nsXMLHttpRequest::OnStatus(nsIRequest *aRequest, nsISupports *aContext, nsresult aStatus, const char16_t *aStatusArg) |
michael@0 | 3537 | { |
michael@0 | 3538 | if (mProgressEventSink) { |
michael@0 | 3539 | mProgressEventSink->OnStatus(aRequest, aContext, aStatus, aStatusArg); |
michael@0 | 3540 | } |
michael@0 | 3541 | |
michael@0 | 3542 | return NS_OK; |
michael@0 | 3543 | } |
michael@0 | 3544 | |
michael@0 | 3545 | bool |
michael@0 | 3546 | nsXMLHttpRequest::AllowUploadProgress() |
michael@0 | 3547 | { |
michael@0 | 3548 | return !(mState & XML_HTTP_REQUEST_USE_XSITE_AC) || |
michael@0 | 3549 | (mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT); |
michael@0 | 3550 | } |
michael@0 | 3551 | |
michael@0 | 3552 | ///////////////////////////////////////////////////// |
michael@0 | 3553 | // nsIInterfaceRequestor methods: |
michael@0 | 3554 | // |
michael@0 | 3555 | NS_IMETHODIMP |
michael@0 | 3556 | nsXMLHttpRequest::GetInterface(const nsIID & aIID, void **aResult) |
michael@0 | 3557 | { |
michael@0 | 3558 | nsresult rv; |
michael@0 | 3559 | |
michael@0 | 3560 | // Make sure to return ourselves for the channel event sink interface and |
michael@0 | 3561 | // progress event sink interface, no matter what. We can forward these to |
michael@0 | 3562 | // mNotificationCallbacks if it wants to get notifications for them. But we |
michael@0 | 3563 | // need to see these notifications for proper functioning. |
michael@0 | 3564 | if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { |
michael@0 | 3565 | mChannelEventSink = do_GetInterface(mNotificationCallbacks); |
michael@0 | 3566 | *aResult = static_cast<nsIChannelEventSink*>(EnsureXPCOMifier().take()); |
michael@0 | 3567 | return NS_OK; |
michael@0 | 3568 | } else if (aIID.Equals(NS_GET_IID(nsIProgressEventSink))) { |
michael@0 | 3569 | mProgressEventSink = do_GetInterface(mNotificationCallbacks); |
michael@0 | 3570 | *aResult = static_cast<nsIProgressEventSink*>(EnsureXPCOMifier().take()); |
michael@0 | 3571 | return NS_OK; |
michael@0 | 3572 | } |
michael@0 | 3573 | |
michael@0 | 3574 | // Now give mNotificationCallbacks (if non-null) a chance to return the |
michael@0 | 3575 | // desired interface. |
michael@0 | 3576 | if (mNotificationCallbacks) { |
michael@0 | 3577 | rv = mNotificationCallbacks->GetInterface(aIID, aResult); |
michael@0 | 3578 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 3579 | NS_ASSERTION(*aResult, "Lying nsIInterfaceRequestor implementation!"); |
michael@0 | 3580 | return rv; |
michael@0 | 3581 | } |
michael@0 | 3582 | } |
michael@0 | 3583 | |
michael@0 | 3584 | if (mState & XML_HTTP_REQUEST_BACKGROUND) { |
michael@0 | 3585 | nsCOMPtr<nsIInterfaceRequestor> badCertHandler(do_CreateInstance(NS_BADCERTHANDLER_CONTRACTID, &rv)); |
michael@0 | 3586 | |
michael@0 | 3587 | // Ignore failure to get component, we may not have all its dependencies |
michael@0 | 3588 | // available |
michael@0 | 3589 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 3590 | rv = badCertHandler->GetInterface(aIID, aResult); |
michael@0 | 3591 | if (NS_SUCCEEDED(rv)) |
michael@0 | 3592 | return rv; |
michael@0 | 3593 | } |
michael@0 | 3594 | } |
michael@0 | 3595 | else if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) || |
michael@0 | 3596 | aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) { |
michael@0 | 3597 | |
michael@0 | 3598 | nsCOMPtr<nsIURI> uri; |
michael@0 | 3599 | rv = mChannel->GetURI(getter_AddRefs(uri)); |
michael@0 | 3600 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3601 | |
michael@0 | 3602 | // Verify that it's ok to prompt for credentials here, per spec |
michael@0 | 3603 | // http://xhr.spec.whatwg.org/#the-send%28%29-method |
michael@0 | 3604 | bool showPrompt = true; |
michael@0 | 3605 | |
michael@0 | 3606 | // If authentication fails, XMLHttpRequest origin and |
michael@0 | 3607 | // the request URL are same origin, ... |
michael@0 | 3608 | /* Disabled - bug: 799540 |
michael@0 | 3609 | if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) { |
michael@0 | 3610 | showPrompt = false; |
michael@0 | 3611 | } |
michael@0 | 3612 | */ |
michael@0 | 3613 | |
michael@0 | 3614 | // ... Authorization is not in the list of author request headers, ... |
michael@0 | 3615 | if (showPrompt) { |
michael@0 | 3616 | for (uint32_t i = 0, len = mModifiedRequestHeaders.Length(); i < len; ++i) { |
michael@0 | 3617 | if (mModifiedRequestHeaders[i].header. |
michael@0 | 3618 | LowerCaseEqualsLiteral("authorization")) { |
michael@0 | 3619 | showPrompt = false; |
michael@0 | 3620 | break; |
michael@0 | 3621 | } |
michael@0 | 3622 | } |
michael@0 | 3623 | } |
michael@0 | 3624 | |
michael@0 | 3625 | // ... request username is null, and request password is null, |
michael@0 | 3626 | if (showPrompt) { |
michael@0 | 3627 | |
michael@0 | 3628 | nsCString username; |
michael@0 | 3629 | rv = uri->GetUsername(username); |
michael@0 | 3630 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3631 | |
michael@0 | 3632 | nsCString password; |
michael@0 | 3633 | rv = uri->GetPassword(password); |
michael@0 | 3634 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3635 | |
michael@0 | 3636 | if (!username.IsEmpty() || !password.IsEmpty()) { |
michael@0 | 3637 | showPrompt = false; |
michael@0 | 3638 | } |
michael@0 | 3639 | } |
michael@0 | 3640 | |
michael@0 | 3641 | // ... user agents should prompt the end user for their username and password. |
michael@0 | 3642 | if (!showPrompt) { |
michael@0 | 3643 | nsRefPtr<XMLHttpRequestAuthPrompt> prompt = new XMLHttpRequestAuthPrompt(); |
michael@0 | 3644 | if (!prompt) |
michael@0 | 3645 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 3646 | |
michael@0 | 3647 | return prompt->QueryInterface(aIID, aResult); |
michael@0 | 3648 | } |
michael@0 | 3649 | |
michael@0 | 3650 | nsCOMPtr<nsIPromptFactory> wwatch = |
michael@0 | 3651 | do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); |
michael@0 | 3652 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3653 | |
michael@0 | 3654 | // Get the an auth prompter for our window so that the parenting |
michael@0 | 3655 | // of the dialogs works as it should when using tabs. |
michael@0 | 3656 | |
michael@0 | 3657 | nsCOMPtr<nsIDOMWindow> window; |
michael@0 | 3658 | if (GetOwner()) { |
michael@0 | 3659 | window = GetOwner()->GetOuterWindow(); |
michael@0 | 3660 | } |
michael@0 | 3661 | |
michael@0 | 3662 | return wwatch->GetPrompt(window, aIID, |
michael@0 | 3663 | reinterpret_cast<void**>(aResult)); |
michael@0 | 3664 | } |
michael@0 | 3665 | // Now check for the various XHR non-DOM interfaces, except |
michael@0 | 3666 | // nsIProgressEventSink and nsIChannelEventSink which we already |
michael@0 | 3667 | // handled above. |
michael@0 | 3668 | else if (aIID.Equals(NS_GET_IID(nsIStreamListener))) { |
michael@0 | 3669 | *aResult = static_cast<nsIStreamListener*>(EnsureXPCOMifier().take()); |
michael@0 | 3670 | return NS_OK; |
michael@0 | 3671 | } |
michael@0 | 3672 | else if (aIID.Equals(NS_GET_IID(nsIRequestObserver))) { |
michael@0 | 3673 | *aResult = static_cast<nsIRequestObserver*>(EnsureXPCOMifier().take()); |
michael@0 | 3674 | return NS_OK; |
michael@0 | 3675 | } |
michael@0 | 3676 | else if (aIID.Equals(NS_GET_IID(nsITimerCallback))) { |
michael@0 | 3677 | *aResult = static_cast<nsITimerCallback*>(EnsureXPCOMifier().take()); |
michael@0 | 3678 | return NS_OK; |
michael@0 | 3679 | } |
michael@0 | 3680 | |
michael@0 | 3681 | return QueryInterface(aIID, aResult); |
michael@0 | 3682 | } |
michael@0 | 3683 | |
michael@0 | 3684 | void |
michael@0 | 3685 | nsXMLHttpRequest::GetInterface(JSContext* aCx, nsIJSID* aIID, |
michael@0 | 3686 | JS::MutableHandle<JS::Value> aRetval, |
michael@0 | 3687 | ErrorResult& aRv) |
michael@0 | 3688 | { |
michael@0 | 3689 | dom::GetInterface(aCx, this, aIID, aRetval, aRv); |
michael@0 | 3690 | } |
michael@0 | 3691 | |
michael@0 | 3692 | nsXMLHttpRequestUpload* |
michael@0 | 3693 | nsXMLHttpRequest::Upload() |
michael@0 | 3694 | { |
michael@0 | 3695 | if (!mUpload) { |
michael@0 | 3696 | mUpload = new nsXMLHttpRequestUpload(this); |
michael@0 | 3697 | } |
michael@0 | 3698 | return mUpload; |
michael@0 | 3699 | } |
michael@0 | 3700 | |
michael@0 | 3701 | NS_IMETHODIMP |
michael@0 | 3702 | nsXMLHttpRequest::GetUpload(nsIXMLHttpRequestUpload** aUpload) |
michael@0 | 3703 | { |
michael@0 | 3704 | nsRefPtr<nsXMLHttpRequestUpload> upload = Upload(); |
michael@0 | 3705 | upload.forget(aUpload); |
michael@0 | 3706 | return NS_OK; |
michael@0 | 3707 | } |
michael@0 | 3708 | |
michael@0 | 3709 | bool |
michael@0 | 3710 | nsXMLHttpRequest::MozAnon() |
michael@0 | 3711 | { |
michael@0 | 3712 | return mIsAnon; |
michael@0 | 3713 | } |
michael@0 | 3714 | |
michael@0 | 3715 | NS_IMETHODIMP |
michael@0 | 3716 | nsXMLHttpRequest::GetMozAnon(bool* aAnon) |
michael@0 | 3717 | { |
michael@0 | 3718 | *aAnon = MozAnon(); |
michael@0 | 3719 | return NS_OK; |
michael@0 | 3720 | } |
michael@0 | 3721 | |
michael@0 | 3722 | bool |
michael@0 | 3723 | nsXMLHttpRequest::MozSystem() |
michael@0 | 3724 | { |
michael@0 | 3725 | return IsSystemXHR(); |
michael@0 | 3726 | } |
michael@0 | 3727 | |
michael@0 | 3728 | NS_IMETHODIMP |
michael@0 | 3729 | nsXMLHttpRequest::GetMozSystem(bool* aSystem) |
michael@0 | 3730 | { |
michael@0 | 3731 | *aSystem = MozSystem(); |
michael@0 | 3732 | return NS_OK; |
michael@0 | 3733 | } |
michael@0 | 3734 | |
michael@0 | 3735 | void |
michael@0 | 3736 | nsXMLHttpRequest::HandleTimeoutCallback() |
michael@0 | 3737 | { |
michael@0 | 3738 | if (mState & XML_HTTP_REQUEST_DONE) { |
michael@0 | 3739 | NS_NOTREACHED("nsXMLHttpRequest::HandleTimeoutCallback with completed request"); |
michael@0 | 3740 | // do nothing! |
michael@0 | 3741 | return; |
michael@0 | 3742 | } |
michael@0 | 3743 | |
michael@0 | 3744 | CloseRequestWithError(NS_LITERAL_STRING(TIMEOUT_STR), |
michael@0 | 3745 | XML_HTTP_REQUEST_TIMED_OUT); |
michael@0 | 3746 | } |
michael@0 | 3747 | |
michael@0 | 3748 | NS_IMETHODIMP |
michael@0 | 3749 | nsXMLHttpRequest::Notify(nsITimer* aTimer) |
michael@0 | 3750 | { |
michael@0 | 3751 | if (mProgressNotifier == aTimer) { |
michael@0 | 3752 | HandleProgressTimerCallback(); |
michael@0 | 3753 | return NS_OK; |
michael@0 | 3754 | } |
michael@0 | 3755 | |
michael@0 | 3756 | if (mTimeoutTimer == aTimer) { |
michael@0 | 3757 | HandleTimeoutCallback(); |
michael@0 | 3758 | return NS_OK; |
michael@0 | 3759 | } |
michael@0 | 3760 | |
michael@0 | 3761 | // Just in case some JS user wants to QI to nsITimerCallback and play with us... |
michael@0 | 3762 | NS_WARNING("Unexpected timer!"); |
michael@0 | 3763 | return NS_ERROR_INVALID_POINTER; |
michael@0 | 3764 | } |
michael@0 | 3765 | |
michael@0 | 3766 | void |
michael@0 | 3767 | nsXMLHttpRequest::HandleProgressTimerCallback() |
michael@0 | 3768 | { |
michael@0 | 3769 | mProgressTimerIsActive = false; |
michael@0 | 3770 | MaybeDispatchProgressEvents(false); |
michael@0 | 3771 | } |
michael@0 | 3772 | |
michael@0 | 3773 | void |
michael@0 | 3774 | nsXMLHttpRequest::StartProgressEventTimer() |
michael@0 | 3775 | { |
michael@0 | 3776 | if (!mProgressNotifier) { |
michael@0 | 3777 | mProgressNotifier = do_CreateInstance(NS_TIMER_CONTRACTID); |
michael@0 | 3778 | } |
michael@0 | 3779 | if (mProgressNotifier) { |
michael@0 | 3780 | mProgressTimerIsActive = true; |
michael@0 | 3781 | mProgressNotifier->Cancel(); |
michael@0 | 3782 | mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL, |
michael@0 | 3783 | nsITimer::TYPE_ONE_SHOT); |
michael@0 | 3784 | } |
michael@0 | 3785 | } |
michael@0 | 3786 | |
michael@0 | 3787 | already_AddRefed<nsXMLHttpRequestXPCOMifier> |
michael@0 | 3788 | nsXMLHttpRequest::EnsureXPCOMifier() |
michael@0 | 3789 | { |
michael@0 | 3790 | if (!mXPCOMifier) { |
michael@0 | 3791 | mXPCOMifier = new nsXMLHttpRequestXPCOMifier(this); |
michael@0 | 3792 | } |
michael@0 | 3793 | nsRefPtr<nsXMLHttpRequestXPCOMifier> newRef(mXPCOMifier); |
michael@0 | 3794 | return newRef.forget(); |
michael@0 | 3795 | } |
michael@0 | 3796 | |
michael@0 | 3797 | NS_IMPL_ISUPPORTS(nsXMLHttpRequest::nsHeaderVisitor, nsIHttpHeaderVisitor) |
michael@0 | 3798 | |
michael@0 | 3799 | NS_IMETHODIMP nsXMLHttpRequest:: |
michael@0 | 3800 | nsHeaderVisitor::VisitHeader(const nsACString &header, const nsACString &value) |
michael@0 | 3801 | { |
michael@0 | 3802 | if (mXHR->IsSafeHeader(header, mHttpChannel)) { |
michael@0 | 3803 | mHeaders.Append(header); |
michael@0 | 3804 | mHeaders.Append(": "); |
michael@0 | 3805 | mHeaders.Append(value); |
michael@0 | 3806 | mHeaders.Append("\r\n"); |
michael@0 | 3807 | } |
michael@0 | 3808 | return NS_OK; |
michael@0 | 3809 | } |
michael@0 | 3810 | |
michael@0 | 3811 | // nsXMLHttpRequestXPCOMifier implementation |
michael@0 | 3812 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXMLHttpRequestXPCOMifier) |
michael@0 | 3813 | NS_INTERFACE_MAP_ENTRY(nsIStreamListener) |
michael@0 | 3814 | NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) |
michael@0 | 3815 | NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink) |
michael@0 | 3816 | NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink) |
michael@0 | 3817 | NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) |
michael@0 | 3818 | NS_INTERFACE_MAP_ENTRY(nsITimerCallback) |
michael@0 | 3819 | NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener) |
michael@0 | 3820 | NS_INTERFACE_MAP_END |
michael@0 | 3821 | |
michael@0 | 3822 | NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXMLHttpRequestXPCOMifier) |
michael@0 | 3823 | NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXMLHttpRequestXPCOMifier) |
michael@0 | 3824 | |
michael@0 | 3825 | // Can't NS_IMPL_CYCLE_COLLECTION( because mXHR has ambiguous |
michael@0 | 3826 | // inheritance from nsISupports. |
michael@0 | 3827 | NS_IMPL_CYCLE_COLLECTION_CLASS(nsXMLHttpRequestXPCOMifier) |
michael@0 | 3828 | |
michael@0 | 3829 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXMLHttpRequestXPCOMifier) |
michael@0 | 3830 | if (tmp->mXHR) { |
michael@0 | 3831 | tmp->mXHR->mXPCOMifier = nullptr; |
michael@0 | 3832 | } |
michael@0 | 3833 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mXHR) |
michael@0 | 3834 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
michael@0 | 3835 | |
michael@0 | 3836 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXMLHttpRequestXPCOMifier) |
michael@0 | 3837 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXHR) |
michael@0 | 3838 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
michael@0 | 3839 | |
michael@0 | 3840 | NS_IMETHODIMP |
michael@0 | 3841 | nsXMLHttpRequestXPCOMifier::GetInterface(const nsIID & aIID, void **aResult) |
michael@0 | 3842 | { |
michael@0 | 3843 | // Return ourselves for the things we implement (except |
michael@0 | 3844 | // nsIInterfaceRequestor) and the XHR for the rest. |
michael@0 | 3845 | if (!aIID.Equals(NS_GET_IID(nsIInterfaceRequestor))) { |
michael@0 | 3846 | nsresult rv = QueryInterface(aIID, aResult); |
michael@0 | 3847 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 3848 | return rv; |
michael@0 | 3849 | } |
michael@0 | 3850 | } |
michael@0 | 3851 | |
michael@0 | 3852 | return mXHR->GetInterface(aIID, aResult); |
michael@0 | 3853 | } |
michael@0 | 3854 | |
michael@0 | 3855 | namespace mozilla { |
michael@0 | 3856 | |
michael@0 | 3857 | ArrayBufferBuilder::ArrayBufferBuilder() |
michael@0 | 3858 | : mDataPtr(nullptr), |
michael@0 | 3859 | mCapacity(0), |
michael@0 | 3860 | mLength(0) |
michael@0 | 3861 | { |
michael@0 | 3862 | } |
michael@0 | 3863 | |
michael@0 | 3864 | ArrayBufferBuilder::~ArrayBufferBuilder() |
michael@0 | 3865 | { |
michael@0 | 3866 | reset(); |
michael@0 | 3867 | } |
michael@0 | 3868 | |
michael@0 | 3869 | void |
michael@0 | 3870 | ArrayBufferBuilder::reset() |
michael@0 | 3871 | { |
michael@0 | 3872 | if (mDataPtr) { |
michael@0 | 3873 | JS_free(nullptr, mDataPtr); |
michael@0 | 3874 | } |
michael@0 | 3875 | mDataPtr = nullptr; |
michael@0 | 3876 | mCapacity = mLength = 0; |
michael@0 | 3877 | } |
michael@0 | 3878 | |
michael@0 | 3879 | bool |
michael@0 | 3880 | ArrayBufferBuilder::setCapacity(uint32_t aNewCap) |
michael@0 | 3881 | { |
michael@0 | 3882 | uint8_t *newdata = (uint8_t *) JS_ReallocateArrayBufferContents(nullptr, aNewCap, mDataPtr, mCapacity); |
michael@0 | 3883 | if (!newdata) { |
michael@0 | 3884 | return false; |
michael@0 | 3885 | } |
michael@0 | 3886 | |
michael@0 | 3887 | mDataPtr = newdata; |
michael@0 | 3888 | mCapacity = aNewCap; |
michael@0 | 3889 | if (mLength > aNewCap) { |
michael@0 | 3890 | mLength = aNewCap; |
michael@0 | 3891 | } |
michael@0 | 3892 | |
michael@0 | 3893 | return true; |
michael@0 | 3894 | } |
michael@0 | 3895 | |
michael@0 | 3896 | bool |
michael@0 | 3897 | ArrayBufferBuilder::append(const uint8_t *aNewData, uint32_t aDataLen, |
michael@0 | 3898 | uint32_t aMaxGrowth) |
michael@0 | 3899 | { |
michael@0 | 3900 | if (mLength + aDataLen > mCapacity) { |
michael@0 | 3901 | uint32_t newcap; |
michael@0 | 3902 | // Double while under aMaxGrowth or if not specified. |
michael@0 | 3903 | if (!aMaxGrowth || mCapacity < aMaxGrowth) { |
michael@0 | 3904 | newcap = mCapacity * 2; |
michael@0 | 3905 | } else { |
michael@0 | 3906 | newcap = mCapacity + aMaxGrowth; |
michael@0 | 3907 | } |
michael@0 | 3908 | |
michael@0 | 3909 | // But make sure there's always enough to satisfy our request. |
michael@0 | 3910 | if (newcap < mLength + aDataLen) { |
michael@0 | 3911 | newcap = mLength + aDataLen; |
michael@0 | 3912 | } |
michael@0 | 3913 | |
michael@0 | 3914 | // Did we overflow? |
michael@0 | 3915 | if (newcap < mCapacity) { |
michael@0 | 3916 | return false; |
michael@0 | 3917 | } |
michael@0 | 3918 | |
michael@0 | 3919 | if (!setCapacity(newcap)) { |
michael@0 | 3920 | return false; |
michael@0 | 3921 | } |
michael@0 | 3922 | } |
michael@0 | 3923 | |
michael@0 | 3924 | // Assert that the region isn't overlapping so we can memcpy. |
michael@0 | 3925 | MOZ_ASSERT(!areOverlappingRegions(aNewData, aDataLen, mDataPtr + mLength, |
michael@0 | 3926 | aDataLen)); |
michael@0 | 3927 | |
michael@0 | 3928 | memcpy(mDataPtr + mLength, aNewData, aDataLen); |
michael@0 | 3929 | mLength += aDataLen; |
michael@0 | 3930 | |
michael@0 | 3931 | return true; |
michael@0 | 3932 | } |
michael@0 | 3933 | |
michael@0 | 3934 | JSObject* |
michael@0 | 3935 | ArrayBufferBuilder::getArrayBuffer(JSContext* aCx) |
michael@0 | 3936 | { |
michael@0 | 3937 | // we need to check for mLength == 0, because nothing may have been |
michael@0 | 3938 | // added |
michael@0 | 3939 | if (mCapacity > mLength || mLength == 0) { |
michael@0 | 3940 | if (!setCapacity(mLength)) { |
michael@0 | 3941 | return nullptr; |
michael@0 | 3942 | } |
michael@0 | 3943 | } |
michael@0 | 3944 | |
michael@0 | 3945 | JSObject* obj = JS_NewArrayBufferWithContents(aCx, mLength, mDataPtr); |
michael@0 | 3946 | mDataPtr = nullptr; |
michael@0 | 3947 | mLength = mCapacity = 0; |
michael@0 | 3948 | if (!obj) { |
michael@0 | 3949 | js_free(mDataPtr); |
michael@0 | 3950 | return nullptr; |
michael@0 | 3951 | } |
michael@0 | 3952 | return obj; |
michael@0 | 3953 | } |
michael@0 | 3954 | |
michael@0 | 3955 | /* static */ bool |
michael@0 | 3956 | ArrayBufferBuilder::areOverlappingRegions(const uint8_t* aStart1, |
michael@0 | 3957 | uint32_t aLength1, |
michael@0 | 3958 | const uint8_t* aStart2, |
michael@0 | 3959 | uint32_t aLength2) |
michael@0 | 3960 | { |
michael@0 | 3961 | const uint8_t* end1 = aStart1 + aLength1; |
michael@0 | 3962 | const uint8_t* end2 = aStart2 + aLength2; |
michael@0 | 3963 | |
michael@0 | 3964 | const uint8_t* max_start = aStart1 > aStart2 ? aStart1 : aStart2; |
michael@0 | 3965 | const uint8_t* min_end = end1 < end2 ? end1 : end2; |
michael@0 | 3966 | |
michael@0 | 3967 | return max_start < min_end; |
michael@0 | 3968 | } |
michael@0 | 3969 | |
michael@0 | 3970 | } // namespace mozilla |