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 David Levin <levin@chromium.org>
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 "CrossOriginAccessControl.h"
27 #include "CrossOriginPreflightResultCache.h"
28 #include "DOMImplementation.h"
31 #include "EventException.h"
32 #include "EventListener.h"
33 #include "EventNames.h"
35 #include "HTTPParsers.h"
36 #include "ResourceError.h"
37 #include "ResourceRequest.h"
38 #include "SecurityOrigin.h"
40 #include "TextResourceDecoder.h"
41 #include "ThreadableLoader.h"
42 #include "XMLHttpRequestException.h"
43 #include "XMLHttpRequestProgressEvent.h"
44 #include "XMLHttpRequestUpload.h"
46 #include <wtf/StdLibExtras.h>
49 #include "JSDOMWindow.h"
54 struct XMLHttpRequestStaticData {
55 XMLHttpRequestStaticData();
56 String m_proxyHeaderPrefix;
57 String m_secHeaderPrefix;
58 HashSet<String, CaseFoldingHash> m_forbiddenRequestHeaders;
61 XMLHttpRequestStaticData::XMLHttpRequestStaticData()
62 : m_proxyHeaderPrefix("proxy-")
63 , m_secHeaderPrefix("sec-")
65 m_forbiddenRequestHeaders.add("accept-charset");
66 m_forbiddenRequestHeaders.add("accept-encoding");
67 m_forbiddenRequestHeaders.add("access-control-request-headers");
68 m_forbiddenRequestHeaders.add("access-control-request-method");
69 m_forbiddenRequestHeaders.add("connection");
70 m_forbiddenRequestHeaders.add("content-length");
71 m_forbiddenRequestHeaders.add("content-transfer-encoding");
72 m_forbiddenRequestHeaders.add("cookie");
73 m_forbiddenRequestHeaders.add("cookie2");
74 m_forbiddenRequestHeaders.add("date");
75 m_forbiddenRequestHeaders.add("expect");
76 m_forbiddenRequestHeaders.add("host");
77 m_forbiddenRequestHeaders.add("keep-alive");
78 m_forbiddenRequestHeaders.add("origin");
79 m_forbiddenRequestHeaders.add("referer");
80 m_forbiddenRequestHeaders.add("te");
81 m_forbiddenRequestHeaders.add("trailer");
82 m_forbiddenRequestHeaders.add("transfer-encoding");
83 m_forbiddenRequestHeaders.add("upgrade");
84 m_forbiddenRequestHeaders.add("user-agent");
85 m_forbiddenRequestHeaders.add("via");
88 // Determines if a string is a valid token, as defined by
89 // "token" in section 2.2 of RFC 2616.
90 static bool isValidToken(const String& name)
92 unsigned length = name.length();
93 for (unsigned i = 0; i < length; i++) {
96 if (c >= 127 || c <= 32)
99 if (c == '(' || c == ')' || c == '<' || c == '>' || c == '@' ||
100 c == ',' || c == ';' || c == ':' || c == '\\' || c == '\"' ||
101 c == '/' || c == '[' || c == ']' || c == '?' || c == '=' ||
102 c == '{' || c == '}')
109 static bool isValidHeaderValue(const String& name)
111 // FIXME: This should really match name against
112 // field-value in section 4.2 of RFC 2616.
114 return !name.contains('\r') && !name.contains('\n');
117 static bool isSetCookieHeader(const AtomicString& name)
119 return equalIgnoringCase(name, "set-cookie") || equalIgnoringCase(name, "set-cookie2");
122 static const XMLHttpRequestStaticData* staticData = 0;
124 static const XMLHttpRequestStaticData* createXMLHttpRequestStaticData()
126 staticData = new XMLHttpRequestStaticData;
130 static const XMLHttpRequestStaticData* initializeXMLHttpRequestStaticData()
132 // Uses dummy to avoid warnings about an unused variable.
133 AtomicallyInitializedStatic(const XMLHttpRequestStaticData*, dummy = createXMLHttpRequestStaticData());
137 XMLHttpRequest::XMLHttpRequest(ScriptExecutionContext* context)
138 : ActiveDOMObject(context, this)
140 , m_includeCredentials(false)
143 , m_createdDocument(false)
145 , m_uploadComplete(false)
146 , m_sameOriginRequest(true)
147 , m_inPreflight(false)
148 , m_receivedLength(0)
149 , m_lastSendLineNumber(0)
152 initializeXMLHttpRequestStaticData();
155 XMLHttpRequest::~XMLHttpRequest()
158 m_upload->disconnectXMLHttpRequest();
161 Document* XMLHttpRequest::document() const
163 ASSERT(scriptExecutionContext()->isDocument());
164 return static_cast<Document*>(scriptExecutionContext());
167 #if ENABLE(DASHBOARD_SUPPORT)
168 bool XMLHttpRequest::usesDashboardBackwardCompatibilityMode() const
170 if (scriptExecutionContext()->isWorkerContext())
172 Settings* settings = document()->settings();
173 return settings && settings->usesDashboardBackwardCompatibilityMode();
177 XMLHttpRequest::State XMLHttpRequest::readyState() const
182 const ScriptString& XMLHttpRequest::responseText() const
184 return m_responseText;
187 Document* XMLHttpRequest::responseXML() const
192 if (!m_createdDocument) {
193 if ((m_response.isHTTP() && !responseIsXML()) || scriptExecutionContext()->isWorkerContext()) {
194 // The W3C spec requires this.
197 m_responseXML = document()->implementation()->createDocument(0);
198 m_responseXML->open();
199 m_responseXML->setURL(m_url);
200 // FIXME: Set Last-Modified.
201 m_responseXML->write(String(m_responseText));
202 m_responseXML->finishParsing();
203 m_responseXML->close();
205 if (!m_responseXML->wellFormed())
208 m_createdDocument = true;
211 return m_responseXML.get();
214 XMLHttpRequestUpload* XMLHttpRequest::upload()
217 m_upload = XMLHttpRequestUpload::create(this);
218 return m_upload.get();
221 void XMLHttpRequest::addEventListener(const AtomicString& eventType, PassRefPtr<EventListener> eventListener, bool)
223 EventListenersMap::iterator iter = m_eventListeners.find(eventType);
224 if (iter == m_eventListeners.end()) {
225 ListenerVector listeners;
226 listeners.append(eventListener);
227 m_eventListeners.add(eventType, listeners);
229 ListenerVector& listeners = iter->second;
230 for (ListenerVector::iterator listenerIter = listeners.begin(); listenerIter != listeners.end(); ++listenerIter)
231 if (*listenerIter == eventListener)
234 listeners.append(eventListener);
235 m_eventListeners.add(eventType, listeners);
239 void XMLHttpRequest::removeEventListener(const AtomicString& eventType, EventListener* eventListener, bool)
241 EventListenersMap::iterator iter = m_eventListeners.find(eventType);
242 if (iter == m_eventListeners.end())
245 ListenerVector& listeners = iter->second;
246 for (ListenerVector::const_iterator listenerIter = listeners.begin(); listenerIter != listeners.end(); ++listenerIter)
247 if (*listenerIter == eventListener) {
248 listeners.remove(listenerIter - listeners.begin());
253 bool XMLHttpRequest::dispatchEvent(PassRefPtr<Event> evt, ExceptionCode& ec)
255 // FIXME: check for other error conditions enumerated in the spec.
256 if (!evt || evt->type().isEmpty()) {
257 ec = EventException::UNSPECIFIED_EVENT_TYPE_ERR;
261 ListenerVector listenersCopy = m_eventListeners.get(evt->type());
262 for (ListenerVector::const_iterator listenerIter = listenersCopy.begin(); listenerIter != listenersCopy.end(); ++listenerIter) {
263 evt->setTarget(this);
264 evt->setCurrentTarget(this);
265 listenerIter->get()->handleEvent(evt.get(), false);
268 return !evt->defaultPrevented();
271 void XMLHttpRequest::changeState(State newState)
273 if (m_state != newState) {
275 callReadyStateChangeListener();
279 void XMLHttpRequest::callReadyStateChangeListener()
281 if (!scriptExecutionContext())
284 dispatchReadyStateChangeEvent();
286 if (m_state == DONE && !m_error)
290 void XMLHttpRequest::setWithCredentials(bool value, ExceptionCode& ec)
292 if (m_state != OPENED || m_loader) {
293 ec = INVALID_STATE_ERR;
297 m_includeCredentials = value;
300 void XMLHttpRequest::open(const String& method, const KURL& url, bool async, ExceptionCode& ec)
303 State previousState = m_state;
307 m_uploadComplete = false;
309 // clear stuff from possible previous load
313 ASSERT(m_state == UNSENT);
315 if (!isValidToken(method)) {
320 // Method names are case sensitive. But since Firefox uppercases method names it knows, we'll do the same.
321 String methodUpper(method.upper());
323 if (methodUpper == "TRACE" || methodUpper == "TRACK" || methodUpper == "CONNECT") {
330 if (methodUpper == "COPY" || methodUpper == "DELETE" || methodUpper == "GET" || methodUpper == "HEAD"
331 || methodUpper == "INDEX" || methodUpper == "LOCK" || methodUpper == "M-POST" || methodUpper == "MKCOL" || methodUpper == "MOVE"
332 || methodUpper == "OPTIONS" || methodUpper == "POST" || methodUpper == "PROPFIND" || methodUpper == "PROPPATCH" || methodUpper == "PUT"
333 || methodUpper == "UNLOCK")
334 m_method = methodUpper;
342 // Check previous state to avoid dispatching readyState event
343 // when calling open several times in a row.
344 if (previousState != OPENED)
350 void XMLHttpRequest::open(const String& method, const KURL& url, bool async, const String& user, ExceptionCode& ec)
352 KURL urlWithCredentials(url);
353 urlWithCredentials.setUser(user);
355 open(method, urlWithCredentials, async, ec);
358 void XMLHttpRequest::open(const String& method, const KURL& url, bool async, const String& user, const String& password, ExceptionCode& ec)
360 KURL urlWithCredentials(url);
361 urlWithCredentials.setUser(user);
362 urlWithCredentials.setPass(password);
364 open(method, urlWithCredentials, async, ec);
367 bool XMLHttpRequest::initSend(ExceptionCode& ec)
369 if (!scriptExecutionContext())
372 if (m_state != OPENED || m_loader) {
373 ec = INVALID_STATE_ERR;
381 void XMLHttpRequest::send(ExceptionCode& ec)
386 void XMLHttpRequest::send(Document* document, ExceptionCode& ec)
393 if (m_method != "GET" && m_method != "HEAD" && m_url.protocolInHTTPFamily()) {
394 String contentType = getRequestHeader("Content-Type");
395 if (contentType.isEmpty()) {
396 #if ENABLE(DASHBOARD_SUPPORT)
397 if (usesDashboardBackwardCompatibilityMode())
398 setRequestHeaderInternal("Content-Type", "application/x-www-form-urlencoded");
401 // FIXME: this should include the charset used for encoding.
402 setRequestHeaderInternal("Content-Type", "application/xml");
405 // FIXME: According to XMLHttpRequest Level 2, this should use the Document.innerHTML algorithm
406 // from the HTML5 specification to serialize the document.
407 String body = createMarkup(document);
409 // FIXME: this should use value of document.inputEncoding to determine the encoding to use.
410 TextEncoding encoding = UTF8Encoding();
411 m_requestEntityBody = FormData::create(encoding.encode(body.characters(), body.length(), EntitiesForUnencodables));
413 m_requestEntityBody->setAlwaysStream(true);
419 void XMLHttpRequest::send(const String& body, ExceptionCode& ec)
424 if (!body.isNull() && m_method != "GET" && m_method != "HEAD" && m_url.protocolInHTTPFamily()) {
425 String contentType = getRequestHeader("Content-Type");
426 if (contentType.isEmpty()) {
427 #if ENABLE(DASHBOARD_SUPPORT)
428 if (usesDashboardBackwardCompatibilityMode())
429 setRequestHeaderInternal("Content-Type", "application/x-www-form-urlencoded");
432 setRequestHeaderInternal("Content-Type", "application/xml");
435 m_requestEntityBody = FormData::create(UTF8Encoding().encode(body.characters(), body.length(), EntitiesForUnencodables));
437 m_requestEntityBody->setAlwaysStream(true);
443 void XMLHttpRequest::send(File* body, ExceptionCode& ec)
448 if (m_method != "GET" && m_method != "HEAD" && m_url.protocolInHTTPFamily()) {
449 // FIXME: Should we set a Content-Type if one is not set.
450 // FIXME: add support for uploading bundles.
451 m_requestEntityBody = FormData::create();
452 m_requestEntityBody->appendFile(body->path(), false);
458 void XMLHttpRequest::createRequest(ExceptionCode& ec)
460 // Upload event listeners should be disallowed for simple cross-origin requests, because POSTing to an URL that does not
461 // permit cross origin requests should look exactly like POSTing to an URL that does not respond at all. If a listener exists
462 // when creating the request, it will force preflight.
463 // Also, only async requests support upload progress events.
464 m_uploadEventsAllowed = false;
466 dispatchLoadStartEvent();
467 if (m_requestEntityBody && m_upload) {
468 m_uploadEventsAllowed = m_upload->hasListeners();
469 m_upload->dispatchLoadStartEvent();
473 m_sameOriginRequest = scriptExecutionContext()->securityOrigin()->canRequest(m_url);
475 if (!m_sameOriginRequest) {
476 makeCrossOriginAccessRequest(ec);
480 m_uploadEventsAllowed = true;
482 makeSameOriginRequest(ec);
485 void XMLHttpRequest::makeSameOriginRequest(ExceptionCode& ec)
487 ASSERT(m_sameOriginRequest);
489 ResourceRequest request(m_url);
490 request.setHTTPMethod(m_method);
492 if (m_requestEntityBody) {
493 ASSERT(m_method != "GET");
494 ASSERT(m_method != "HEAD");
495 request.setHTTPBody(m_requestEntityBody.release());
498 if (m_requestHeaders.size() > 0)
499 request.addHTTPHeaderFields(m_requestHeaders);
502 loadRequestAsynchronously(request);
504 loadRequestSynchronously(request, ec);
507 void XMLHttpRequest::makeCrossOriginAccessRequest(ExceptionCode& ec)
509 ASSERT(!m_sameOriginRequest);
511 if (!m_uploadEventsAllowed && isSimpleCrossOriginAccessRequest(m_method, m_requestHeaders))
512 makeSimpleCrossOriginAccessRequest(ec);
514 makeCrossOriginAccessRequestWithPreflight(ec);
517 void XMLHttpRequest::makeSimpleCrossOriginAccessRequest(ExceptionCode& ec)
519 ASSERT(isSimpleCrossOriginAccessRequest(m_method, m_requestHeaders));
521 // Cross-origin requests are only defined for HTTP. We would catch this when checking response headers later, but there is no reason to send a request that's guaranteed to be denied.
522 if (!m_url.protocolInHTTPFamily()) {
523 ec = XMLHttpRequestException::NETWORK_ERR;
529 url.setUser(String());
530 url.setPass(String());
532 ResourceRequest request(url);
533 request.setHTTPMethod(m_method);
534 request.setAllowHTTPCookies(m_includeCredentials);
535 request.setHTTPOrigin(scriptExecutionContext()->securityOrigin()->toString());
537 if (m_requestHeaders.size() > 0)
538 request.addHTTPHeaderFields(m_requestHeaders);
540 if (m_requestEntityBody) {
541 ASSERT(m_method != "GET");
542 ASSERT(m_method != "HEAD");
543 request.setHTTPBody(m_requestEntityBody.release());
547 loadRequestAsynchronously(request);
549 loadRequestSynchronously(request, ec);
552 void XMLHttpRequest::makeCrossOriginAccessRequestWithPreflight(ExceptionCode& ec)
554 String origin = scriptExecutionContext()->securityOrigin()->toString();
556 url.setUser(String());
557 url.setPass(String());
559 if (!CrossOriginPreflightResultCache::shared().canSkipPreflight(origin, url, m_includeCredentials, m_method, m_requestHeaders)) {
560 m_inPreflight = true;
561 ResourceRequest preflightRequest(url);
562 preflightRequest.setHTTPMethod("OPTIONS");
563 preflightRequest.setHTTPHeaderField("Origin", origin);
564 preflightRequest.setHTTPHeaderField("Access-Control-Request-Method", m_method);
566 if (m_requestHeaders.size() > 0) {
567 Vector<UChar> headerBuffer;
568 HTTPHeaderMap::const_iterator it = m_requestHeaders.begin();
569 append(headerBuffer, it->first);
572 HTTPHeaderMap::const_iterator end = m_requestHeaders.end();
573 for (; it != end; ++it) {
574 headerBuffer.append(',');
575 headerBuffer.append(' ');
576 append(headerBuffer, it->first);
579 preflightRequest.setHTTPHeaderField("Access-Control-Request-Headers", String::adopt(headerBuffer));
580 preflightRequest.addHTTPHeaderFields(m_requestHeaders);
584 m_uploadEventsAllowed = true;
585 loadRequestAsynchronously(preflightRequest);
589 loadRequestSynchronously(preflightRequest, ec);
590 m_inPreflight = false;
596 // Send the actual request.
597 ResourceRequest request(url);
598 request.setHTTPMethod(m_method);
599 request.setAllowHTTPCookies(m_includeCredentials);
600 request.setHTTPHeaderField("Origin", origin);
602 if (m_requestHeaders.size() > 0)
603 request.addHTTPHeaderFields(m_requestHeaders);
605 if (m_requestEntityBody) {
606 ASSERT(m_method != "GET");
607 ASSERT(m_method != "HEAD");
608 request.setHTTPBody(m_requestEntityBody.release());
612 m_uploadEventsAllowed = true;
613 loadRequestAsynchronously(request);
617 loadRequestSynchronously(request, ec);
620 void XMLHttpRequest::handleAsynchronousPreflightResult()
622 ASSERT(m_inPreflight);
625 m_inPreflight = false;
628 url.setUser(String());
629 url.setPass(String());
631 ResourceRequest request(url);
632 request.setHTTPMethod(m_method);
633 request.setAllowHTTPCookies(m_includeCredentials);
634 request.setHTTPOrigin(scriptExecutionContext()->securityOrigin()->toString());
636 if (m_requestHeaders.size() > 0)
637 request.addHTTPHeaderFields(m_requestHeaders);
639 if (m_requestEntityBody) {
640 ASSERT(m_method != "GET");
641 ASSERT(m_method != "HEAD");
642 request.setHTTPBody(m_requestEntityBody.release());
645 m_uploadEventsAllowed = true;
646 loadRequestAsynchronously(request);
649 void XMLHttpRequest::loadRequestSynchronously(ResourceRequest& request, ExceptionCode& ec)
655 StoredCredentials storedCredentials = (m_sameOriginRequest || m_includeCredentials) ? AllowStoredCredentials : DoNotAllowStoredCredentials;
657 ThreadableLoader::loadResourceSynchronously(scriptExecutionContext(), request, *this, storedCredentials);
658 if (!m_exceptionCode && m_error)
659 m_exceptionCode = XMLHttpRequestException::NETWORK_ERR;
660 ec = m_exceptionCode;
663 void XMLHttpRequest::loadRequestAsynchronously(ResourceRequest& request)
667 // SubresourceLoader::create can return null here, for example if we're no longer attached to a page.
668 // This is true while running onunload handlers.
669 // FIXME: We need to be able to send XMLHttpRequests from onunload, <http://bugs.webkit.org/show_bug.cgi?id=10904>.
670 // FIXME: Maybe create can return null for other reasons too?
671 LoadCallbacks callbacks = m_inPreflight ? DoNotSendLoadCallbacks : SendLoadCallbacks;
672 StoredCredentials storedCredentials = (m_sameOriginRequest || m_includeCredentials) ? AllowStoredCredentials : DoNotAllowStoredCredentials;
675 request.setReportUploadProgress(true);
677 m_loader = ThreadableLoader::create(scriptExecutionContext(), this, request, callbacks, DoNotSniffContent, storedCredentials, RequireSameRedirectOrigin);
680 // Neither this object nor the JavaScript wrapper should be deleted while
681 // a request is in progress because we need to keep the listeners alive,
682 // and they are referenced by the JavaScript wrapper.
683 setPendingActivity(this);
687 void XMLHttpRequest::abort()
689 // internalAbort() calls dropProtection(), which may release the last reference.
690 RefPtr<XMLHttpRequest> protect(this);
692 bool sendFlag = m_loader;
696 // Clear headers as required by the spec
697 m_requestHeaders.clear();
699 if ((m_state <= OPENED && !sendFlag) || m_state == DONE)
707 dispatchAbortEvent();
708 if (!m_uploadComplete) {
709 m_uploadComplete = true;
710 if (m_upload && m_uploadEventsAllowed)
711 m_upload->dispatchAbortEvent();
715 void XMLHttpRequest::internalAbort()
717 bool hadLoader = m_loader;
721 // FIXME: when we add the support for multi-part XHR, we will have to think be careful with this initialization.
722 m_receivedLength = 0;
735 void XMLHttpRequest::clearResponse()
737 m_response = ResourceResponse();
739 m_createdDocument = false;
743 void XMLHttpRequest::clearRequest()
745 m_requestHeaders.clear();
746 m_requestEntityBody = 0;
749 void XMLHttpRequest::genericError()
758 void XMLHttpRequest::networkError()
761 dispatchErrorEvent();
762 if (!m_uploadComplete) {
763 m_uploadComplete = true;
764 if (m_upload && m_uploadEventsAllowed)
765 m_upload->dispatchErrorEvent();
770 void XMLHttpRequest::abortError()
773 dispatchAbortEvent();
774 if (!m_uploadComplete) {
775 m_uploadComplete = true;
776 if (m_upload && m_uploadEventsAllowed)
777 m_upload->dispatchAbortEvent();
781 void XMLHttpRequest::dropProtection()
784 // The XHR object itself holds on to the responseText, and
785 // thus has extra cost even independent of any
786 // responseText or responseXML objects it has handed
787 // out. But it is protected from GC while loading, so this
788 // can't be recouped until the load is done, so only
789 // report the extra cost at that point.
791 if (JSDOMGlobalObject* globalObject = toJSDOMGlobalObject(scriptExecutionContext()))
792 if (DOMObject* wrapper = getCachedDOMObjectWrapper(*globalObject->globalData(), this))
793 JSC::Heap::heap(wrapper)->reportExtraMemoryCost(m_responseText.size() * 2);
796 unsetPendingActivity(this);
799 void XMLHttpRequest::overrideMimeType(const String& override)
801 m_mimeTypeOverride = override;
804 static void reportUnsafeUsage(ScriptExecutionContext* context, const String& message)
808 // FIXME: It's not good to report the bad usage without indicating what source line it came from.
809 // We should pass additional parameters so we can tell the console where the mistake occurred.
810 context->addMessage(ConsoleDestination, JSMessageSource, ErrorMessageLevel, message, 1, String());
813 void XMLHttpRequest::setRequestHeader(const AtomicString& name, const String& value, ExceptionCode& ec)
815 if (m_state != OPENED || m_loader) {
816 #if ENABLE(DASHBOARD_SUPPORT)
817 if (usesDashboardBackwardCompatibilityMode())
821 ec = INVALID_STATE_ERR;
825 if (!isValidToken(name) || !isValidHeaderValue(value)) {
830 // A privileged script (e.g. a Dashboard widget) can set any headers.
831 if (!scriptExecutionContext()->securityOrigin()->canLoadLocalResources() && !isSafeRequestHeader(name)) {
832 reportUnsafeUsage(scriptExecutionContext(), "Refused to set unsafe header \"" + name + "\"");
836 setRequestHeaderInternal(name, value);
839 void XMLHttpRequest::setRequestHeaderInternal(const AtomicString& name, const String& value)
841 pair<HTTPHeaderMap::iterator, bool> result = m_requestHeaders.add(name, value);
843 result.first->second += ", " + value;
846 bool XMLHttpRequest::isSafeRequestHeader(const String& name) const
848 return !staticData->m_forbiddenRequestHeaders.contains(name) && !name.startsWith(staticData->m_proxyHeaderPrefix, false)
849 && !name.startsWith(staticData->m_secHeaderPrefix, false);
852 String XMLHttpRequest::getRequestHeader(const AtomicString& name) const
854 return m_requestHeaders.get(name);
857 String XMLHttpRequest::getAllResponseHeaders(ExceptionCode& ec) const
859 if (m_state < LOADING) {
860 ec = INVALID_STATE_ERR;
864 Vector<UChar> stringBuilder;
866 HTTPHeaderMap::const_iterator end = m_response.httpHeaderFields().end();
867 for (HTTPHeaderMap::const_iterator it = m_response.httpHeaderFields().begin(); it!= end; ++it) {
868 // Hide Set-Cookie header fields from the XMLHttpRequest client for these reasons:
869 // 1) If the client did have access to the fields, then it could read HTTP-only
870 // cookies; those cookies are supposed to be hidden from scripts.
871 // 2) There's no known harm in hiding Set-Cookie header fields entirely; we don't
872 // know any widely used technique that requires access to them.
873 // 3) Firefox has implemented this policy.
874 if (isSetCookieHeader(it->first) && !scriptExecutionContext()->securityOrigin()->canLoadLocalResources())
877 if (!m_sameOriginRequest && !isOnAccessControlResponseHeaderWhitelist(it->first))
880 stringBuilder.append(it->first.characters(), it->first.length());
881 stringBuilder.append(':');
882 stringBuilder.append(' ');
883 stringBuilder.append(it->second.characters(), it->second.length());
884 stringBuilder.append('\r');
885 stringBuilder.append('\n');
888 return String::adopt(stringBuilder);
891 String XMLHttpRequest::getResponseHeader(const AtomicString& name, ExceptionCode& ec) const
893 if (m_state < LOADING) {
894 ec = INVALID_STATE_ERR;
898 if (!isValidToken(name))
901 // See comment in getAllResponseHeaders above.
902 if (isSetCookieHeader(name) && !scriptExecutionContext()->securityOrigin()->canLoadLocalResources()) {
903 reportUnsafeUsage(scriptExecutionContext(), "Refused to get unsafe header \"" + name + "\"");
907 if (!m_sameOriginRequest && !isOnAccessControlResponseHeaderWhitelist(name)) {
908 reportUnsafeUsage(scriptExecutionContext(), "Refused to get unsafe header \"" + name + "\"");
912 return m_response.httpHeaderField(name);
915 String XMLHttpRequest::responseMIMEType() const
917 String mimeType = extractMIMETypeFromMediaType(m_mimeTypeOverride);
918 if (mimeType.isEmpty()) {
919 if (m_response.isHTTP())
920 mimeType = extractMIMETypeFromMediaType(m_response.httpHeaderField("Content-Type"));
922 mimeType = m_response.mimeType();
924 if (mimeType.isEmpty())
925 mimeType = "text/xml";
930 bool XMLHttpRequest::responseIsXML() const
932 return DOMImplementation::isXMLMIMEType(responseMIMEType());
935 int XMLHttpRequest::status(ExceptionCode& ec) const
937 if (m_response.httpStatusCode())
938 return m_response.httpStatusCode();
940 if (m_state == OPENED) {
941 // Firefox only raises an exception in this state; we match it.
942 // 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.
943 ec = INVALID_STATE_ERR;
949 String XMLHttpRequest::statusText(ExceptionCode& ec) const
951 if (!m_response.httpStatusText().isNull())
952 return m_response.httpStatusText();
954 if (m_state == OPENED) {
955 // See comments in status() above.
956 ec = INVALID_STATE_ERR;
962 void XMLHttpRequest::didFail(const ResourceError& error)
964 // If we are already in an error state, for instance we called abort(), bail out early.
968 if (error.isCancellation()) {
969 m_exceptionCode = XMLHttpRequestException::ABORT_ERR;
974 m_exceptionCode = XMLHttpRequestException::NETWORK_ERR;
978 void XMLHttpRequest::didFailRedirectCheck()
983 void XMLHttpRequest::didFinishLoading(unsigned long identifier)
989 didFinishLoadingPreflight();
993 if (m_state < HEADERS_RECEIVED)
994 changeState(HEADERS_RECEIVED);
997 m_responseText += m_decoder->flush();
999 scriptExecutionContext()->resourceRetrievedByXMLHttpRequest(identifier, m_responseText);
1000 scriptExecutionContext()->addMessage(InspectorControllerDestination, JSMessageSource, LogMessageLevel, "XHR finished loading: \"" + m_url + "\".", m_lastSendLineNumber, m_lastSendURL);
1002 bool hadLoader = m_loader;
1012 void XMLHttpRequest::didFinishLoadingPreflight()
1014 ASSERT(m_inPreflight);
1015 ASSERT(!m_sameOriginRequest);
1017 // FIXME: this can probably be moved to didReceiveResponsePreflight.
1019 handleAsynchronousPreflightResult();
1022 unsetPendingActivity(this);
1025 void XMLHttpRequest::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
1030 if (m_uploadEventsAllowed)
1031 m_upload->dispatchProgressEvent(bytesSent, totalBytesToBeSent);
1033 if (bytesSent == totalBytesToBeSent && !m_uploadComplete) {
1034 m_uploadComplete = true;
1035 if (m_uploadEventsAllowed)
1036 m_upload->dispatchLoadEvent();
1040 void XMLHttpRequest::didReceiveResponse(const ResourceResponse& response)
1042 if (m_inPreflight) {
1043 didReceiveResponsePreflight(response);
1047 if (!m_sameOriginRequest) {
1048 if (!passesAccessControlCheck(response, m_includeCredentials, scriptExecutionContext()->securityOrigin())) {
1054 m_response = response;
1055 m_responseEncoding = extractCharsetFromMediaType(m_mimeTypeOverride);
1056 if (m_responseEncoding.isEmpty())
1057 m_responseEncoding = response.textEncodingName();
1060 void XMLHttpRequest::didReceiveResponsePreflight(const ResourceResponse& response)
1062 ASSERT(m_inPreflight);
1063 ASSERT(!m_sameOriginRequest);
1065 if (!passesAccessControlCheck(response, m_includeCredentials, scriptExecutionContext()->securityOrigin())) {
1070 OwnPtr<CrossOriginPreflightResultCacheItem> preflightResult(new CrossOriginPreflightResultCacheItem(m_includeCredentials));
1071 if (!preflightResult->parse(response)
1072 || !preflightResult->allowsCrossOriginMethod(m_method)
1073 || !preflightResult->allowsCrossOriginHeaders(m_requestHeaders)) {
1078 CrossOriginPreflightResultCache::shared().appendEntry(scriptExecutionContext()->securityOrigin()->toString(), m_url, preflightResult.release());
1081 void XMLHttpRequest::didReceiveAuthenticationCancellation(const ResourceResponse& failureResponse)
1083 m_response = failureResponse;
1086 void XMLHttpRequest::didReceiveData(const char* data, int len)
1088 if (m_inPreflight || m_error)
1091 if (m_state < HEADERS_RECEIVED)
1092 changeState(HEADERS_RECEIVED);
1095 if (!m_responseEncoding.isEmpty())
1096 m_decoder = TextResourceDecoder::create("text/plain", m_responseEncoding);
1097 // allow TextResourceDecoder to look inside the m_response if it's XML or HTML
1098 else if (responseIsXML()) {
1099 m_decoder = TextResourceDecoder::create("application/xml");
1100 // 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.
1101 m_decoder->useLenientXMLDecoding();
1102 } else if (responseMIMEType() == "text/html")
1103 m_decoder = TextResourceDecoder::create("text/html", "UTF-8");
1105 m_decoder = TextResourceDecoder::create("text/plain", "UTF-8");
1114 m_responseText += m_decoder->decode(data, len);
1117 updateAndDispatchOnProgress(len);
1119 if (m_state != LOADING)
1120 changeState(LOADING);
1122 // Firefox calls readyStateChanged every time it receives data, 4449442
1123 callReadyStateChangeListener();
1127 void XMLHttpRequest::updateAndDispatchOnProgress(unsigned int len)
1129 long long expectedLength = m_response.expectedContentLength();
1130 m_receivedLength += len;
1132 // FIXME: the spec requires that we dispatch the event according to the least
1133 // frequent method between every 350ms (+/-200ms) and for every byte received.
1134 dispatchProgressEvent(expectedLength);
1137 void XMLHttpRequest::dispatchReadyStateChangeEvent()
1139 RefPtr<Event> evt = Event::create(eventNames().readystatechangeEvent, false, false);
1140 if (m_onReadyStateChangeListener) {
1141 evt->setTarget(this);
1142 evt->setCurrentTarget(this);
1143 m_onReadyStateChangeListener->handleEvent(evt.get(), false);
1146 ExceptionCode ec = 0;
1147 dispatchEvent(evt.release(), ec);
1151 void XMLHttpRequest::dispatchXMLHttpRequestProgressEvent(EventListener* listener, const AtomicString& type, bool lengthComputable, unsigned loaded, unsigned total)
1153 RefPtr<XMLHttpRequestProgressEvent> evt = XMLHttpRequestProgressEvent::create(type, lengthComputable, loaded, total);
1155 evt->setTarget(this);
1156 evt->setCurrentTarget(this);
1157 listener->handleEvent(evt.get(), false);
1160 ExceptionCode ec = 0;
1161 dispatchEvent(evt.release(), ec);
1165 void XMLHttpRequest::dispatchAbortEvent()
1167 dispatchXMLHttpRequestProgressEvent(m_onAbortListener.get(), eventNames().abortEvent, false, 0, 0);
1170 void XMLHttpRequest::dispatchErrorEvent()
1172 dispatchXMLHttpRequestProgressEvent(m_onErrorListener.get(), eventNames().errorEvent, false, 0, 0);
1175 void XMLHttpRequest::dispatchLoadEvent()
1177 dispatchXMLHttpRequestProgressEvent(m_onLoadListener.get(), eventNames().loadEvent, false, 0, 0);
1180 void XMLHttpRequest::dispatchLoadStartEvent()
1182 dispatchXMLHttpRequestProgressEvent(m_onLoadStartListener.get(), eventNames().loadstartEvent, false, 0, 0);
1185 void XMLHttpRequest::dispatchProgressEvent(long long expectedLength)
1187 dispatchXMLHttpRequestProgressEvent(m_onProgressListener.get(), eventNames().progressEvent, expectedLength && m_receivedLength <= expectedLength,
1188 static_cast<unsigned>(m_receivedLength), static_cast<unsigned>(expectedLength));
1191 bool XMLHttpRequest::canSuspend() const
1196 void XMLHttpRequest::stop()
1201 void XMLHttpRequest::contextDestroyed()
1204 ActiveDOMObject::contextDestroyed();
1207 ScriptExecutionContext* XMLHttpRequest::scriptExecutionContext() const
1209 return ActiveDOMObject::scriptExecutionContext();
1212 } // namespace WebCore