1/*
2 * Copyright (C) 2004-2016 Apple Inc. All rights reserved.
3 * Copyright (C) 2005-2007 Alexey Proskuryakov <ap@webkit.org>
4 * Copyright (C) 2007, 2008 Julien Chaffraix <jchaffraix@webkit.org>
5 * Copyright (C) 2008, 2011 Google Inc. All rights reserved.
6 * Copyright (C) 2012 Intel Corporation
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22
23#include "config.h"
24#include "XMLHttpRequest.h"
25
26#include "Blob.h"
27#include "CachedResourceRequestInitiators.h"
28#include "ContentSecurityPolicy.h"
29#include "CrossOriginAccessControl.h"
30#include "DOMFormData.h"
31#include "DOMWindow.h"
32#include "Event.h"
33#include "EventNames.h"
34#include "File.h"
35#include "HTMLDocument.h"
36#include "HTTPHeaderNames.h"
37#include "HTTPHeaderValues.h"
38#include "HTTPParsers.h"
39#include "InspectorInstrumentation.h"
40#include "JSDOMBinding.h"
41#include "JSDOMWindow.h"
42#include "MIMETypeRegistry.h"
43#include "MemoryCache.h"
44#include "ParsedContentType.h"
45#include "ResourceError.h"
46#include "ResourceRequest.h"
47#include "RuntimeApplicationChecks.h"
48#include "SecurityOriginPolicy.h"
49#include "Settings.h"
50#include "SharedBuffer.h"
51#include "StringAdaptors.h"
52#include "TextResourceDecoder.h"
53#include "ThreadableLoader.h"
54#include "XMLDocument.h"
55#include "XMLHttpRequestProgressEvent.h"
56#include "XMLHttpRequestUpload.h"
57#include "markup.h"
58#include <JavaScriptCore/ArrayBuffer.h>
59#include <JavaScriptCore/ArrayBufferView.h>
60#include <JavaScriptCore/JSCInlines.h>
61#include <JavaScriptCore/JSLock.h>
62#include <wtf/IsoMallocInlines.h>
63#include <wtf/RefCountedLeakCounter.h>
64#include <wtf/StdLibExtras.h>
65#include <wtf/text/CString.h>
66
67namespace WebCore {
68
69static const Seconds maximumIntervalForUserGestureForwarding { 10_s };
70
71WTF_MAKE_ISO_ALLOCATED_IMPL(XMLHttpRequest);
72
73DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, xmlHttpRequestCounter, ("XMLHttpRequest"));
74
75// Histogram enum to see when we can deprecate xhr.send(ArrayBuffer).
76enum XMLHttpRequestSendArrayBufferOrView {
77 XMLHttpRequestSendArrayBuffer,
78 XMLHttpRequestSendArrayBufferView,
79 XMLHttpRequestSendArrayBufferOrViewMax,
80};
81
82static void replaceCharsetInMediaTypeIfNeeded(String& mediaType)
83{
84 auto parsedContentType = ParsedContentType::create(mediaType);
85 if (!parsedContentType || parsedContentType->charset().isEmpty() || equalIgnoringASCIICase(parsedContentType->charset(), "UTF-8"))
86 return;
87
88 parsedContentType->setCharset("UTF-8");
89 mediaType = parsedContentType->serialize();
90}
91
92static void logConsoleError(ScriptExecutionContext* context, const String& message)
93{
94 if (!context)
95 return;
96 // FIXME: It's not good to report the bad usage without indicating what source line it came from.
97 // We should pass additional parameters so we can tell the console where the mistake occurred.
98 context->addConsoleMessage(MessageSource::JS, MessageLevel::Error, message);
99}
100
101Ref<XMLHttpRequest> XMLHttpRequest::create(ScriptExecutionContext& context)
102{
103 auto xmlHttpRequest = adoptRef(*new XMLHttpRequest(context));
104 xmlHttpRequest->suspendIfNeeded();
105 return xmlHttpRequest;
106}
107
108XMLHttpRequest::XMLHttpRequest(ScriptExecutionContext& context)
109 : ActiveDOMObject(&context)
110 , m_async(true)
111 , m_includeCredentials(false)
112 , m_sendFlag(false)
113 , m_createdDocument(false)
114 , m_error(false)
115 , m_uploadListenerFlag(false)
116 , m_uploadComplete(false)
117 , m_wasAbortedByClient(false)
118 , m_responseCacheIsValid(false)
119 , m_dispatchErrorOnResuming(false)
120 , m_readyState(static_cast<unsigned>(UNSENT))
121 , m_responseType(static_cast<unsigned>(ResponseType::EmptyString))
122 , m_progressEventThrottle(this)
123 , m_resumeTimer(*this, &XMLHttpRequest::resumeTimerFired)
124 , m_networkErrorTimer(*this, &XMLHttpRequest::networkErrorTimerFired)
125 , m_timeoutTimer(*this, &XMLHttpRequest::didReachTimeout)
126 , m_maximumIntervalForUserGestureForwarding(maximumIntervalForUserGestureForwarding)
127{
128#ifndef NDEBUG
129 xmlHttpRequestCounter.increment();
130#endif
131}
132
133XMLHttpRequest::~XMLHttpRequest()
134{
135#ifndef NDEBUG
136 xmlHttpRequestCounter.decrement();
137#endif
138}
139
140Document* XMLHttpRequest::document() const
141{
142 ASSERT(scriptExecutionContext());
143 return downcast<Document>(scriptExecutionContext());
144}
145
146SecurityOrigin* XMLHttpRequest::securityOrigin() const
147{
148 return scriptExecutionContext()->securityOrigin();
149}
150
151#if ENABLE(DASHBOARD_SUPPORT)
152
153bool XMLHttpRequest::usesDashboardBackwardCompatibilityMode() const
154{
155 if (scriptExecutionContext()->isWorkerGlobalScope())
156 return false;
157 return document()->settings().usesDashboardBackwardCompatibilityMode();
158}
159
160#endif
161
162ExceptionOr<OwnedString> XMLHttpRequest::responseText()
163{
164 if (responseType() != ResponseType::EmptyString && responseType() != ResponseType::Text)
165 return Exception { InvalidStateError };
166 return OwnedString { responseTextIgnoringResponseType() };
167}
168
169void XMLHttpRequest::didCacheResponse()
170{
171 ASSERT(doneWithoutErrors());
172 m_responseCacheIsValid = true;
173 m_responseBuilder.clear();
174}
175
176ExceptionOr<Document*> XMLHttpRequest::responseXML()
177{
178 ASSERT(scriptExecutionContext()->isDocument());
179
180 if (responseType() != ResponseType::EmptyString && responseType() != ResponseType::Document)
181 return Exception { InvalidStateError };
182
183 if (!doneWithoutErrors())
184 return nullptr;
185
186 if (!m_createdDocument) {
187 String mimeType = responseMIMEType();
188 bool isHTML = equalLettersIgnoringASCIICase(mimeType, "text/html");
189
190 // The W3C spec requires the final MIME type to be some valid XML type, or text/html.
191 // If it is text/html, then the responseType of "document" must have been supplied explicitly.
192 if ((m_response.isHTTP() && !responseIsXML() && !isHTML)
193 || (isHTML && responseType() == ResponseType::EmptyString)) {
194 m_responseDocument = nullptr;
195 } else {
196 if (isHTML)
197 m_responseDocument = HTMLDocument::create(nullptr, m_url);
198 else
199 m_responseDocument = XMLDocument::create(nullptr, m_url);
200 m_responseDocument->overrideLastModified(m_response.lastModified());
201 m_responseDocument->setContent(m_responseBuilder.toStringPreserveCapacity());
202 m_responseDocument->setContextDocument(downcast<Document>(*scriptExecutionContext()));
203 m_responseDocument->setSecurityOriginPolicy(scriptExecutionContext()->securityOriginPolicy());
204 m_responseDocument->overrideMIMEType(mimeType);
205
206 if (!m_responseDocument->wellFormed())
207 m_responseDocument = nullptr;
208 }
209 m_createdDocument = true;
210 }
211
212 return m_responseDocument.get();
213}
214
215Ref<Blob> XMLHttpRequest::createResponseBlob()
216{
217 ASSERT(responseType() == ResponseType::Blob);
218 ASSERT(doneWithoutErrors());
219
220 // FIXME: We just received the data from NetworkProcess, and are sending it back. This is inefficient.
221 Vector<uint8_t> data;
222 if (m_binaryResponseBuilder)
223 data.append(m_binaryResponseBuilder->data(), m_binaryResponseBuilder->size());
224 m_binaryResponseBuilder = nullptr;
225 String normalizedContentType = Blob::normalizedContentType(responseMIMEType()); // responseMIMEType defaults to text/xml which may be incorrect.
226 return Blob::create(WTFMove(data), normalizedContentType);
227}
228
229RefPtr<ArrayBuffer> XMLHttpRequest::createResponseArrayBuffer()
230{
231 ASSERT(responseType() == ResponseType::Arraybuffer);
232 ASSERT(doneWithoutErrors());
233
234 auto result = m_binaryResponseBuilder ? m_binaryResponseBuilder->tryCreateArrayBuffer() : ArrayBuffer::create(nullptr, 0);
235 m_binaryResponseBuilder = nullptr;
236 return result;
237}
238
239ExceptionOr<void> XMLHttpRequest::setTimeout(unsigned timeout)
240{
241 if (scriptExecutionContext()->isDocument() && !m_async) {
242 logConsoleError(scriptExecutionContext(), "XMLHttpRequest.timeout cannot be set for synchronous HTTP(S) requests made from the window context.");
243 return Exception { InvalidAccessError };
244 }
245 m_timeoutMilliseconds = timeout;
246 if (!m_timeoutTimer.isActive())
247 return { };
248
249 // If timeout is zero, we should use the default network timeout. But we disabled it so let's mimic it with a 60 seconds timeout value.
250 Seconds interval = Seconds { m_timeoutMilliseconds ? m_timeoutMilliseconds / 1000. : 60. } - (MonotonicTime::now() - m_sendingTime);
251 m_timeoutTimer.startOneShot(std::max(interval, 0_s));
252 return { };
253}
254
255ExceptionOr<void> XMLHttpRequest::setResponseType(ResponseType type)
256{
257 if (!scriptExecutionContext()->isDocument() && type == ResponseType::Document)
258 return { };
259
260 if (readyState() >= LOADING)
261 return Exception { InvalidStateError };
262
263 // Newer functionality is not available to synchronous requests in window contexts, as a spec-mandated
264 // attempt to discourage synchronous XHR use. responseType is one such piece of functionality.
265 // We'll only disable this functionality for HTTP(S) requests since sync requests for local protocols
266 // such as file: and data: still make sense to allow.
267 if (!m_async && scriptExecutionContext()->isDocument() && m_url.protocolIsInHTTPFamily()) {
268 logConsoleError(scriptExecutionContext(), "XMLHttpRequest.responseType cannot be changed for synchronous HTTP(S) requests made from the window context.");
269 return Exception { InvalidAccessError };
270 }
271
272 m_responseType = static_cast<unsigned>(type);
273 return { };
274}
275
276String XMLHttpRequest::responseURL() const
277{
278 URL responseURL(m_response.url());
279 responseURL.removeFragmentIdentifier();
280
281 return responseURL.string();
282}
283
284XMLHttpRequestUpload& XMLHttpRequest::upload()
285{
286 if (!m_upload)
287 m_upload = std::make_unique<XMLHttpRequestUpload>(*this);
288 return *m_upload;
289}
290
291void XMLHttpRequest::changeState(State newState)
292{
293 if (readyState() != newState) {
294 m_readyState = static_cast<State>(newState);
295 if (readyState() == DONE) {
296 // The XHR object itself holds on to the responseText, and
297 // thus has extra cost even independent of any
298 // responseText or responseXML objects it has handed
299 // out. But it is protected from GC while loading, so this
300 // can't be recouped until the load is done, so only
301 // report the extra cost at that point.
302 if (auto* context = scriptExecutionContext()) {
303 JSC::VM& vm = context->vm();
304 JSC::JSLockHolder lock(vm);
305 vm.heap.reportExtraMemoryAllocated(memoryCost());
306 }
307 }
308 callReadyStateChangeListener();
309 }
310}
311
312void XMLHttpRequest::callReadyStateChangeListener()
313{
314 if (!scriptExecutionContext())
315 return;
316
317 // Check whether sending load and loadend events before sending readystatechange event, as it may change m_error/m_readyState values.
318 bool shouldSendLoadEvent = (readyState() == DONE && !m_error);
319
320 if (m_async || (readyState() <= OPENED || readyState() == DONE)) {
321 m_progressEventThrottle.dispatchReadyStateChangeEvent(Event::create(eventNames().readystatechangeEvent, Event::CanBubble::No, Event::IsCancelable::No),
322 readyState() == DONE ? FlushProgressEvent : DoNotFlushProgressEvent);
323 }
324
325 if (shouldSendLoadEvent) {
326 m_progressEventThrottle.dispatchProgressEvent(eventNames().loadEvent);
327 m_progressEventThrottle.dispatchProgressEvent(eventNames().loadendEvent);
328 }
329}
330
331ExceptionOr<void> XMLHttpRequest::setWithCredentials(bool value)
332{
333 if (readyState() > OPENED || m_sendFlag)
334 return Exception { InvalidStateError };
335
336 m_includeCredentials = value;
337 return { };
338}
339
340ExceptionOr<void> XMLHttpRequest::open(const String& method, const String& url)
341{
342 // If the async argument is omitted, set async to true.
343 return open(method, scriptExecutionContext()->completeURL(url), true);
344}
345
346ExceptionOr<void> XMLHttpRequest::open(const String& method, const URL& url, bool async)
347{
348 if (!isValidHTTPToken(method))
349 return Exception { SyntaxError };
350
351 if (isForbiddenMethod(method))
352 return Exception { SecurityError };
353
354 if (!url.isValid())
355 return Exception { SyntaxError };
356
357 if (!async && scriptExecutionContext()->isDocument()) {
358 // Newer functionality is not available to synchronous requests in window contexts, as a spec-mandated
359 // attempt to discourage synchronous XHR use. responseType is one such piece of functionality.
360 // We'll only disable this functionality for HTTP(S) requests since sync requests for local protocols
361 // such as file: and data: still make sense to allow.
362 if (url.protocolIsInHTTPFamily() && responseType() != ResponseType::EmptyString) {
363 logConsoleError(scriptExecutionContext(), "Synchronous HTTP(S) requests made from the window context cannot have XMLHttpRequest.responseType set.");
364 return Exception { InvalidAccessError };
365 }
366
367 // Similarly, timeouts are disabled for synchronous requests as well.
368 if (m_timeoutMilliseconds > 0) {
369 logConsoleError(scriptExecutionContext(), "Synchronous XMLHttpRequests must not have a timeout value set.");
370 return Exception { InvalidAccessError };
371 }
372 }
373
374 if (!internalAbort())
375 return { };
376
377 m_sendFlag = false;
378 m_uploadListenerFlag = false;
379 m_method = normalizeHTTPMethod(method);
380 m_error = false;
381 m_uploadComplete = false;
382 m_wasAbortedByClient = false;
383
384 // clear stuff from possible previous load
385 clearResponse();
386 clearRequest();
387
388 m_url = url;
389 scriptExecutionContext()->contentSecurityPolicy()->upgradeInsecureRequestIfNeeded(m_url, ContentSecurityPolicy::InsecureRequestType::Load);
390
391 m_async = async;
392
393 ASSERT(!m_loader);
394
395 changeState(OPENED);
396
397 return { };
398}
399
400ExceptionOr<void> XMLHttpRequest::open(const String& method, const String& url, bool async, const String& user, const String& password)
401{
402 URL urlWithCredentials = scriptExecutionContext()->completeURL(url);
403 if (!user.isNull())
404 urlWithCredentials.setUser(user);
405 if (!password.isNull())
406 urlWithCredentials.setPass(password);
407
408 return open(method, urlWithCredentials, async);
409}
410
411Optional<ExceptionOr<void>> XMLHttpRequest::prepareToSend()
412{
413 // A return value other than WTF::nullopt means we should not try to send, and we should return that value to the caller.
414 // WTF::nullopt means we are ready to send and should continue with the send algorithm.
415
416 if (!scriptExecutionContext())
417 return ExceptionOr<void> { };
418
419 auto& context = *scriptExecutionContext();
420
421 if (readyState() != OPENED || m_sendFlag)
422 return ExceptionOr<void> { Exception { InvalidStateError } };
423 ASSERT(!m_loader);
424
425 // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved.
426 if (!context.shouldBypassMainWorldContentSecurityPolicy() && !context.contentSecurityPolicy()->allowConnectToSource(m_url)) {
427 if (!m_async)
428 return ExceptionOr<void> { Exception { NetworkError } };
429 setPendingActivity(*this);
430 m_timeoutTimer.stop();
431 m_networkErrorTimer.startOneShot(0_s);
432 return ExceptionOr<void> { };
433 }
434
435 m_error = false;
436 return WTF::nullopt;
437}
438
439ExceptionOr<void> XMLHttpRequest::send(Optional<SendTypes>&& sendType)
440{
441 InspectorInstrumentation::willSendXMLHttpRequest(scriptExecutionContext(), url());
442 m_userGestureToken = UserGestureIndicator::currentUserGesture();
443
444 ExceptionOr<void> result;
445 if (!sendType)
446 result = send();
447 else {
448 result = WTF::switchOn(sendType.value(),
449 [this] (const RefPtr<Document>& document) -> ExceptionOr<void> { return send(*document); },
450 [this] (const RefPtr<Blob>& blob) -> ExceptionOr<void> { return send(*blob); },
451 [this] (const RefPtr<JSC::ArrayBufferView>& arrayBufferView) -> ExceptionOr<void> { return send(*arrayBufferView); },
452 [this] (const RefPtr<JSC::ArrayBuffer>& arrayBuffer) -> ExceptionOr<void> { return send(*arrayBuffer); },
453 [this] (const RefPtr<DOMFormData>& formData) -> ExceptionOr<void> { return send(*formData); },
454 [this] (const String& string) -> ExceptionOr<void> { return send(string); }
455 );
456 }
457
458 return result;
459}
460
461ExceptionOr<void> XMLHttpRequest::send(Document& document)
462{
463 if (auto result = prepareToSend())
464 return WTFMove(result.value());
465
466 if (m_method != "GET" && m_method != "HEAD") {
467 if (!m_requestHeaders.contains(HTTPHeaderName::ContentType)) {
468#if ENABLE(DASHBOARD_SUPPORT)
469 if (usesDashboardBackwardCompatibilityMode())
470 m_requestHeaders.set(HTTPHeaderName::ContentType, "application/x-www-form-urlencoded"_s);
471 else
472#endif
473 // FIXME: this should include the charset used for encoding.
474 m_requestHeaders.set(HTTPHeaderName::ContentType, document.isHTMLDocument() ? "text/html;charset=UTF-8"_s : "application/xml;charset=UTF-8"_s);
475 } else {
476 String contentType = m_requestHeaders.get(HTTPHeaderName::ContentType);
477 replaceCharsetInMediaTypeIfNeeded(contentType);
478 m_requestHeaders.set(HTTPHeaderName::ContentType, contentType);
479 }
480
481 // FIXME: According to XMLHttpRequest Level 2, this should use the Document.innerHTML algorithm
482 // from the HTML5 specification to serialize the document.
483 m_requestEntityBody = FormData::create(UTF8Encoding().encode(serializeFragment(document, SerializedNodes::SubtreeIncludingNode), UnencodableHandling::Entities));
484 if (m_upload)
485 m_requestEntityBody->setAlwaysStream(true);
486 }
487
488 return createRequest();
489}
490
491ExceptionOr<void> XMLHttpRequest::send(const String& body)
492{
493 if (auto result = prepareToSend())
494 return WTFMove(result.value());
495
496 if (!body.isNull() && m_method != "GET" && m_method != "HEAD") {
497 String contentType = m_requestHeaders.get(HTTPHeaderName::ContentType);
498 if (contentType.isNull()) {
499#if ENABLE(DASHBOARD_SUPPORT)
500 if (usesDashboardBackwardCompatibilityMode())
501 m_requestHeaders.set(HTTPHeaderName::ContentType, "application/x-www-form-urlencoded"_s);
502 else
503#endif
504 m_requestHeaders.set(HTTPHeaderName::ContentType, HTTPHeaderValues::textPlainContentType());
505 } else {
506 replaceCharsetInMediaTypeIfNeeded(contentType);
507 m_requestHeaders.set(HTTPHeaderName::ContentType, contentType);
508 }
509
510 m_requestEntityBody = FormData::create(UTF8Encoding().encode(body, UnencodableHandling::Entities));
511 if (m_upload)
512 m_requestEntityBody->setAlwaysStream(true);
513 }
514
515 return createRequest();
516}
517
518ExceptionOr<void> XMLHttpRequest::send(Blob& body)
519{
520 if (auto result = prepareToSend())
521 return WTFMove(result.value());
522
523 if (m_method != "GET" && m_method != "HEAD") {
524 if (!m_url.protocolIsInHTTPFamily()) {
525 // FIXME: We would like to support posting Blobs to non-http URLs (e.g. custom URL schemes)
526 // but because of the architecture of blob-handling that will require a fair amount of work.
527
528 ASCIILiteral consoleMessage { "POST of a Blob to non-HTTP protocols in XMLHttpRequest.send() is currently unsupported."_s };
529 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Warning, consoleMessage);
530
531 return createRequest();
532 }
533
534 if (!m_requestHeaders.contains(HTTPHeaderName::ContentType)) {
535 const String& blobType = body.type();
536 if (!blobType.isEmpty() && isValidContentType(blobType))
537 m_requestHeaders.set(HTTPHeaderName::ContentType, blobType);
538 else {
539 // From FileAPI spec, whenever media type cannot be determined, empty string must be returned.
540 m_requestHeaders.set(HTTPHeaderName::ContentType, emptyString());
541 }
542 }
543
544 m_requestEntityBody = FormData::create();
545 m_requestEntityBody->appendBlob(body.url());
546 }
547
548 return createRequest();
549}
550
551ExceptionOr<void> XMLHttpRequest::send(DOMFormData& body)
552{
553 if (auto result = prepareToSend())
554 return WTFMove(result.value());
555
556 if (m_method != "GET" && m_method != "HEAD") {
557 m_requestEntityBody = FormData::createMultiPart(body, document());
558 m_requestEntityBody->generateFiles(document());
559 if (!m_requestHeaders.contains(HTTPHeaderName::ContentType))
560 m_requestHeaders.set(HTTPHeaderName::ContentType, makeString("multipart/form-data; boundary=", m_requestEntityBody->boundary().data()));
561 }
562
563 return createRequest();
564}
565
566ExceptionOr<void> XMLHttpRequest::send(ArrayBuffer& body)
567{
568 ASCIILiteral consoleMessage { "ArrayBuffer is deprecated in XMLHttpRequest.send(). Use ArrayBufferView instead."_s };
569 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Warning, consoleMessage);
570 return sendBytesData(body.data(), body.byteLength());
571}
572
573ExceptionOr<void> XMLHttpRequest::send(ArrayBufferView& body)
574{
575 return sendBytesData(body.baseAddress(), body.byteLength());
576}
577
578ExceptionOr<void> XMLHttpRequest::sendBytesData(const void* data, size_t length)
579{
580 if (auto result = prepareToSend())
581 return WTFMove(result.value());
582
583 if (m_method != "GET" && m_method != "HEAD") {
584 m_requestEntityBody = FormData::create(data, length);
585 if (m_upload)
586 m_requestEntityBody->setAlwaysStream(true);
587 }
588
589 return createRequest();
590}
591
592ExceptionOr<void> XMLHttpRequest::createRequest()
593{
594 // Only GET request is supported for blob URL.
595 if (!m_async && m_url.protocolIsBlob() && m_method != "GET")
596 return Exception { NetworkError };
597
598 if (m_async && m_upload && m_upload->hasEventListeners())
599 m_uploadListenerFlag = true;
600
601 ResourceRequest request(m_url);
602 request.setRequester(ResourceRequest::Requester::XHR);
603 request.setInitiatorIdentifier(scriptExecutionContext()->resourceRequestIdentifier());
604 request.setHTTPMethod(m_method);
605
606 if (m_requestEntityBody) {
607 ASSERT(m_method != "GET");
608 ASSERT(m_method != "HEAD");
609 request.setHTTPBody(WTFMove(m_requestEntityBody));
610 }
611
612 if (!m_requestHeaders.isEmpty())
613 request.setHTTPHeaderFields(m_requestHeaders);
614
615 ThreadableLoaderOptions options;
616 options.sendLoadCallbacks = SendCallbackPolicy::SendCallbacks;
617 // The presence of upload event listeners forces us to use preflighting because POSTing to an URL that does not
618 // permit cross origin requests should look exactly like POSTing to an URL that does not respond at all.
619 options.preflightPolicy = m_uploadListenerFlag ? PreflightPolicy::Force : PreflightPolicy::Consider;
620 options.credentials = m_includeCredentials ? FetchOptions::Credentials::Include : FetchOptions::Credentials::SameOrigin;
621 options.mode = FetchOptions::Mode::Cors;
622 options.contentSecurityPolicyEnforcement = scriptExecutionContext()->shouldBypassMainWorldContentSecurityPolicy() ? ContentSecurityPolicyEnforcement::DoNotEnforce : ContentSecurityPolicyEnforcement::EnforceConnectSrcDirective;
623 options.initiator = cachedResourceRequestInitiators().xmlhttprequest;
624 options.sameOriginDataURLFlag = SameOriginDataURLFlag::Set;
625 options.filteringPolicy = ResponseFilteringPolicy::Enable;
626 options.sniffContentEncoding = ContentEncodingSniffingPolicy::DoNotSniff;
627
628 if (m_timeoutMilliseconds) {
629 if (!m_async)
630 request.setTimeoutInterval(m_timeoutMilliseconds / 1000.0);
631 else {
632 request.setTimeoutInterval(std::numeric_limits<double>::infinity());
633 m_sendingTime = MonotonicTime::now();
634 m_timeoutTimer.startOneShot(1_ms * m_timeoutMilliseconds);
635 }
636 }
637
638 m_exceptionCode = WTF::nullopt;
639 m_error = false;
640 m_uploadComplete = !request.httpBody();
641 m_sendFlag = true;
642
643 if (m_async) {
644 m_progressEventThrottle.dispatchProgressEvent(eventNames().loadstartEvent);
645 if (!m_uploadComplete && m_uploadListenerFlag)
646 m_upload->dispatchProgressEvent(eventNames().loadstartEvent, 0, request.httpBody()->lengthInBytes());
647
648 if (readyState() != OPENED || !m_sendFlag || m_loader)
649 return { };
650
651 // ThreadableLoader::create can return null here, for example if we're no longer attached to a page or if a content blocker blocks the load.
652 // This is true while running onunload handlers.
653 // FIXME: Maybe we need to be able to send XMLHttpRequests from onunload, <http://bugs.webkit.org/show_bug.cgi?id=10904>.
654 m_loader = ThreadableLoader::create(*scriptExecutionContext(), *this, WTFMove(request), options);
655
656 // Either loader is null or some error was synchronously sent to us.
657 ASSERT(m_loader || !m_sendFlag);
658
659 // Neither this object nor the JavaScript wrapper should be deleted while
660 // a request is in progress because we need to keep the listeners alive,
661 // and they are referenced by the JavaScript wrapper.
662 if (m_loader)
663 setPendingActivity(*this);
664 } else {
665 request.setDomainForCachePartition(scriptExecutionContext()->domainForCachePartition());
666 InspectorInstrumentation::willLoadXHRSynchronously(scriptExecutionContext());
667 ThreadableLoader::loadResourceSynchronously(*scriptExecutionContext(), WTFMove(request), *this, options);
668 InspectorInstrumentation::didLoadXHRSynchronously(scriptExecutionContext());
669 }
670
671 if (m_exceptionCode)
672 return Exception { m_exceptionCode.value() };
673 if (m_error)
674 return Exception { NetworkError };
675 return { };
676}
677
678void XMLHttpRequest::abort()
679{
680 // internalAbort() calls unsetPendingActivity(this), which may release the last reference.
681 Ref<XMLHttpRequest> protectedThis(*this);
682
683 m_wasAbortedByClient = true;
684 if (!internalAbort())
685 return;
686
687 clearResponseBuffers();
688
689 m_requestHeaders.clear();
690 if ((readyState() == OPENED && m_sendFlag) || readyState() == HEADERS_RECEIVED || readyState() == LOADING) {
691 ASSERT(!m_loader);
692 m_sendFlag = false;
693 changeState(DONE);
694 dispatchErrorEvents(eventNames().abortEvent);
695 }
696 if (readyState() == DONE)
697 m_readyState = static_cast<State>(UNSENT);
698}
699
700bool XMLHttpRequest::internalAbort()
701{
702 m_error = true;
703
704 // FIXME: when we add the support for multi-part XHR, we will have to think be careful with this initialization.
705 m_receivedLength = 0;
706
707 m_decoder = nullptr;
708
709 m_timeoutTimer.stop();
710
711 if (!m_loader)
712 return true;
713
714 // Cancelling m_loader may trigger a window.onload callback which can call open() on the same xhr.
715 // This would create internalAbort reentrant call.
716 // m_loader is set to null before being cancelled to exit early in any reentrant internalAbort() call.
717 auto loader = WTFMove(m_loader);
718 loader->cancel();
719
720 // If window.onload callback calls open() and send() on the same xhr, m_loader is now set to a new value.
721 // The function calling internalAbort() should abort to let the open() and send() calls continue properly.
722 // We ask the function calling internalAbort() to exit by returning false.
723 // Save this information to a local variable since we are going to drop protection.
724 bool newLoadStarted = m_loader;
725
726 unsetPendingActivity(*this);
727
728 return !newLoadStarted;
729}
730
731void XMLHttpRequest::clearResponse()
732{
733 m_response = ResourceResponse();
734 clearResponseBuffers();
735}
736
737void XMLHttpRequest::clearResponseBuffers()
738{
739 m_responseBuilder.clear();
740 m_responseEncoding = String();
741 m_createdDocument = false;
742 m_responseDocument = nullptr;
743 m_binaryResponseBuilder = nullptr;
744 m_responseCacheIsValid = false;
745}
746
747void XMLHttpRequest::clearRequest()
748{
749 m_requestHeaders.clear();
750 m_requestEntityBody = nullptr;
751}
752
753void XMLHttpRequest::genericError()
754{
755 clearResponse();
756 clearRequest();
757 m_sendFlag = false;
758 m_error = true;
759
760 changeState(DONE);
761}
762
763void XMLHttpRequest::networkError()
764{
765 genericError();
766 dispatchErrorEvents(eventNames().errorEvent);
767 internalAbort();
768}
769
770void XMLHttpRequest::networkErrorTimerFired()
771{
772 networkError();
773 unsetPendingActivity(*this);
774}
775
776void XMLHttpRequest::abortError()
777{
778 ASSERT(m_wasAbortedByClient);
779 genericError();
780 dispatchErrorEvents(eventNames().abortEvent);
781}
782
783size_t XMLHttpRequest::memoryCost() const
784{
785 if (readyState() == DONE)
786 return m_responseBuilder.length() * 2;
787 return 0;
788}
789
790ExceptionOr<void> XMLHttpRequest::overrideMimeType(const String& mimeType)
791{
792 if (readyState() == LOADING || readyState() == DONE)
793 return Exception { InvalidStateError };
794
795 m_mimeTypeOverride = "application/octet-stream"_s;
796 if (isValidContentType(mimeType))
797 m_mimeTypeOverride = mimeType;
798
799 return { };
800}
801
802ExceptionOr<void> XMLHttpRequest::setRequestHeader(const String& name, const String& value)
803{
804 if (readyState() != OPENED || m_sendFlag) {
805#if ENABLE(DASHBOARD_SUPPORT)
806 if (usesDashboardBackwardCompatibilityMode())
807 return { };
808#endif
809 return Exception { InvalidStateError };
810 }
811
812 String normalizedValue = stripLeadingAndTrailingHTTPSpaces(value);
813 if (!isValidHTTPToken(name) || !isValidHTTPHeaderValue(normalizedValue))
814 return Exception { SyntaxError };
815
816 bool allowUnsafeHeaderField = false;
817#if ENABLE(DASHBOARD_SUPPORT)
818 allowUnsafeHeaderField = usesDashboardBackwardCompatibilityMode();
819#endif
820 if (securityOrigin()->canLoadLocalResources() && document()->settings().allowSettingAnyXHRHeaderFromFileURLs())
821 allowUnsafeHeaderField = true;
822 if (!allowUnsafeHeaderField && isForbiddenHeaderName(name)) {
823 logConsoleError(scriptExecutionContext(), "Refused to set unsafe header \"" + name + "\"");
824 return { };
825 }
826
827 m_requestHeaders.add(name, normalizedValue);
828 return { };
829}
830
831String XMLHttpRequest::getAllResponseHeaders() const
832{
833 if (readyState() < HEADERS_RECEIVED || m_error)
834 return emptyString();
835
836 if (!m_allResponseHeaders) {
837 Vector<String> headers;
838 headers.reserveInitialCapacity(m_response.httpHeaderFields().size());
839
840 for (auto& header : m_response.httpHeaderFields()) {
841 StringBuilder stringBuilder;
842 stringBuilder.append(header.key.convertToASCIILowercase());
843 stringBuilder.appendLiteral(": ");
844 stringBuilder.append(header.value);
845 stringBuilder.appendLiteral("\r\n");
846 headers.uncheckedAppend(stringBuilder.toString());
847 }
848 std::sort(headers.begin(), headers.end(), WTF::codePointCompareLessThan);
849
850 StringBuilder stringBuilder;
851 for (auto& header : headers)
852 stringBuilder.append(header);
853 m_allResponseHeaders = stringBuilder.toString();
854 }
855
856 return m_allResponseHeaders;
857}
858
859String XMLHttpRequest::getResponseHeader(const String& name) const
860{
861 if (readyState() < HEADERS_RECEIVED || m_error)
862 return String();
863
864 return m_response.httpHeaderField(name);
865}
866
867String XMLHttpRequest::responseMIMEType() const
868{
869 String mimeType = extractMIMETypeFromMediaType(m_mimeTypeOverride);
870 if (mimeType.isEmpty()) {
871 if (m_response.isHTTP())
872 mimeType = extractMIMETypeFromMediaType(m_response.httpHeaderField(HTTPHeaderName::ContentType));
873 else
874 mimeType = m_response.mimeType();
875 if (mimeType.isEmpty())
876 mimeType = "text/xml"_s;
877 }
878 return mimeType;
879}
880
881bool XMLHttpRequest::responseIsXML() const
882{
883 return MIMETypeRegistry::isXMLMIMEType(responseMIMEType());
884}
885
886int XMLHttpRequest::status() const
887{
888 if (readyState() == UNSENT || readyState() == OPENED || m_error)
889 return 0;
890
891 return m_response.httpStatusCode();
892}
893
894String XMLHttpRequest::statusText() const
895{
896 if (readyState() == UNSENT || readyState() == OPENED || m_error)
897 return String();
898
899 return m_response.httpStatusText();
900}
901
902void XMLHttpRequest::didFail(const ResourceError& error)
903{
904 // If we are already in an error state, for instance we called abort(), bail out early.
905 if (m_error)
906 return;
907
908 // The XHR specification says we should only fire an abort event if the cancelation was requested by the client.
909 if (m_wasAbortedByClient && error.isCancellation()) {
910 m_exceptionCode = AbortError;
911 abortError();
912 return;
913 }
914
915 // In case of worker sync timeouts.
916 if (error.isTimeout()) {
917 didReachTimeout();
918 return;
919 }
920
921 // In case didFail is called synchronously on an asynchronous XHR call, let's dispatch network error asynchronously
922 if (m_async && m_sendFlag && !m_loader) {
923 m_sendFlag = false;
924 setPendingActivity(*this);
925 m_timeoutTimer.stop();
926 m_networkErrorTimer.startOneShot(0_s);
927 return;
928 }
929 m_exceptionCode = NetworkError;
930 networkError();
931}
932
933void XMLHttpRequest::didFinishLoading(unsigned long)
934{
935 if (m_error)
936 return;
937
938 if (readyState() < HEADERS_RECEIVED)
939 changeState(HEADERS_RECEIVED);
940
941 if (m_decoder)
942 m_responseBuilder.append(m_decoder->flush());
943
944 m_responseBuilder.shrinkToFit();
945
946 bool hadLoader = m_loader;
947 m_loader = nullptr;
948
949 m_sendFlag = false;
950 changeState(DONE);
951 m_responseEncoding = String();
952 m_decoder = nullptr;
953
954 m_timeoutTimer.stop();
955
956 if (hadLoader)
957 unsetPendingActivity(*this);
958}
959
960void XMLHttpRequest::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
961{
962 if (!m_upload)
963 return;
964
965 if (m_uploadListenerFlag)
966 m_upload->dispatchProgressEvent(eventNames().progressEvent, bytesSent, totalBytesToBeSent);
967
968 if (bytesSent == totalBytesToBeSent && !m_uploadComplete) {
969 m_uploadComplete = true;
970 if (m_uploadListenerFlag) {
971 m_upload->dispatchProgressEvent(eventNames().loadEvent, bytesSent, totalBytesToBeSent);
972 m_upload->dispatchProgressEvent(eventNames().loadendEvent, bytesSent, totalBytesToBeSent);
973 }
974 }
975}
976
977void XMLHttpRequest::didReceiveResponse(unsigned long, const ResourceResponse& response)
978{
979 m_response = response;
980}
981
982static inline bool shouldDecodeResponse(XMLHttpRequest::ResponseType type)
983{
984 switch (type) {
985 case XMLHttpRequest::ResponseType::EmptyString:
986 case XMLHttpRequest::ResponseType::Document:
987 case XMLHttpRequest::ResponseType::Json:
988 case XMLHttpRequest::ResponseType::Text:
989 return true;
990 case XMLHttpRequest::ResponseType::Arraybuffer:
991 case XMLHttpRequest::ResponseType::Blob:
992 return false;
993 }
994 ASSERT_NOT_REACHED();
995 return true;
996}
997
998// https://xhr.spec.whatwg.org/#final-charset
999TextEncoding XMLHttpRequest::finalResponseCharset() const
1000{
1001 String label = m_responseEncoding;
1002
1003 String overrideResponseCharset = extractCharsetFromMediaType(m_mimeTypeOverride);
1004 if (!overrideResponseCharset.isEmpty())
1005 label = overrideResponseCharset;
1006
1007 return TextEncoding(label);
1008}
1009
1010Ref<TextResourceDecoder> XMLHttpRequest::createDecoder() const
1011{
1012 TextEncoding finalResponseCharset = this->finalResponseCharset();
1013 if (finalResponseCharset.isValid())
1014 return TextResourceDecoder::create("text/plain", finalResponseCharset);
1015
1016 switch (responseType()) {
1017 case ResponseType::EmptyString:
1018 if (responseIsXML()) {
1019 auto decoder = TextResourceDecoder::create("application/xml");
1020 // Don't stop on encoding errors, unlike it is done for other kinds of XML resources. This matches the behavior of previous WebKit versions, Firefox and Opera.
1021 decoder->useLenientXMLDecoding();
1022 return decoder;
1023 }
1024 FALLTHROUGH;
1025 case ResponseType::Text:
1026 case ResponseType::Json:
1027 return TextResourceDecoder::create("text/plain", "UTF-8");
1028 case ResponseType::Document: {
1029 if (equalLettersIgnoringASCIICase(responseMIMEType(), "text/html"))
1030 return TextResourceDecoder::create("text/html", "UTF-8");
1031 auto decoder = TextResourceDecoder::create("application/xml");
1032 // Don't stop on encoding errors, unlike it is done for other kinds of XML resources. This matches the behavior of previous WebKit versions, Firefox and Opera.
1033 decoder->useLenientXMLDecoding();
1034 return decoder;
1035 }
1036 case ResponseType::Arraybuffer:
1037 case ResponseType::Blob:
1038 ASSERT_NOT_REACHED();
1039 break;
1040 }
1041 return TextResourceDecoder::create("text/plain", "UTF-8");
1042}
1043
1044void XMLHttpRequest::didReceiveData(const char* data, int len)
1045{
1046 if (m_error)
1047 return;
1048
1049 if (readyState() < HEADERS_RECEIVED)
1050 changeState(HEADERS_RECEIVED);
1051
1052 if (!m_mimeTypeOverride.isEmpty())
1053 m_responseEncoding = extractCharsetFromMediaType(m_mimeTypeOverride);
1054 if (m_responseEncoding.isEmpty())
1055 m_responseEncoding = m_response.textEncodingName();
1056
1057 bool useDecoder = shouldDecodeResponse(responseType());
1058
1059 if (useDecoder && !m_decoder)
1060 m_decoder = createDecoder();
1061
1062 if (!len)
1063 return;
1064
1065 if (len == -1)
1066 len = strlen(data);
1067
1068 if (useDecoder)
1069 m_responseBuilder.append(m_decoder->decode(data, len));
1070 else {
1071 // Buffer binary data.
1072 if (!m_binaryResponseBuilder)
1073 m_binaryResponseBuilder = SharedBuffer::create();
1074 m_binaryResponseBuilder->append(data, len);
1075 }
1076
1077 if (!m_error) {
1078 m_receivedLength += len;
1079
1080 if (readyState() != LOADING)
1081 changeState(LOADING);
1082 else {
1083 // Firefox calls readyStateChanged every time it receives data, 4449442
1084 callReadyStateChangeListener();
1085 }
1086
1087 if (m_async) {
1088 long long expectedLength = m_response.expectedContentLength();
1089 bool lengthComputable = expectedLength > 0 && m_receivedLength <= expectedLength;
1090 unsigned long long total = lengthComputable ? expectedLength : 0;
1091 m_progressEventThrottle.dispatchThrottledProgressEvent(lengthComputable, m_receivedLength, total);
1092 }
1093 }
1094}
1095
1096void XMLHttpRequest::dispatchEvent(Event& event)
1097{
1098 if (m_userGestureToken && m_userGestureToken->hasExpired(m_maximumIntervalForUserGestureForwarding))
1099 m_userGestureToken = nullptr;
1100
1101 if (readyState() != DONE || !m_userGestureToken || !m_userGestureToken->processingUserGesture()) {
1102 EventTarget::dispatchEvent(event);
1103 return;
1104 }
1105
1106 UserGestureIndicator gestureIndicator(m_userGestureToken, UserGestureToken::GestureScope::MediaOnly);
1107 EventTarget::dispatchEvent(event);
1108}
1109
1110void XMLHttpRequest::dispatchErrorEvents(const AtomicString& type)
1111{
1112 if (!m_uploadComplete) {
1113 m_uploadComplete = true;
1114 if (m_upload && m_uploadListenerFlag) {
1115 m_upload->dispatchProgressEvent(type, 0, 0);
1116 m_upload->dispatchProgressEvent(eventNames().loadendEvent, 0, 0);
1117 }
1118 }
1119 m_progressEventThrottle.dispatchProgressEvent(type);
1120 m_progressEventThrottle.dispatchProgressEvent(eventNames().loadendEvent);
1121}
1122
1123void XMLHttpRequest::didReachTimeout()
1124{
1125 // internalAbort() calls unsetPendingActivity(this), which may release the last reference.
1126 Ref<XMLHttpRequest> protectedThis(*this);
1127 if (!internalAbort())
1128 return;
1129
1130 clearResponse();
1131 clearRequest();
1132
1133 m_sendFlag = false;
1134 m_error = true;
1135 m_exceptionCode = TimeoutError;
1136
1137 if (!m_async) {
1138 m_readyState = static_cast<State>(DONE);
1139 m_exceptionCode = TimeoutError;
1140 return;
1141 }
1142
1143 changeState(DONE);
1144
1145 dispatchErrorEvents(eventNames().timeoutEvent);
1146}
1147
1148bool XMLHttpRequest::canSuspendForDocumentSuspension() const
1149{
1150 // If the load event has not fired yet, cancelling the load in suspend() may cause
1151 // the load event to be fired and arbitrary JS execution, which would be unsafe.
1152 // Therefore, we prevent suspending in this case.
1153 return document()->loadEventFinished();
1154}
1155
1156const char* XMLHttpRequest::activeDOMObjectName() const
1157{
1158 return "XMLHttpRequest";
1159}
1160
1161void XMLHttpRequest::suspend(ReasonForSuspension reason)
1162{
1163 m_progressEventThrottle.suspend();
1164
1165 if (m_resumeTimer.isActive()) {
1166 m_resumeTimer.stop();
1167 m_dispatchErrorOnResuming = true;
1168 }
1169
1170 if (reason == ReasonForSuspension::PageCache && m_loader) {
1171 // Going into PageCache, abort the request and dispatch a network error on resuming.
1172 genericError();
1173 m_dispatchErrorOnResuming = true;
1174 bool aborted = internalAbort();
1175 // It should not be possible to restart the load when aborting in suspend() because
1176 // we are not allowed to execute in JS in suspend().
1177 ASSERT_UNUSED(aborted, aborted);
1178 }
1179}
1180
1181void XMLHttpRequest::resume()
1182{
1183 m_progressEventThrottle.resume();
1184
1185 // We are not allowed to execute arbitrary JS in resume() so dispatch
1186 // the error event in a timer.
1187 if (m_dispatchErrorOnResuming && !m_resumeTimer.isActive())
1188 m_resumeTimer.startOneShot(0_s);
1189}
1190
1191void XMLHttpRequest::resumeTimerFired()
1192{
1193 ASSERT(m_dispatchErrorOnResuming);
1194 m_dispatchErrorOnResuming = false;
1195 dispatchErrorEvents(eventNames().errorEvent);
1196}
1197
1198void XMLHttpRequest::stop()
1199{
1200 internalAbort();
1201}
1202
1203void XMLHttpRequest::contextDestroyed()
1204{
1205 ASSERT(!m_loader);
1206 ActiveDOMObject::contextDestroyed();
1207}
1208
1209void XMLHttpRequest::setMaximumIntervalForUserGestureForwarding(double interval)
1210{
1211 m_maximumIntervalForUserGestureForwarding = Seconds(interval);
1212}
1213
1214} // namespace WebCore
1215