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>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 #include "XMLHttpRequest.h"
26 #include "DOMImplementation.h"
27 #include "DOMWindow.h"
29 #include "EventException.h"
30 #include "EventListener.h"
31 #include "EventNames.h"
33 #include "FrameLoader.h"
34 #include "HTTPParsers.h"
35 #include "InspectorController.h"
38 #include "SubresourceLoader.h"
39 #include "TextResourceDecoder.h"
40 #include "XMLHttpRequestException.h"
41 #include "XMLHttpRequestProgressEvent.h"
42 #include "kjs_binding.h"
46 using namespace EventNames;
48 typedef HashSet<XMLHttpRequest*> RequestsSet;
50 static HashMap<Document*, RequestsSet*>& requestsByDocument()
52 static HashMap<Document*, RequestsSet*> map;
56 static void addToRequestsByDocument(Document* doc, XMLHttpRequest* req)
61 RequestsSet* requests = requestsByDocument().get(doc);
63 requests = new RequestsSet;
64 requestsByDocument().set(doc, requests);
67 ASSERT(!requests->contains(req));
71 static void removeFromRequestsByDocument(Document* doc, XMLHttpRequest* req)
76 RequestsSet* requests = requestsByDocument().get(doc);
78 ASSERT(requests->contains(req));
79 requests->remove(req);
80 if (requests->isEmpty()) {
81 requestsByDocument().remove(doc);
86 static bool isSafeRequestHeader(const String& name)
88 static HashSet<String, CaseFoldingHash> forbiddenHeaders;
89 static String proxyString("proxy-");
90 static String secString("sec-");
92 if (forbiddenHeaders.isEmpty()) {
93 forbiddenHeaders.add("accept-charset");
94 forbiddenHeaders.add("accept-encoding");
95 forbiddenHeaders.add("connection");
96 forbiddenHeaders.add("content-length");
97 forbiddenHeaders.add("content-transfer-encoding");
98 forbiddenHeaders.add("date");
99 forbiddenHeaders.add("expect");
100 forbiddenHeaders.add("host");
101 forbiddenHeaders.add("keep-alive");
102 forbiddenHeaders.add("referer");
103 forbiddenHeaders.add("te");
104 forbiddenHeaders.add("trailer");
105 forbiddenHeaders.add("transfer-encoding");
106 forbiddenHeaders.add("upgrade");
107 forbiddenHeaders.add("via");
110 return !forbiddenHeaders.contains(name) && !name.startsWith(proxyString, false) &&
111 !name.startsWith(secString, false);
114 // Determines if a string is a valid token, as defined by
115 // "token" in section 2.2 of RFC 2616.
116 static bool isValidToken(const String& name)
118 unsigned length = name.length();
119 for (unsigned i = 0; i < length; i++) {
122 if (c >= 127 || c <= 32)
125 if (c == '(' || c == ')' || c == '<' || c == '>' || c == '@' ||
126 c == ',' || c == ';' || c == ':' || c == '\\' || c == '\"' ||
127 c == '/' || c == '[' || c == ']' || c == '?' || c == '=' ||
128 c == '{' || c == '}')
135 static bool isValidHeaderValue(const String& name)
137 // FIXME: This should really match name against
138 // field-value in section 4.2 of RFC 2616.
140 return !name.contains('\r') && !name.contains('\n');
143 XMLHttpRequestState XMLHttpRequest::readyState() const
148 const KJS::UString& XMLHttpRequest::responseText() const
150 return m_responseText;
153 Document* XMLHttpRequest::responseXML() const
158 if (!m_createdDocument) {
159 if (m_response.isHTTP() && !responseIsXML()) {
160 // The W3C spec requires this.
163 m_responseXML = m_doc->implementation()->createDocument(0);
164 m_responseXML->open();
165 m_responseXML->setURL(m_url);
166 // FIXME: set Last-Modified and cookies (currently, those are only available for HTMLDocuments).
167 m_responseXML->write(String(m_responseText));
168 m_responseXML->finishParsing();
169 m_responseXML->close();
171 if (!m_responseXML->wellFormed())
174 m_createdDocument = true;
177 return m_responseXML.get();
180 EventListener* XMLHttpRequest::onReadyStateChangeListener() const
182 return m_onReadyStateChangeListener.get();
185 void XMLHttpRequest::setOnReadyStateChangeListener(EventListener* eventListener)
187 m_onReadyStateChangeListener = eventListener;
190 EventListener* XMLHttpRequest::onLoadListener() const
192 return m_onLoadListener.get();
195 EventListener* XMLHttpRequest::onProgressListener() const
197 return m_onProgressListener.get();
200 void XMLHttpRequest::setOnLoadListener(EventListener* eventListener)
202 m_onLoadListener = eventListener;
205 void XMLHttpRequest::setOnProgressListener(EventListener* eventListener)
207 m_onProgressListener = eventListener;
210 void XMLHttpRequest::addEventListener(const AtomicString& eventType, PassRefPtr<EventListener> eventListener, bool)
212 EventListenersMap::iterator iter = m_eventListeners.find(eventType.impl());
213 if (iter == m_eventListeners.end()) {
214 ListenerVector listeners;
215 listeners.append(eventListener);
216 m_eventListeners.add(eventType.impl(), listeners);
218 ListenerVector& listeners = iter->second;
219 for (ListenerVector::iterator listenerIter = listeners.begin(); listenerIter != listeners.end(); ++listenerIter)
220 if (*listenerIter == eventListener)
223 listeners.append(eventListener);
224 m_eventListeners.add(eventType.impl(), listeners);
228 void XMLHttpRequest::removeEventListener(const AtomicString& eventType, EventListener* eventListener, bool)
230 EventListenersMap::iterator iter = m_eventListeners.find(eventType.impl());
231 if (iter == m_eventListeners.end())
234 ListenerVector& listeners = iter->second;
235 for (ListenerVector::const_iterator listenerIter = listeners.begin(); listenerIter != listeners.end(); ++listenerIter)
236 if (*listenerIter == eventListener) {
237 listeners.remove(listenerIter - listeners.begin());
242 bool XMLHttpRequest::dispatchEvent(PassRefPtr<Event> evt, ExceptionCode& ec, bool /*tempEvent*/)
244 // FIXME: check for other error conditions enumerated in the spec.
245 if (evt->type().isEmpty()) {
246 ec = EventException::UNSPECIFIED_EVENT_TYPE_ERR;
250 ListenerVector listenersCopy = m_eventListeners.get(evt->type().impl());
251 for (ListenerVector::const_iterator listenerIter = listenersCopy.begin(); listenerIter != listenersCopy.end(); ++listenerIter) {
252 evt->setTarget(this);
253 evt->setCurrentTarget(this);
254 listenerIter->get()->handleEvent(evt.get(), false);
257 return !evt->defaultPrevented();
260 XMLHttpRequest::XMLHttpRequest(Document* d)
266 , m_createdDocument(false)
268 , m_receivedLength(0)
271 addToRequestsByDocument(m_doc, this);
274 XMLHttpRequest::~XMLHttpRequest()
277 removeFromRequestsByDocument(m_doc, this);
280 void XMLHttpRequest::changeState(XMLHttpRequestState newState)
282 if (m_state != newState) {
284 callReadyStateChangeListener();
288 void XMLHttpRequest::callReadyStateChangeListener()
290 if (!m_doc || !m_doc->frame())
293 RefPtr<Event> evt = new Event(readystatechangeEvent, false, false);
294 if (m_onReadyStateChangeListener) {
295 evt->setTarget(this);
296 evt->setCurrentTarget(this);
297 m_onReadyStateChangeListener->handleEvent(evt.get(), false);
300 ExceptionCode ec = 0;
301 dispatchEvent(evt.release(), ec, false);
304 if (m_state == DONE) {
305 evt = new Event(loadEvent, false, false);
306 if (m_onLoadListener) {
307 evt->setTarget(this);
308 evt->setCurrentTarget(this);
309 m_onLoadListener->handleEvent(evt.get(), false);
312 dispatchEvent(evt, ec, false);
317 bool XMLHttpRequest::urlMatchesDocumentDomain(const KURL& url) const
319 // a local file can load anything
320 if (m_doc->isAllowedToLoadLocalResources())
323 // but a remote document can only load from the same port on the server
324 KURL documentURL(m_doc->url());
325 if (documentURL.protocol().lower() == url.protocol().lower()
326 && documentURL.host().lower() == url.host().lower()
327 && documentURL.port() == url.port())
333 void XMLHttpRequest::open(const String& method, const KURL& url, bool async, ExceptionCode& ec)
336 XMLHttpRequestState previousState = m_state;
340 // clear stuff from possible previous load
341 m_requestHeaders.clear();
342 m_response = ResourceResponse();
347 m_createdDocument = false;
350 ASSERT(m_state == UNSENT);
352 if (!urlMatchesDocumentDomain(url)) {
357 if (!isValidToken(method)) {
362 // Method names are case sensitive. But since Firefox uppercases method names it knows, we'll do the same.
363 String methodUpper(method.upper());
365 if (methodUpper == "TRACE" || methodUpper == "TRACK" || methodUpper == "CONNECT") {
372 if (methodUpper == "COPY" || methodUpper == "DELETE" || methodUpper == "GET" || methodUpper == "HEAD"
373 || methodUpper == "INDEX" || methodUpper == "LOCK" || methodUpper == "M-POST" || methodUpper == "MKCOL" || methodUpper == "MOVE"
374 || methodUpper == "OPTIONS" || methodUpper == "POST" || methodUpper == "PROPFIND" || methodUpper == "PROPPATCH" || methodUpper == "PUT"
375 || methodUpper == "UNLOCK")
376 m_method = methodUpper;
384 // Check previous state to avoid dispatching readyState event
385 // when calling open several times in a row.
386 if (previousState != OPENED)
392 void XMLHttpRequest::open(const String& method, const KURL& url, bool async, const String& user, ExceptionCode& ec)
394 KURL urlWithCredentials(url);
395 urlWithCredentials.setUser(user);
397 open(method, urlWithCredentials, async, ec);
400 void XMLHttpRequest::open(const String& method, const KURL& url, bool async, const String& user, const String& password, ExceptionCode& ec)
402 KURL urlWithCredentials(url);
403 urlWithCredentials.setUser(user);
404 urlWithCredentials.setPass(password);
406 open(method, urlWithCredentials, async, ec);
409 void XMLHttpRequest::send(const String& body, ExceptionCode& ec)
414 if (m_state != OPENED || m_loader) {
415 ec = INVALID_STATE_ERR;
421 ResourceRequest request(m_url);
422 request.setHTTPMethod(m_method);
424 if (!body.isNull() && m_method != "GET" && m_method != "HEAD" && (m_url.protocol().lower() == "http" || m_url.protocol().lower() == "https")) {
425 String contentType = getRequestHeader("Content-Type");
426 if (contentType.isEmpty()) {
427 ExceptionCode ec = 0;
428 #if ENABLE(DASHBOARD_SUPPORT)
429 Settings* settings = m_doc->settings();
430 if (settings && settings->usesDashboardBackwardCompatibilityMode())
431 setRequestHeader("Content-Type", "application/x-www-form-urlencoded", ec);
434 setRequestHeader("Content-Type", "application/xml", ec);
438 // FIXME: must use xmlEncoding for documents.
439 String charset = "UTF-8";
441 TextEncoding encoding(charset);
442 if (!encoding.isValid()) // FIXME: report an error?
443 encoding = UTF8Encoding();
444 request.setHTTPBody(FormData::create(encoding.encode(body.characters(), body.length(), EntitiesForUnencodables)));
447 if (m_requestHeaders.size() > 0)
448 request.addHTTPHeaderFields(m_requestHeaders);
453 ResourceResponse response;
456 // avoid deadlock in case the loader wants to use JS on a background thread
457 KJS::JSLock::DropAllLocks dropLocks;
459 m_identifier = m_doc->frame()->loader()->loadResourceSynchronously(request, error, response, data);
464 // No exception for file:/// resources, see <rdar://problem/4962298>.
465 // Also, if we have an HTTP response, then it wasn't a network error in fact.
466 if (error.isNull() || request.url().isLocalFile() || response.httpStatusCode() > 0)
467 processSyncLoadResults(data, response);
469 ec = XMLHttpRequestException::NETWORK_ERR;
474 // SubresourceLoader::create can return null here, for example if we're no longer attached to a page.
475 // This is true while running onunload handlers.
476 // FIXME: We need to be able to send XMLHttpRequests from onunload, <http://bugs.webkit.org/show_bug.cgi?id=10904>.
477 // FIXME: Maybe create can return null for other reasons too?
478 // We need to keep content sniffing enabled for local files due to CFNetwork not providing a MIME type
479 // for local files otherwise, <rdar://problem/5671813>.
480 m_loader = SubresourceLoader::create(m_doc->frame(), this, request, false, true, request.url().isLocalFile());
483 // Neither this object nor the JavaScript wrapper should be deleted while
484 // a request is in progress because we need to keep the listeners alive,
485 // and they are referenced by the JavaScript wrapper.
489 gcProtectNullTolerant(ScriptInterpreter::getDOMObject(this));
493 void XMLHttpRequest::abort()
495 bool sendFlag = m_loader;
499 // Clear headers as required by the spec
500 m_requestHeaders.clear();
502 if ((m_state <= OPENED && !sendFlag) || m_state == DONE)
511 void XMLHttpRequest::internalAbort()
513 bool hadLoader = m_loader;
517 // FIXME: when we add the support for multi-part XHR, we will have to think be careful with this initialization.
518 m_receivedLength = 0;
531 void XMLHttpRequest::dropProtection()
535 KJS::JSValue* wrapper = ScriptInterpreter::getDOMObject(this);
536 KJS::gcUnprotectNullTolerant(wrapper);
538 // the XHR object itself holds on to the responseText, and
539 // thus has extra cost even independent of any
540 // responseText or responseXML objects it has handed
541 // out. But it is protected from GC while loading, so this
542 // can't be recouped until the load is done, so only
543 // report the extra cost at that point.
546 KJS::Heap::threadHeap()->reportExtraMemoryCost(m_responseText.size() * 2);
552 void XMLHttpRequest::overrideMimeType(const String& override)
554 m_mimeTypeOverride = override;
557 void XMLHttpRequest::setRequestHeader(const String& name, const String& value, ExceptionCode& ec)
559 if (m_state != OPENED || m_loader) {
560 #if ENABLE(DASHBOARD_SUPPORT)
561 Settings* settings = m_doc ? m_doc->settings() : 0;
562 if (settings && settings->usesDashboardBackwardCompatibilityMode())
566 ec = INVALID_STATE_ERR;
570 if (!isValidToken(name) || !isValidHeaderValue(value)) {
575 // A privileged script (e.g. a Dashboard widget) can set any headers.
576 if (!m_doc->isAllowedToLoadLocalResources() && !isSafeRequestHeader(name)) {
577 if (m_doc && m_doc->frame())
578 m_doc->frame()->domWindow()->console()->addMessage(JSMessageSource, ErrorMessageLevel, "Refused to set unsafe header " + name, 1, String());
582 if (!m_requestHeaders.contains(name)) {
583 m_requestHeaders.set(name, value);
587 String oldValue = m_requestHeaders.get(name);
588 m_requestHeaders.set(name, oldValue + ", " + value);
591 String XMLHttpRequest::getRequestHeader(const String& name) const
593 return m_requestHeaders.get(name);
596 String XMLHttpRequest::getAllResponseHeaders(ExceptionCode& ec) const
598 if (m_state < LOADING) {
599 ec = INVALID_STATE_ERR;
603 Vector<UChar> stringBuilder;
604 String separator(": ");
606 HTTPHeaderMap::const_iterator end = m_response.httpHeaderFields().end();
607 for (HTTPHeaderMap::const_iterator it = m_response.httpHeaderFields().begin(); it!= end; ++it) {
608 stringBuilder.append(it->first.characters(), it->first.length());
609 stringBuilder.append(separator.characters(), separator.length());
610 stringBuilder.append(it->second.characters(), it->second.length());
611 stringBuilder.append((UChar)'\r');
612 stringBuilder.append((UChar)'\n');
615 return String::adopt(stringBuilder);
618 String XMLHttpRequest::getResponseHeader(const String& name, ExceptionCode& ec) const
620 if (m_state < LOADING) {
621 ec = INVALID_STATE_ERR;
625 if (!isValidToken(name))
628 return m_response.httpHeaderField(name);
631 String XMLHttpRequest::responseMIMEType() const
633 String mimeType = extractMIMETypeFromMediaType(m_mimeTypeOverride);
634 if (mimeType.isEmpty()) {
635 if (m_response.isHTTP())
636 mimeType = extractMIMETypeFromMediaType(m_response.httpHeaderField("Content-Type"));
638 mimeType = m_response.mimeType();
640 if (mimeType.isEmpty())
641 mimeType = "text/xml";
646 bool XMLHttpRequest::responseIsXML() const
648 return DOMImplementation::isXMLMIMEType(responseMIMEType());
651 int XMLHttpRequest::status(ExceptionCode& ec) const
653 if (m_response.httpStatusCode())
654 return m_response.httpStatusCode();
656 if (m_state == OPENED) {
657 // Firefox only raises an exception in this state; we match it.
658 // 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.
659 ec = INVALID_STATE_ERR;
665 String XMLHttpRequest::statusText(ExceptionCode& ec) const
667 // FIXME: <http://bugs.webkit.org/show_bug.cgi?id=3547> XMLHttpRequest.statusText returns always "OK".
668 if (m_response.httpStatusCode())
671 if (m_state == OPENED) {
672 // See comments in getStatus() above.
673 ec = INVALID_STATE_ERR;
679 void XMLHttpRequest::processSyncLoadResults(const Vector<char>& data, const ResourceResponse& response)
681 if (!urlMatchesDocumentDomain(response.url())) {
686 didReceiveResponse(0, response);
687 changeState(HEADERS_RECEIVED);
691 const char* bytes = static_cast<const char*>(data.data());
692 int len = static_cast<int>(data.size());
694 didReceiveData(0, bytes, len);
701 void XMLHttpRequest::didFail(SubresourceLoader* loader, const ResourceError&)
703 didFinishLoading(loader);
706 void XMLHttpRequest::didFinishLoading(SubresourceLoader* loader)
711 ASSERT(loader == m_loader);
713 if (m_state < HEADERS_RECEIVED)
714 changeState(HEADERS_RECEIVED);
719 m_responseText += m_decoder->flush();
722 if (Frame* frame = m_doc->frame()) {
723 if (Page* page = frame->page())
724 page->inspectorController()->resourceRetrievedByXMLHttpRequest(m_loader ? m_loader->identifier() : m_identifier, m_responseText);
727 bool hadLoader = m_loader;
737 void XMLHttpRequest::willSendRequest(SubresourceLoader*, ResourceRequest& request, const ResourceResponse& redirectResponse)
739 if (!urlMatchesDocumentDomain(request.url()))
743 void XMLHttpRequest::didReceiveResponse(SubresourceLoader*, const ResourceResponse& response)
745 m_response = response;
746 m_responseEncoding = extractCharsetFromMediaType(m_mimeTypeOverride);
747 if (m_responseEncoding.isEmpty())
748 m_responseEncoding = response.textEncodingName();
752 void XMLHttpRequest::receivedCancellation(SubresourceLoader*, const AuthenticationChallenge& challenge)
754 m_response = challenge.failureResponse();
757 void XMLHttpRequest::didReceiveData(SubresourceLoader*, const char* data, int len)
759 if (m_state < HEADERS_RECEIVED)
760 changeState(HEADERS_RECEIVED);
763 if (!m_responseEncoding.isEmpty())
764 m_decoder = new TextResourceDecoder("text/plain", m_responseEncoding);
765 // allow TextResourceDecoder to look inside the m_response if it's XML or HTML
766 else if (responseIsXML())
767 m_decoder = new TextResourceDecoder("application/xml");
768 else if (responseMIMEType() == "text/html")
769 m_decoder = new TextResourceDecoder("text/html", "UTF-8");
771 m_decoder = new TextResourceDecoder("text/plain", "UTF-8");
779 String decoded = m_decoder->decode(data, len);
783 m_responseText += decoded;
787 updateAndDispatchOnProgress(len);
789 if (m_state != LOADING)
790 changeState(LOADING);
792 // Firefox calls readyStateChanged every time it receives data, 4449442
793 callReadyStateChangeListener();
797 void XMLHttpRequest::updateAndDispatchOnProgress(unsigned int len)
799 long long expectedLength = m_response.expectedContentLength();
801 m_receivedLength += len;
803 // FIXME: the spec requires that we dispatch the event according to the least
804 // frequent method between every 350ms (+/-200ms) and for every byte received.
805 dispatchProgressEvent(expectedLength);
808 void XMLHttpRequest::dispatchProgressEvent(long long expectedLength)
810 RefPtr<XMLHttpRequestProgressEvent> evt;
812 // If we do not have the information or it is odd, set lengthComputable to false.
813 evt = new XMLHttpRequestProgressEvent(progressEvent, expectedLength && m_receivedLength <= expectedLength, static_cast<unsigned>(m_receivedLength), static_cast<unsigned>(expectedLength));
815 if (m_onProgressListener) {
816 evt->setTarget(this);
817 evt->setCurrentTarget(this);
818 m_onProgressListener->handleEvent(evt.get(), false);
821 ExceptionCode ec = 0;
822 dispatchEvent(evt, ec, false);
826 void XMLHttpRequest::cancelRequests(Document* m_doc)
828 RequestsSet* requests = requestsByDocument().get(m_doc);
831 RequestsSet copy = *requests;
832 RequestsSet::const_iterator end = copy.end();
833 for (RequestsSet::const_iterator it = copy.begin(); it != end; ++it)
834 (*it)->internalAbort();
837 void XMLHttpRequest::detachRequests(Document* m_doc)
839 RequestsSet* requests = requestsByDocument().get(m_doc);
842 requestsByDocument().remove(m_doc);
843 RequestsSet::const_iterator end = requests->end();
844 for (RequestsSet::const_iterator it = requests->begin(); it != end; ++it) {
846 (*it)->internalAbort();