2 * Copyright (C) 2004, 2006, 2008 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.
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 #include "XMLHttpRequest.h"
26 #include "ContentSecurityPolicy.h"
27 #include "CrossOriginAccessControl.h"
28 #include "DOMFormData.h"
29 #include "DOMImplementation.h"
32 #include "EventException.h"
33 #include "EventListener.h"
34 #include "EventNames.h"
35 #include "ExceptionCode.h"
37 #include "HTTPParsers.h"
38 #include "HTTPValidation.h"
39 #include "InspectorInstrumentation.h"
40 #include "MemoryCache.h"
41 #include "ResourceError.h"
42 #include "ResourceRequest.h"
43 #include "ScriptCallStack.h"
44 #include "ScriptProfile.h"
45 #include "SecurityOrigin.h"
47 #include "SharedBuffer.h"
48 #include "TextResourceDecoder.h"
49 #include "ThreadableLoader.h"
50 #include "XMLHttpRequestException.h"
51 #include "XMLHttpRequestProgressEvent.h"
52 #include "XMLHttpRequestUpload.h"
54 #include <wtf/ArrayBuffer.h>
55 #include <wtf/RefCountedLeakCounter.h>
56 #include <wtf/StdLibExtras.h>
57 #include <wtf/UnusedParam.h>
58 #include <wtf/text/CString.h>
61 #include "JSDOMBinding.h"
62 #include "JSDOMWindow.h"
63 #include <heap/Strong.h>
64 #include <runtime/JSLock.h>
69 DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, xmlHttpRequestCounter, ("XMLHttpRequest"));
71 struct XMLHttpRequestStaticData {
72 WTF_MAKE_NONCOPYABLE(XMLHttpRequestStaticData); WTF_MAKE_FAST_ALLOCATED;
74 XMLHttpRequestStaticData();
75 String m_proxyHeaderPrefix;
76 String m_secHeaderPrefix;
77 HashSet<String, CaseFoldingHash> m_forbiddenRequestHeaders;
80 XMLHttpRequestStaticData::XMLHttpRequestStaticData()
81 : m_proxyHeaderPrefix("proxy-")
82 , m_secHeaderPrefix("sec-")
84 m_forbiddenRequestHeaders.add("accept-charset");
85 m_forbiddenRequestHeaders.add("accept-encoding");
86 m_forbiddenRequestHeaders.add("access-control-request-headers");
87 m_forbiddenRequestHeaders.add("access-control-request-method");
88 m_forbiddenRequestHeaders.add("connection");
89 m_forbiddenRequestHeaders.add("content-length");
90 m_forbiddenRequestHeaders.add("content-transfer-encoding");
91 m_forbiddenRequestHeaders.add("cookie");
92 m_forbiddenRequestHeaders.add("cookie2");
93 m_forbiddenRequestHeaders.add("date");
94 m_forbiddenRequestHeaders.add("expect");
95 m_forbiddenRequestHeaders.add("host");
96 m_forbiddenRequestHeaders.add("keep-alive");
97 m_forbiddenRequestHeaders.add("origin");
98 m_forbiddenRequestHeaders.add("referer");
99 m_forbiddenRequestHeaders.add("te");
100 m_forbiddenRequestHeaders.add("trailer");
101 m_forbiddenRequestHeaders.add("transfer-encoding");
102 m_forbiddenRequestHeaders.add("upgrade");
103 m_forbiddenRequestHeaders.add("user-agent");
104 m_forbiddenRequestHeaders.add("via");
107 static bool isSetCookieHeader(const AtomicString& name)
109 return equalIgnoringCase(name, "set-cookie") || equalIgnoringCase(name, "set-cookie2");
112 static void replaceCharsetInMediaType(String& mediaType, const String& charsetValue)
114 unsigned int pos = 0, len = 0;
116 findCharsetInMediaType(mediaType, pos, len);
119 // When no charset found, do nothing.
123 // Found at least one existing charset, replace all occurrences with new charset.
125 mediaType.replace(pos, len, charsetValue);
126 unsigned int start = pos + charsetValue.length();
127 findCharsetInMediaType(mediaType, pos, len, start);
131 static const XMLHttpRequestStaticData* staticData = 0;
133 static const XMLHttpRequestStaticData* createXMLHttpRequestStaticData()
135 staticData = new XMLHttpRequestStaticData;
139 static const XMLHttpRequestStaticData* initializeXMLHttpRequestStaticData()
141 // Uses dummy to avoid warnings about an unused variable.
142 AtomicallyInitializedStatic(const XMLHttpRequestStaticData*, dummy = createXMLHttpRequestStaticData());
146 PassRefPtr<XMLHttpRequest> XMLHttpRequest::create(ScriptExecutionContext* context, PassRefPtr<SecurityOrigin> securityOrigin)
148 return adoptRef(new XMLHttpRequest(context, securityOrigin));
151 XMLHttpRequest::XMLHttpRequest(ScriptExecutionContext* context, PassRefPtr<SecurityOrigin> securityOrigin)
152 : ActiveDOMObject(context, this)
154 , m_includeCredentials(false)
156 , m_createdDocument(false)
158 , m_uploadEventsAllowed(true)
159 , m_uploadComplete(false)
160 , m_sameOriginRequest(true)
161 , m_receivedLength(0)
162 , m_lastSendLineNumber(0)
164 , m_progressEventThrottle(this)
165 , m_responseTypeCode(ResponseTypeDefault)
166 , m_securityOrigin(securityOrigin)
168 initializeXMLHttpRequestStaticData();
170 xmlHttpRequestCounter.increment();
174 XMLHttpRequest::~XMLHttpRequest()
177 xmlHttpRequestCounter.decrement();
181 Document* XMLHttpRequest::document() const
183 ASSERT(scriptExecutionContext()->isDocument());
184 return static_cast<Document*>(scriptExecutionContext());
187 SecurityOrigin* XMLHttpRequest::securityOrigin() const
189 return m_securityOrigin ? m_securityOrigin.get() : scriptExecutionContext()->securityOrigin();
192 #if ENABLE(DASHBOARD_SUPPORT)
193 bool XMLHttpRequest::usesDashboardBackwardCompatibilityMode() const
195 if (scriptExecutionContext()->isWorkerContext())
197 Settings* settings = document()->settings();
198 return settings && settings->usesDashboardBackwardCompatibilityMode();
202 XMLHttpRequest::State XMLHttpRequest::readyState() const
207 String XMLHttpRequest::responseText(ExceptionCode& ec)
209 if (responseTypeCode() != ResponseTypeDefault && responseTypeCode() != ResponseTypeText) {
210 ec = INVALID_STATE_ERR;
213 return m_responseBuilder.toStringPreserveCapacity();
216 Document* XMLHttpRequest::responseXML(ExceptionCode& ec)
218 if (responseTypeCode() != ResponseTypeDefault && responseTypeCode() != ResponseTypeText && responseTypeCode() != ResponseTypeDocument) {
219 ec = INVALID_STATE_ERR;
226 if (!m_createdDocument) {
227 if ((m_response.isHTTP() && !responseIsXML()) || scriptExecutionContext()->isWorkerContext()) {
228 // The W3C spec requires this.
231 m_responseXML = Document::create(0, m_url);
232 // FIXME: Set Last-Modified.
233 m_responseXML->setContent(m_responseBuilder.toStringPreserveCapacity());
234 m_responseXML->setSecurityOrigin(securityOrigin());
235 if (!m_responseXML->wellFormed())
238 m_createdDocument = true;
241 return m_responseXML.get();
244 #if ENABLE(XHR_RESPONSE_BLOB)
245 Blob* XMLHttpRequest::responseBlob(ExceptionCode& ec) const
247 if (responseTypeCode() != ResponseTypeBlob) {
248 ec = INVALID_STATE_ERR;
251 return m_responseBlob.get();
255 ArrayBuffer* XMLHttpRequest::responseArrayBuffer(ExceptionCode& ec)
257 if (m_responseTypeCode != ResponseTypeArrayBuffer) {
258 ec = INVALID_STATE_ERR;
265 if (!m_responseArrayBuffer.get() && m_binaryResponseBuilder.get() && m_binaryResponseBuilder->size() > 0) {
266 m_responseArrayBuffer = ArrayBuffer::create(const_cast<char*>(m_binaryResponseBuilder->data()), static_cast<unsigned>(m_binaryResponseBuilder->size()));
267 m_binaryResponseBuilder.clear();
270 if (m_responseArrayBuffer.get())
271 return m_responseArrayBuffer.get();
276 void XMLHttpRequest::setResponseType(const String& responseType, ExceptionCode& ec)
278 if (m_state != OPENED || m_loader) {
279 ec = INVALID_STATE_ERR;
283 if (responseType == "")
284 m_responseTypeCode = ResponseTypeDefault;
285 else if (responseType == "text")
286 m_responseTypeCode = ResponseTypeText;
287 else if (responseType == "document")
288 m_responseTypeCode = ResponseTypeDocument;
289 else if (responseType == "blob") {
290 #if ENABLE(XHR_RESPONSE_BLOB)
291 m_responseTypeCode = ResponseTypeBlob;
293 } else if (responseType == "arraybuffer") {
294 m_responseTypeCode = ResponseTypeArrayBuffer;
299 String XMLHttpRequest::responseType()
301 switch (m_responseTypeCode) {
302 case ResponseTypeDefault:
304 case ResponseTypeText:
306 case ResponseTypeDocument:
308 case ResponseTypeBlob:
310 case ResponseTypeArrayBuffer:
311 return "arraybuffer";
316 XMLHttpRequestUpload* XMLHttpRequest::upload()
319 m_upload = XMLHttpRequestUpload::create(this);
320 return m_upload.get();
323 void XMLHttpRequest::changeState(State newState)
325 if (m_state != newState) {
327 callReadyStateChangeListener();
331 void XMLHttpRequest::callReadyStateChangeListener()
333 if (!scriptExecutionContext())
336 InspectorInstrumentationCookie cookie = InspectorInstrumentation::willChangeXHRReadyState(scriptExecutionContext(), this);
338 if (m_async || (m_state <= OPENED || m_state == DONE))
339 m_progressEventThrottle.dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().readystatechangeEvent), m_state == DONE ? FlushProgressEvent : DoNotFlushProgressEvent);
341 InspectorInstrumentation::didChangeXHRReadyState(cookie);
343 if (m_state == DONE && !m_error) {
344 InspectorInstrumentationCookie cookie = InspectorInstrumentation::willLoadXHR(scriptExecutionContext(), this);
345 m_progressEventThrottle.dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().loadEvent));
346 InspectorInstrumentation::didLoadXHR(cookie);
350 void XMLHttpRequest::setWithCredentials(bool value, ExceptionCode& ec)
352 if (m_state != OPENED || m_loader) {
353 ec = INVALID_STATE_ERR;
357 m_includeCredentials = value;
360 #if ENABLE(XHR_RESPONSE_BLOB)
361 void XMLHttpRequest::setAsBlob(bool value, ExceptionCode& ec)
363 if (m_state != OPENED || m_loader) {
364 ec = INVALID_STATE_ERR;
368 m_responseTypeCode = value ? ResponseTypeBlob : ResponseTypeDefault;
372 bool XMLHttpRequest::isAllowedHTTPMethod(const String& method)
374 return !equalIgnoringCase(method, "TRACE")
375 && !equalIgnoringCase(method, "TRACK")
376 && !equalIgnoringCase(method, "CONNECT");
379 String XMLHttpRequest::uppercaseKnownHTTPMethod(const String& method)
381 if (equalIgnoringCase(method, "COPY") || equalIgnoringCase(method, "DELETE") || equalIgnoringCase(method, "GET")
382 || equalIgnoringCase(method, "HEAD") || equalIgnoringCase(method, "INDEX") || equalIgnoringCase(method, "LOCK")
383 || equalIgnoringCase(method, "M-POST") || equalIgnoringCase(method, "MKCOL") || equalIgnoringCase(method, "MOVE")
384 || equalIgnoringCase(method, "OPTIONS") || equalIgnoringCase(method, "POST") || equalIgnoringCase(method, "PROPFIND")
385 || equalIgnoringCase(method, "PROPPATCH") || equalIgnoringCase(method, "PUT") || equalIgnoringCase(method, "UNLOCK")) {
386 return method.upper();
391 bool XMLHttpRequest::isAllowedHTTPHeader(const String& name)
393 initializeXMLHttpRequestStaticData();
394 return !staticData->m_forbiddenRequestHeaders.contains(name) && !name.startsWith(staticData->m_proxyHeaderPrefix, false)
395 && !name.startsWith(staticData->m_secHeaderPrefix, false);
398 void XMLHttpRequest::open(const String& method, const KURL& url, ExceptionCode& ec)
400 open(method, url, true, ec);
403 void XMLHttpRequest::open(const String& method, const KURL& url, bool async, ExceptionCode& ec)
406 State previousState = m_state;
409 m_responseTypeCode = ResponseTypeDefault;
410 m_uploadComplete = false;
412 // clear stuff from possible previous load
416 ASSERT(m_state == UNSENT);
418 if (!isValidHTTPToken(method)) {
423 if (!isAllowedHTTPMethod(method)) {
428 if (!scriptExecutionContext()->contentSecurityPolicy()->allowConnectFromSource(url)) {
429 // FIXME: Should this be throwing an exception?
434 m_method = uppercaseKnownHTTPMethod(method);
442 // Check previous state to avoid dispatching readyState event
443 // when calling open several times in a row.
444 if (previousState != OPENED)
450 void XMLHttpRequest::open(const String& method, const KURL& url, bool async, const String& user, ExceptionCode& ec)
452 KURL urlWithCredentials(url);
453 urlWithCredentials.setUser(user);
455 open(method, urlWithCredentials, async, ec);
458 void XMLHttpRequest::open(const String& method, const KURL& url, bool async, const String& user, const String& password, ExceptionCode& ec)
460 KURL urlWithCredentials(url);
461 urlWithCredentials.setUser(user);
462 urlWithCredentials.setPass(password);
464 open(method, urlWithCredentials, async, ec);
467 bool XMLHttpRequest::initSend(ExceptionCode& ec)
469 if (!scriptExecutionContext())
472 if (m_state != OPENED || m_loader) {
473 ec = INVALID_STATE_ERR;
481 void XMLHttpRequest::send(ExceptionCode& ec)
486 void XMLHttpRequest::send(Document* document, ExceptionCode& ec)
493 if (m_method != "GET" && m_method != "HEAD" && m_url.protocolInHTTPFamily()) {
494 String contentType = getRequestHeader("Content-Type");
495 if (contentType.isEmpty()) {
496 #if ENABLE(DASHBOARD_SUPPORT)
497 if (usesDashboardBackwardCompatibilityMode())
498 setRequestHeaderInternal("Content-Type", "application/x-www-form-urlencoded");
501 // FIXME: this should include the charset used for encoding.
502 setRequestHeaderInternal("Content-Type", "application/xml");
505 // FIXME: According to XMLHttpRequest Level 2, this should use the Document.innerHTML algorithm
506 // from the HTML5 specification to serialize the document.
507 String body = createMarkup(document);
509 // FIXME: this should use value of document.inputEncoding to determine the encoding to use.
510 TextEncoding encoding = UTF8Encoding();
511 m_requestEntityBody = FormData::create(encoding.encode(body.characters(), body.length(), EntitiesForUnencodables));
513 m_requestEntityBody->setAlwaysStream(true);
519 void XMLHttpRequest::send(const String& body, ExceptionCode& ec)
524 if (!body.isNull() && m_method != "GET" && m_method != "HEAD" && m_url.protocolInHTTPFamily()) {
525 String contentType = getRequestHeader("Content-Type");
526 if (contentType.isEmpty()) {
527 #if ENABLE(DASHBOARD_SUPPORT)
528 if (usesDashboardBackwardCompatibilityMode())
529 setRequestHeaderInternal("Content-Type", "application/x-www-form-urlencoded");
532 setRequestHeaderInternal("Content-Type", "application/xml");
534 replaceCharsetInMediaType(contentType, "UTF-8");
535 m_requestHeaders.set("Content-Type", contentType);
538 m_requestEntityBody = FormData::create(UTF8Encoding().encode(body.characters(), body.length(), EntitiesForUnencodables));
540 m_requestEntityBody->setAlwaysStream(true);
546 void XMLHttpRequest::send(Blob* body, ExceptionCode& ec)
551 if (m_method != "GET" && m_method != "HEAD" && m_url.protocolInHTTPFamily()) {
552 // FIXME: Should we set a Content-Type if one is not set.
553 // FIXME: add support for uploading bundles.
554 m_requestEntityBody = FormData::create();
556 m_requestEntityBody->appendFile(static_cast<File*>(body)->path());
559 m_requestEntityBody->appendBlob(body->url());
566 void XMLHttpRequest::send(DOMFormData* body, ExceptionCode& ec)
571 if (m_method != "GET" && m_method != "HEAD" && m_url.protocolInHTTPFamily()) {
572 m_requestEntityBody = FormData::createMultiPart(*(static_cast<FormDataList*>(body)), body->encoding(), document());
574 // We need to ask the client to provide the generated file names if needed. When FormData fills the element
575 // for the file, it could set a flag to use the generated file name, i.e. a package file on Mac.
576 m_requestEntityBody->generateFiles(document());
578 String contentType = getRequestHeader("Content-Type");
579 if (contentType.isEmpty()) {
580 contentType = "multipart/form-data; boundary=";
581 contentType += m_requestEntityBody->boundary().data();
582 setRequestHeaderInternal("Content-Type", contentType);
589 void XMLHttpRequest::send(ArrayBuffer* body, ExceptionCode& ec)
594 if (m_method != "GET" && m_method != "HEAD" && m_url.protocolInHTTPFamily()) {
595 m_requestEntityBody = FormData::create(body->data(), body->byteLength());
597 m_requestEntityBody->setAlwaysStream(true);
603 void XMLHttpRequest::createRequest(ExceptionCode& ec)
606 // Only GET request is supported for blob URL.
607 if (m_url.protocolIs("blob") && m_method != "GET") {
608 ec = XMLHttpRequestException::NETWORK_ERR;
613 // The presence of upload event listeners forces us to use preflighting because POSTing to an URL that does not
614 // permit cross origin requests should look exactly like POSTing to an URL that does not respond at all.
615 // Also, only async requests support upload progress events.
616 bool uploadEvents = false;
618 m_progressEventThrottle.dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().loadstartEvent));
619 if (m_requestEntityBody && m_upload) {
620 uploadEvents = m_upload->hasEventListeners();
621 m_upload->dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().loadstartEvent));
625 m_sameOriginRequest = securityOrigin()->canRequest(m_url);
627 // We also remember whether upload events should be allowed for this request in case the upload listeners are
628 // added after the request is started.
629 m_uploadEventsAllowed = m_sameOriginRequest || uploadEvents || !isSimpleCrossOriginAccessRequest(m_method, m_requestHeaders);
631 ResourceRequest request(m_url);
632 request.setHTTPMethod(m_method);
633 #if PLATFORM(CHROMIUM)
634 request.setTargetType(ResourceRequest::TargetIsXHR);
637 #if ENABLE(TIZEN_XMLHTTPREQUEST_SECURITY)
638 request.setRequesterType(ResourceRequest::RequesterIsScript);
641 if (m_requestEntityBody) {
642 ASSERT(m_method != "GET");
643 ASSERT(m_method != "HEAD");
644 request.setHTTPBody(m_requestEntityBody.release());
647 if (m_requestHeaders.size() > 0)
648 request.addHTTPHeaderFields(m_requestHeaders);
650 ThreadableLoaderOptions options;
651 options.sendLoadCallbacks = SendCallbacks;
652 options.sniffContent = DoNotSniffContent;
653 options.preflightPolicy = uploadEvents ? ForcePreflight : ConsiderPreflight;
654 options.allowCredentials = (m_sameOriginRequest || m_includeCredentials) ? AllowStoredCredentials : DoNotAllowStoredCredentials;
655 options.crossOriginRequestPolicy = UseAccessControl;
656 options.securityOrigin = securityOrigin();
663 request.setReportUploadProgress(true);
665 // ThreadableLoader::create can return null here, for example if we're no longer attached to a page.
666 // This is true while running onunload handlers.
667 // FIXME: Maybe we need to be able to send XMLHttpRequests from onunload, <http://bugs.webkit.org/show_bug.cgi?id=10904>.
668 // FIXME: Maybe create() can return null for other reasons too?
669 m_loader = ThreadableLoader::create(scriptExecutionContext(), this, request, options);
671 // Neither this object nor the JavaScript wrapper should be deleted while
672 // a request is in progress because we need to keep the listeners alive,
673 // and they are referenced by the JavaScript wrapper.
674 setPendingActivity(this);
677 InspectorInstrumentation::willLoadXHRSynchronously(scriptExecutionContext());
678 ThreadableLoader::loadResourceSynchronously(scriptExecutionContext(), request, *this, options);
679 InspectorInstrumentation::didLoadXHRSynchronously(scriptExecutionContext());
682 if (!m_exceptionCode && m_error)
683 m_exceptionCode = XMLHttpRequestException::NETWORK_ERR;
684 ec = m_exceptionCode;
685 #if ENABLE(TIZEN_XMLHTTPREQUEST_SECURITY)
686 if (request.securityError())
687 ec = WebCore::SECURITY_ERR;
691 void XMLHttpRequest::abort()
693 // internalAbort() calls dropProtection(), which may release the last reference.
694 RefPtr<XMLHttpRequest> protect(this);
696 bool sendFlag = m_loader;
700 clearResponseBuffers();
702 // Clear headers as required by the spec
703 m_requestHeaders.clear();
705 if ((m_state <= OPENED && !sendFlag) || m_state == DONE)
713 m_progressEventThrottle.dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().abortEvent));
714 if (!m_uploadComplete) {
715 m_uploadComplete = true;
716 if (m_upload && m_uploadEventsAllowed)
717 m_upload->dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().abortEvent));
721 void XMLHttpRequest::internalAbort()
723 bool hadLoader = m_loader;
727 // FIXME: when we add the support for multi-part XHR, we will have to think be careful with this initialization.
728 m_receivedLength = 0;
741 void XMLHttpRequest::clearResponse()
743 m_response = ResourceResponse();
744 clearResponseBuffers();
747 void XMLHttpRequest::clearResponseBuffers()
749 m_responseBuilder.clear();
750 m_createdDocument = false;
752 #if ENABLE(XHR_RESPONSE_BLOB)
755 m_binaryResponseBuilder.clear();
756 m_responseArrayBuffer.clear();
759 void XMLHttpRequest::clearRequest()
761 m_requestHeaders.clear();
762 m_requestEntityBody = 0;
765 void XMLHttpRequest::genericError()
774 void XMLHttpRequest::networkError()
777 m_progressEventThrottle.dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().errorEvent));
778 if (!m_uploadComplete) {
779 m_uploadComplete = true;
780 if (m_upload && m_uploadEventsAllowed)
781 m_upload->dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().errorEvent));
786 void XMLHttpRequest::abortError()
789 m_progressEventThrottle.dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().abortEvent));
790 if (!m_uploadComplete) {
791 m_uploadComplete = true;
792 if (m_upload && m_uploadEventsAllowed)
793 m_upload->dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().abortEvent));
797 void XMLHttpRequest::dropProtection()
800 // The XHR object itself holds on to the responseText, and
801 // thus has extra cost even independent of any
802 // responseText or responseXML objects it has handed
803 // out. But it is protected from GC while loading, so this
804 // can't be recouped until the load is done, so only
805 // report the extra cost at that point.
806 JSC::JSLock lock(JSC::SilenceAssertionsOnly);
807 JSC::JSGlobalData* globalData = scriptExecutionContext()->globalData();
808 globalData->heap.reportExtraMemoryCost(m_responseBuilder.length() * 2);
811 unsetPendingActivity(this);
814 void XMLHttpRequest::overrideMimeType(const String& override)
816 m_mimeTypeOverride = override;
819 static void reportUnsafeUsage(ScriptExecutionContext* context, const String& message)
823 // FIXME: It's not good to report the bad usage without indicating what source line it came from.
824 // We should pass additional parameters so we can tell the console where the mistake occurred.
825 context->addConsoleMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, message);
828 void XMLHttpRequest::setRequestHeader(const AtomicString& name, const String& value, ExceptionCode& ec)
830 if (m_state != OPENED || m_loader) {
831 #if ENABLE(DASHBOARD_SUPPORT)
832 if (usesDashboardBackwardCompatibilityMode())
836 ec = INVALID_STATE_ERR;
840 if (!isValidHTTPToken(name) || !isValidHTTPHeaderValue(value)) {
845 // A privileged script (e.g. a Dashboard widget) can set any headers.
846 if (!securityOrigin()->canLoadLocalResources() && !isAllowedHTTPHeader(name)) {
847 reportUnsafeUsage(scriptExecutionContext(), "Refused to set unsafe header \"" + name + "\"");
851 setRequestHeaderInternal(name, value);
854 void XMLHttpRequest::setRequestHeaderInternal(const AtomicString& name, const String& value)
856 pair<HTTPHeaderMap::iterator, bool> result = m_requestHeaders.add(name, value);
858 result.first->second += ", " + value;
861 String XMLHttpRequest::getRequestHeader(const AtomicString& name) const
863 return m_requestHeaders.get(name);
866 String XMLHttpRequest::getAllResponseHeaders(ExceptionCode& ec) const
868 if (m_state < HEADERS_RECEIVED) {
869 ec = INVALID_STATE_ERR;
873 StringBuilder stringBuilder;
875 HTTPHeaderMap::const_iterator end = m_response.httpHeaderFields().end();
876 for (HTTPHeaderMap::const_iterator it = m_response.httpHeaderFields().begin(); it!= end; ++it) {
877 // Hide Set-Cookie header fields from the XMLHttpRequest client for these reasons:
878 // 1) If the client did have access to the fields, then it could read HTTP-only
879 // cookies; those cookies are supposed to be hidden from scripts.
880 // 2) There's no known harm in hiding Set-Cookie header fields entirely; we don't
881 // know any widely used technique that requires access to them.
882 // 3) Firefox has implemented this policy.
883 if (isSetCookieHeader(it->first) && !securityOrigin()->canLoadLocalResources())
886 if (!m_sameOriginRequest && !isOnAccessControlResponseHeaderWhitelist(it->first))
889 stringBuilder.append(it->first);
890 stringBuilder.append(':');
891 stringBuilder.append(' ');
892 stringBuilder.append(it->second);
893 stringBuilder.append('\r');
894 stringBuilder.append('\n');
897 return stringBuilder.toString();
900 String XMLHttpRequest::getResponseHeader(const AtomicString& name, ExceptionCode& ec) const
902 if (m_state < HEADERS_RECEIVED) {
903 ec = INVALID_STATE_ERR;
907 // See comment in getAllResponseHeaders above.
908 if (isSetCookieHeader(name) && !securityOrigin()->canLoadLocalResources()) {
909 reportUnsafeUsage(scriptExecutionContext(), "Refused to get unsafe header \"" + name + "\"");
913 if (!m_sameOriginRequest && !isOnAccessControlResponseHeaderWhitelist(name)) {
914 reportUnsafeUsage(scriptExecutionContext(), "Refused to get unsafe header \"" + name + "\"");
917 return m_response.httpHeaderField(name);
920 String XMLHttpRequest::responseMIMEType() const
922 String mimeType = extractMIMETypeFromMediaType(m_mimeTypeOverride);
923 if (mimeType.isEmpty()) {
924 if (m_response.isHTTP())
925 mimeType = extractMIMETypeFromMediaType(m_response.httpHeaderField("Content-Type"));
927 mimeType = m_response.mimeType();
929 if (mimeType.isEmpty())
930 mimeType = "text/xml";
935 bool XMLHttpRequest::responseIsXML() const
937 return DOMImplementation::isXMLMIMEType(responseMIMEType());
940 int XMLHttpRequest::status(ExceptionCode& ec) const
942 if (m_response.httpStatusCode())
943 return m_response.httpStatusCode();
945 if (m_state == OPENED) {
946 // Firefox only raises an exception in this state; we match it.
947 // Note the case of local file requests, where we have no HTTP response code! Firefox never raises an exception for those, but we match HTTP case for consistency.
948 ec = INVALID_STATE_ERR;
954 String XMLHttpRequest::statusText(ExceptionCode& ec) const
956 if (!m_response.httpStatusText().isNull())
957 return m_response.httpStatusText();
959 if (m_state == OPENED) {
960 // See comments in status() above.
961 ec = INVALID_STATE_ERR;
967 void XMLHttpRequest::didFail(const ResourceError& error)
970 // If we are already in an error state, for instance we called abort(), bail out early.
974 if (error.isCancellation()) {
975 m_exceptionCode = XMLHttpRequestException::ABORT_ERR;
980 // Network failures are already reported to Web Inspector by ResourceLoader.
981 if (error.domain() == errorDomainWebKitInternal)
982 reportUnsafeUsage(scriptExecutionContext(), "XMLHttpRequest cannot load " + error.failingURL() + ". " + error.localizedDescription());
984 m_exceptionCode = XMLHttpRequestException::NETWORK_ERR;
988 void XMLHttpRequest::didFailRedirectCheck()
993 void XMLHttpRequest::didFinishLoading(unsigned long identifier, double)
998 if (m_state < HEADERS_RECEIVED)
999 changeState(HEADERS_RECEIVED);
1002 m_responseBuilder.append(m_decoder->flush());
1004 m_responseBuilder.shrinkToFit();
1006 #if ENABLE(XHR_RESPONSE_BLOB)
1007 // FIXME: Set m_responseBlob to something here in the ResponseTypeBlob case.
1010 InspectorInstrumentation::resourceRetrievedByXMLHttpRequest(scriptExecutionContext(), identifier, m_responseBuilder.toStringPreserveCapacity(), m_url, m_lastSendURL, m_lastSendLineNumber);
1012 bool hadLoader = m_loader;
1022 void XMLHttpRequest::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
1027 if (m_uploadEventsAllowed)
1028 m_upload->dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().progressEvent, true, bytesSent, totalBytesToBeSent));
1030 if (bytesSent == totalBytesToBeSent && !m_uploadComplete) {
1031 m_uploadComplete = true;
1032 if (m_uploadEventsAllowed)
1033 m_upload->dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().loadEvent));
1037 void XMLHttpRequest::didReceiveResponse(unsigned long identifier, const ResourceResponse& response)
1039 InspectorInstrumentation::didReceiveXHRResponse(scriptExecutionContext(), identifier);
1041 m_response = response;
1042 m_responseEncoding = extractCharsetFromMediaType(m_mimeTypeOverride);
1043 if (m_responseEncoding.isEmpty())
1044 m_responseEncoding = response.textEncodingName();
1047 void XMLHttpRequest::didReceiveData(const char* data, int len)
1052 if (m_state < HEADERS_RECEIVED)
1053 changeState(HEADERS_RECEIVED);
1055 bool useDecoder = responseTypeCode() == ResponseTypeDefault || responseTypeCode() == ResponseTypeText || responseTypeCode() == ResponseTypeDocument;
1057 if (useDecoder && !m_decoder) {
1058 if (!m_responseEncoding.isEmpty())
1059 m_decoder = TextResourceDecoder::create("text/plain", m_responseEncoding);
1060 // allow TextResourceDecoder to look inside the m_response if it's XML or HTML
1061 else if (responseIsXML()) {
1062 m_decoder = TextResourceDecoder::create("application/xml");
1063 // 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.
1064 m_decoder->useLenientXMLDecoding();
1065 } else if (responseMIMEType() == "text/html")
1066 m_decoder = TextResourceDecoder::create("text/html", "UTF-8");
1068 m_decoder = TextResourceDecoder::create("text/plain", "UTF-8");
1078 m_responseBuilder.append(m_decoder->decode(data, len));
1079 else if (responseTypeCode() == ResponseTypeArrayBuffer) {
1080 // Buffer binary data.
1081 if (!m_binaryResponseBuilder)
1082 m_binaryResponseBuilder = SharedBuffer::create();
1083 m_binaryResponseBuilder->append(data, len);
1087 long long expectedLength = m_response.expectedContentLength();
1088 m_receivedLength += len;
1091 bool lengthComputable = expectedLength && m_receivedLength <= expectedLength;
1092 m_progressEventThrottle.dispatchProgressEvent(lengthComputable, m_receivedLength, expectedLength);
1095 if (m_state != LOADING)
1096 changeState(LOADING);
1098 // Firefox calls readyStateChanged every time it receives data, 4449442
1099 callReadyStateChangeListener();
1103 bool XMLHttpRequest::canSuspend() const
1108 void XMLHttpRequest::suspend(ReasonForSuspension)
1110 m_progressEventThrottle.suspend();
1113 void XMLHttpRequest::resume()
1115 m_progressEventThrottle.resume();
1118 void XMLHttpRequest::stop()
1123 void XMLHttpRequest::contextDestroyed()
1126 ActiveDOMObject::contextDestroyed();
1129 const AtomicString& XMLHttpRequest::interfaceName() const
1131 return eventNames().interfaceForXMLHttpRequest;
1134 ScriptExecutionContext* XMLHttpRequest::scriptExecutionContext() const
1136 return ActiveDOMObject::scriptExecutionContext();
1139 EventTargetData* XMLHttpRequest::eventTargetData()
1141 return &m_eventTargetData;
1144 EventTargetData* XMLHttpRequest::ensureEventTargetData()
1146 return &m_eventTargetData;
1149 } // namespace WebCore