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