Reviewed by Darin.
[WebKit-https.git] / WebCore / xml / XMLHttpRequest.cpp
1 /*
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 Julien Chaffraix <julien.chaffraix@gmail.com>
5  *
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.
10  *
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.
15  *
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
19  */
20
21 #include "config.h"
22 #include "XMLHttpRequest.h"
23
24 #include "CString.h"
25 #include "DOMImplementation.h"
26 #include "Event.h"
27 #include "EventException.h"
28 #include "EventListener.h"
29 #include "EventNames.h"
30 #include "Frame.h"
31 #include "FrameLoader.h"
32 #include "HTTPParsers.h"
33 #include "Page.h"
34 #include "Settings.h"
35 #include "SubresourceLoader.h"
36 #include "TextResourceDecoder.h"
37 #include "XMLHttpRequestException.h"
38 #include "kjs_binding.h"
39
40 namespace WebCore {
41
42 using namespace EventNames;
43
44 typedef HashSet<XMLHttpRequest*> RequestsSet;
45
46 static HashMap<Document*, RequestsSet*>& requestsByDocument()
47 {
48     static HashMap<Document*, RequestsSet*> map;
49     return map;
50 }
51
52 static void addToRequestsByDocument(Document* doc, XMLHttpRequest* req)
53 {
54     ASSERT(doc);
55     ASSERT(req);
56
57     RequestsSet* requests = requestsByDocument().get(doc);
58     if (!requests) {
59         requests = new RequestsSet;
60         requestsByDocument().set(doc, requests);
61     }
62
63     ASSERT(!requests->contains(req));
64     requests->add(req);
65 }
66
67 static void removeFromRequestsByDocument(Document* doc, XMLHttpRequest* req)
68 {
69     ASSERT(doc);
70     ASSERT(req);
71
72     RequestsSet* requests = requestsByDocument().get(doc);
73     ASSERT(requests);
74     ASSERT(requests->contains(req));
75     requests->remove(req);
76     if (requests->isEmpty()) {
77         requestsByDocument().remove(doc);
78         delete requests;
79     }
80 }
81
82 static bool canSetRequestHeader(const String& name)
83 {
84     // A privileged script (e.g. a Dashboard widget) can set any headers.
85     if (m_doc->isAllowedToLoadLocalResources())
86         return true;
87
88     static HashSet<String, CaseFoldingHash> forbiddenHeaders;
89     static String proxyString("proxy-");
90     
91     if (forbiddenHeaders.isEmpty()) {
92         forbiddenHeaders.add("accept-charset");
93         forbiddenHeaders.add("accept-encoding");
94         forbiddenHeaders.add("connection");
95         forbiddenHeaders.add("content-length");
96         forbiddenHeaders.add("content-transfer-encoding");
97         forbiddenHeaders.add("date");
98         forbiddenHeaders.add("expect");
99         forbiddenHeaders.add("host");
100         forbiddenHeaders.add("keep-alive");
101         forbiddenHeaders.add("referer");
102         forbiddenHeaders.add("te");
103         forbiddenHeaders.add("trailer");
104         forbiddenHeaders.add("transfer-encoding");
105         forbiddenHeaders.add("upgrade");
106         forbiddenHeaders.add("via");
107     }
108     
109     return !forbiddenHeaders.contains(name) && !name.startsWith(proxyString, false);
110 }
111
112 // Determines if a string is a valid token, as defined by
113 // "token" in section 2.2 of RFC 2616.
114 static bool isValidToken(const String& name)
115 {
116     unsigned length = name.length();
117     for (unsigned i = 0; i < length; i++) {
118         UChar c = name[i];
119         
120         if (c >= 127 || c <= 32)
121             return false;
122         
123         if (c == '(' || c == ')' || c == '<' || c == '>' || c == '@' ||
124             c == ',' || c == ';' || c == ':' || c == '\\' || c == '\"' ||
125             c == '/' || c == '[' || c == ']' || c == '?' || c == '=' ||
126             c == '{' || c == '}')
127             return false;
128     }
129     
130     return true;
131 }
132     
133 static bool isValidHeaderValue(const String& name)
134 {
135     // FIXME: This should really match name against 
136     // field-value in section 4.2 of RFC 2616.
137         
138     return !name.contains('\r') && !name.contains('\n');
139 }
140     
141 XMLHttpRequestState XMLHttpRequest::getReadyState() const
142 {
143     return m_state;
144 }
145
146 const KJS::UString& XMLHttpRequest::getResponseText(ExceptionCode& ec) const
147 {
148     return m_responseText;
149 }
150
151 Document* XMLHttpRequest::getResponseXML(ExceptionCode& ec) const
152 {
153     if (m_state != Loaded)
154         return 0;
155
156     if (!m_createdDocument) {
157         if (m_response.isHTTP() && !responseIsXML()) {
158             // The W3C spec requires this.
159             m_responseXML = 0;
160         } else {
161             m_responseXML = m_doc->implementation()->createDocument(0);
162             m_responseXML->open();
163             m_responseXML->setURL(m_url);
164             // FIXME: set Last-Modified and cookies (currently, those are only available for HTMLDocuments).
165             m_responseXML->write(String(m_responseText));
166             m_responseXML->finishParsing();
167             m_responseXML->close();
168             
169             if (!m_responseXML->wellFormed())
170                 m_responseXML = 0;
171         }
172         m_createdDocument = true;
173     }
174
175     return m_responseXML.get();
176 }
177
178 EventListener* XMLHttpRequest::onReadyStateChangeListener() const
179 {
180     return m_onReadyStateChangeListener.get();
181 }
182
183 void XMLHttpRequest::setOnReadyStateChangeListener(EventListener* eventListener)
184 {
185     m_onReadyStateChangeListener = eventListener;
186 }
187
188 EventListener* XMLHttpRequest::onLoadListener() const
189 {
190     return m_onLoadListener.get();
191 }
192
193 void XMLHttpRequest::setOnLoadListener(EventListener* eventListener)
194 {
195     m_onLoadListener = eventListener;
196 }
197
198 void XMLHttpRequest::addEventListener(const AtomicString& eventType, PassRefPtr<EventListener> eventListener, bool)
199 {
200     EventListenersMap::iterator iter = m_eventListeners.find(eventType.impl());
201     if (iter == m_eventListeners.end()) {
202         ListenerVector listeners;
203         listeners.append(eventListener);
204         m_eventListeners.add(eventType.impl(), listeners);
205     } else {
206         ListenerVector& listeners = iter->second;
207         for (ListenerVector::iterator listenerIter = listeners.begin(); listenerIter != listeners.end(); ++listenerIter)
208             if (*listenerIter == eventListener)
209                 return;
210         
211         listeners.append(eventListener);
212         m_eventListeners.add(eventType.impl(), listeners);
213     }
214 }
215
216 void XMLHttpRequest::removeEventListener(const AtomicString& eventType, EventListener* eventListener, bool)
217 {
218     EventListenersMap::iterator iter = m_eventListeners.find(eventType.impl());
219     if (iter == m_eventListeners.end())
220         return;
221
222     ListenerVector& listeners = iter->second;
223     for (ListenerVector::const_iterator listenerIter = listeners.begin(); listenerIter != listeners.end(); ++listenerIter)
224         if (*listenerIter == eventListener) {
225             listeners.remove(listenerIter - listeners.begin());
226             return;
227         }
228 }
229
230 bool XMLHttpRequest::dispatchEvent(PassRefPtr<Event> evt, ExceptionCode& ec, bool /*tempEvent*/)
231 {
232     // FIXME: check for other error conditions enumerated in the spec.
233     if (evt->type().isEmpty()) {
234         ec = EventException::UNSPECIFIED_EVENT_TYPE_ERR;
235         return true;
236     }
237
238     ListenerVector listenersCopy = m_eventListeners.get(evt->type().impl());
239     for (ListenerVector::const_iterator listenerIter = listenersCopy.begin(); listenerIter != listenersCopy.end(); ++listenerIter) {
240         evt->setTarget(this);
241         evt->setCurrentTarget(this);
242         listenerIter->get()->handleEvent(evt.get(), false);
243     }
244
245     return !evt->defaultPrevented();
246 }
247
248 XMLHttpRequest::XMLHttpRequest(Document* d)
249     : m_doc(d)
250     , m_async(true)
251     , m_loader(0)
252     , m_state(Uninitialized)
253     , m_responseText("")
254     , m_createdDocument(false)
255     , m_aborted(false)
256 {
257     ASSERT(m_doc);
258     addToRequestsByDocument(m_doc, this);
259 }
260
261 XMLHttpRequest::~XMLHttpRequest()
262 {
263     if (m_doc)
264         removeFromRequestsByDocument(m_doc, this);
265 }
266
267 void XMLHttpRequest::changeState(XMLHttpRequestState newState)
268 {
269     if (m_state != newState) {
270         m_state = newState;
271         callReadyStateChangeListener();
272     }
273 }
274
275 void XMLHttpRequest::callReadyStateChangeListener()
276 {
277     if (!m_doc || !m_doc->frame())
278         return;
279
280     RefPtr<Event> evt = new Event(readystatechangeEvent, false, false);
281     if (m_onReadyStateChangeListener) {
282         evt->setTarget(this);
283         evt->setCurrentTarget(this);
284         m_onReadyStateChangeListener->handleEvent(evt.get(), false);
285     }
286
287     ExceptionCode ec = 0;
288     dispatchEvent(evt.release(), ec, false);
289     ASSERT(!ec);
290     
291     if (m_state == Loaded) {
292         evt = new Event(loadEvent, false, false);
293         if (m_onLoadListener) {
294             evt->setTarget(this);
295             evt->setCurrentTarget(this);
296             m_onLoadListener->handleEvent(evt.get(), false);
297         }
298         
299         dispatchEvent(evt, ec, false);
300         ASSERT(!ec);
301     }
302 }
303
304 bool XMLHttpRequest::urlMatchesDocumentDomain(const KURL& url) const
305 {
306     // a local file can load anything
307     if (m_doc->isAllowedToLoadLocalResources())
308         return true;
309
310     // but a remote document can only load from the same port on the server
311     KURL documentURL(m_doc->url());
312     if (documentURL.protocol().lower() == url.protocol().lower()
313             && documentURL.host().lower() == url.host().lower()
314             && documentURL.port() == url.port())
315         return true;
316
317     return false;
318 }
319
320 void XMLHttpRequest::open(const String& method, const KURL& url, bool async, ExceptionCode& ec)
321 {
322     internalAbort();
323     m_state = Uninitialized;
324     m_aborted = false;
325
326     // clear stuff from possible previous load
327     m_requestHeaders.clear();
328     m_response = ResourceResponse();
329     {
330         KJS::JSLock lock;
331         m_responseText = "";
332     }
333     m_createdDocument = false;
334     m_responseXML = 0;
335
336     ASSERT(m_state == Uninitialized);
337
338     if (!urlMatchesDocumentDomain(url)) {
339         ec = XMLHttpRequestException::PERMISSION_DENIED;
340         return;
341     }
342
343     if (!isValidToken(method)) {
344         ec = SYNTAX_ERR;
345         return;
346     }
347     
348     // Method names are case sensitive. But since Firefox uppercases method names it knows, we'll do the same.
349     String methodUpper(method.upper());
350     
351     if (methodUpper == "TRACE" || methodUpper == "TRACK" || methodUpper == "CONNECT") {
352         ec = XMLHttpRequestException::PERMISSION_DENIED;
353         return;
354     }
355
356     m_url = url;
357
358     if (methodUpper == "COPY" || methodUpper == "DELETE" || methodUpper == "GET" || methodUpper == "HEAD"
359         || methodUpper == "INDEX" || methodUpper == "LOCK" || methodUpper == "M-POST" || methodUpper == "MKCOL" || methodUpper == "MOVE"
360         || methodUpper == "OPTIONS" || methodUpper == "POST" || methodUpper == "PROPFIND" || methodUpper == "PROPPATCH" || methodUpper == "PUT" 
361         || methodUpper == "UNLOCK")
362         m_method = methodUpper;
363     else
364         m_method = method;
365
366     m_async = async;
367
368     ASSERT(!m_loader);
369     changeState(Open);
370 }
371
372 void XMLHttpRequest::open(const String& method, const KURL& url, bool async, const String& user, ExceptionCode& ec)
373 {
374     KURL urlWithCredentials(url);
375     urlWithCredentials.setUser(user);
376     
377     open(method, urlWithCredentials, async, ec);
378 }
379
380 void XMLHttpRequest::open(const String& method, const KURL& url, bool async, const String& user, const String& password, ExceptionCode& ec)
381 {
382     KURL urlWithCredentials(url);
383     urlWithCredentials.setUser(user);
384     urlWithCredentials.setPass(password);
385     
386     open(method, urlWithCredentials, async, ec);
387 }
388
389 void XMLHttpRequest::send(const String& body, ExceptionCode& ec)
390 {
391     if (!m_doc)
392         return;
393
394     if (m_state != Open || m_loader) {
395         ec = INVALID_STATE_ERR;
396         return;
397     }
398
399     m_aborted = false;
400
401     ResourceRequest request(m_url);
402     request.setHTTPMethod(m_method);
403
404     if (!body.isNull() && m_method != "GET" && m_method != "HEAD" && (m_url.protocol().lower() == "http" || m_url.protocol().lower() == "https")) {
405         String contentType = getRequestHeader("Content-Type");
406         if (contentType.isEmpty()) {
407             ExceptionCode ec = 0;
408             Settings* settings = m_doc->settings();
409             if (settings && settings->usesDashboardBackwardCompatibilityMode())
410                 setRequestHeader("Content-Type", "application/x-www-form-urlencoded", ec);
411             else
412                 setRequestHeader("Content-Type", "application/xml", ec);
413             ASSERT(ec == 0);
414         }
415
416         // FIXME: must use xmlEncoding for documents.
417         String charset = "UTF-8";
418       
419         TextEncoding m_encoding(charset);
420         if (!m_encoding.isValid()) // FIXME: report an error?
421             m_encoding = UTF8Encoding();
422
423         request.setHTTPBody(PassRefPtr<FormData>(new FormData(m_encoding.encode(body.characters(), body.length()))));
424     }
425
426     if (m_requestHeaders.size() > 0)
427         request.addHTTPHeaderFields(m_requestHeaders);
428
429     if (!m_async) {
430         Vector<char> data;
431         ResourceError error;
432         ResourceResponse response;
433
434         {
435             // avoid deadlock in case the loader wants to use JS on a background thread
436             KJS::JSLock::DropAllLocks dropLocks;
437             if (m_doc->frame()) 
438                 m_doc->frame()->loader()->loadResourceSynchronously(request, error, response, data);
439         }
440
441         m_loader = 0;
442         
443         // No exception for file:/// resources, see <rdar://problem/4962298>.
444         // Also, if we have an HTTP response, then it wasn't a network error in fact.
445         if (error.isNull() || request.url().isLocalFile() || response.httpStatusCode() > 0)
446             processSyncLoadResults(data, response);
447         else
448             ec = XMLHttpRequestException::NETWORK_ERR;
449
450         return;
451     }
452
453     // Neither this object nor the JavaScript wrapper should be deleted while
454     // a request is in progress because we need to keep the listeners alive,
455     // and they are referenced by the JavaScript wrapper.
456     ref();
457     {
458         KJS::JSLock lock;
459         gcProtectNullTolerant(ScriptInterpreter::getDOMObject(this));
460     }
461   
462     // SubresourceLoader::create can return null here, for example if we're no longer attached to a page.
463     // This is true while running onunload handlers.
464     // FIXME: We need to be able to send XMLHttpRequests from onunload, <http://bugs.webkit.org/show_bug.cgi?id=10904>.
465     // FIXME: Maybe create can return null for other reasons too?
466     // We need to keep content sniffing enabled for local files due to CFNetwork not providing a MIME type
467     // for local files otherwise, <rdar://problem/5671813>.
468     m_loader = SubresourceLoader::create(m_doc->frame(), this, request, false, true, request.url().isLocalFile());
469 }
470
471 void XMLHttpRequest::abort()
472 {
473     bool sendFlag = m_loader;
474
475     internalAbort();
476
477     // Clear headers as required by the spec
478     m_requestHeaders.clear();
479
480     if ((m_state <= Open && !sendFlag) || m_state == Loaded)
481         m_state = Uninitialized;
482      else {
483         ASSERT(!m_loader);
484         changeState(Loaded);
485         m_state = Uninitialized;
486     }
487 }
488
489 void XMLHttpRequest::internalAbort()
490 {
491     bool hadLoader = m_loader;
492
493     m_aborted = true;
494
495     if (hadLoader) {
496         m_loader->cancel();
497         m_loader = 0;
498     }
499
500     m_decoder = 0;
501
502     if (hadLoader)
503         dropProtection();
504 }
505
506 void XMLHttpRequest::dropProtection()        
507 {
508     {
509         KJS::JSLock lock;
510         KJS::JSValue* wrapper = ScriptInterpreter::getDOMObject(this);
511         KJS::gcUnprotectNullTolerant(wrapper);
512     
513         // the XHR object itself holds on to the responseText, and
514         // thus has extra cost even independent of any
515         // responseText or responseXML objects it has handed
516         // out. But it is protected from GC while loading, so this
517         // can't be recouped until the load is done, so only
518         // report the extra cost at that point.
519     
520         if (wrapper)
521             KJS::Collector::reportExtraMemoryCost(m_responseText.size() * 2);
522     }
523
524     deref();
525 }
526
527 void XMLHttpRequest::overrideMIMEType(const String& override)
528 {
529     m_mimeTypeOverride = override;
530 }
531     
532 void XMLHttpRequest::setRequestHeader(const String& name, const String& value, ExceptionCode& ec)
533 {
534     if (m_state != Open || m_loader) {
535         Settings* settings = m_doc ? m_doc->settings() : 0;
536         if (settings && settings->usesDashboardBackwardCompatibilityMode())
537             return;
538
539         ec = INVALID_STATE_ERR;
540         return;
541     }
542
543     if (!isValidToken(name) || !isValidHeaderValue(value)) {
544         ec = SYNTAX_ERR;
545         return;
546     }
547         
548     if (!canSetRequestHeader(name)) {
549         if (m_doc && m_doc->frame() && m_doc->frame()->page())
550             m_doc->frame()->page()->chrome()->addMessageToConsole(JSMessageSource, ErrorMessageLevel, "Refused to set unsafe header " + name, 1, String());
551         return;
552     }
553
554     if (!m_requestHeaders.contains(name)) {
555         m_requestHeaders.set(name, value);
556         return;
557     }
558     
559     String oldValue = m_requestHeaders.get(name);
560     m_requestHeaders.set(name, oldValue + ", " + value);
561 }
562
563 String XMLHttpRequest::getRequestHeader(const String& name) const
564 {
565     return m_requestHeaders.get(name);
566 }
567
568 String XMLHttpRequest::getAllResponseHeaders(ExceptionCode& ec) const
569 {
570     if (m_state < Receiving) {
571         ec = INVALID_STATE_ERR;
572         return "";
573     }
574
575     Vector<UChar> stringBuilder;
576     String separator(": ");
577
578     HTTPHeaderMap::const_iterator end = m_response.httpHeaderFields().end();
579     for (HTTPHeaderMap::const_iterator it = m_response.httpHeaderFields().begin(); it!= end; ++it) {
580         stringBuilder.append(it->first.characters(), it->first.length());
581         stringBuilder.append(separator.characters(), separator.length());
582         stringBuilder.append(it->second.characters(), it->second.length());
583         stringBuilder.append((UChar)'\r');
584         stringBuilder.append((UChar)'\n');
585     }
586
587     return String::adopt(stringBuilder);
588 }
589
590 String XMLHttpRequest::getResponseHeader(const String& name, ExceptionCode& ec) const
591 {
592     if (m_state < Receiving) {
593         ec = INVALID_STATE_ERR;
594         return "";
595     }
596
597     if (!isValidToken(name))
598         return "";
599
600     return m_response.httpHeaderField(name);
601 }
602
603 String XMLHttpRequest::responseMIMEType() const
604 {
605     String mimeType = extractMIMETypeFromMediaType(m_mimeTypeOverride);
606     if (mimeType.isEmpty()) {
607         if (m_response.isHTTP())
608             mimeType = extractMIMETypeFromMediaType(m_response.httpHeaderField("Content-Type"));
609         else
610             mimeType = m_response.mimeType();
611     }
612     if (mimeType.isEmpty())
613         mimeType = "text/xml";
614     
615     return mimeType;
616 }
617
618 bool XMLHttpRequest::responseIsXML() const
619 {
620     return DOMImplementation::isXMLMIMEType(responseMIMEType());
621 }
622
623 int XMLHttpRequest::getStatus(ExceptionCode& ec) const
624 {
625     if (m_state == Uninitialized)
626         return 0;
627     
628     if (m_response.httpStatusCode() == 0) {
629         if (m_state != Receiving && m_state != Loaded)
630             // status MUST be available in these states, but we don't get any headers from non-HTTP requests
631             ec = INVALID_STATE_ERR;
632     }
633
634     return m_response.httpStatusCode();
635 }
636
637 String XMLHttpRequest::getStatusText(ExceptionCode& ec) const
638 {
639     if (m_state == Uninitialized)
640         return "";
641     
642     if (m_response.httpStatusCode() == 0) {
643         if (m_state != Receiving && m_state != Loaded)
644             // statusText MUST be available in these states, but we don't get any headers from non-HTTP requests
645             ec = INVALID_STATE_ERR;
646         return String();
647     }
648
649     // FIXME: should try to preserve status text in response
650     return "OK";
651 }
652
653 void XMLHttpRequest::processSyncLoadResults(const Vector<char>& data, const ResourceResponse& response)
654 {
655     if (!urlMatchesDocumentDomain(response.url())) {
656         internalAbort();
657         return;
658     }
659
660     didReceiveResponse(0, response);
661     changeState(Sent);
662     if (m_aborted)
663         return;
664
665     const char* bytes = static_cast<const char*>(data.data());
666     int len = static_cast<int>(data.size());
667
668     didReceiveData(0, bytes, len);
669     if (m_aborted)
670         return;
671
672     didFinishLoading(0);
673 }
674
675 void XMLHttpRequest::didFail(SubresourceLoader* loader, const ResourceError&)
676 {
677     didFinishLoading(loader);
678 }
679
680 void XMLHttpRequest::didFinishLoading(SubresourceLoader* loader)
681 {
682     if (m_aborted)
683         return;
684         
685     ASSERT(loader == m_loader);
686
687     if (m_state < Sent)
688         changeState(Sent);
689
690     {
691         KJS::JSLock lock;
692         if (m_decoder)
693             m_responseText += m_decoder->flush();
694     }
695
696     bool hadLoader = m_loader;
697     m_loader = 0;
698
699     changeState(Loaded);
700     m_decoder = 0;
701
702     if (hadLoader)
703         dropProtection();
704 }
705
706 void XMLHttpRequest::willSendRequest(SubresourceLoader*, ResourceRequest& request, const ResourceResponse& redirectResponse)
707 {
708     if (!urlMatchesDocumentDomain(request.url()))
709         internalAbort();
710 }
711
712 void XMLHttpRequest::didReceiveResponse(SubresourceLoader*, const ResourceResponse& response)
713 {
714     m_response = response;
715     m_encoding = extractCharsetFromMediaType(m_mimeTypeOverride);
716     if (m_encoding.isEmpty())
717         m_encoding = response.textEncodingName();
718
719 }
720
721 void XMLHttpRequest::receivedCancellation(SubresourceLoader*, const AuthenticationChallenge& challenge)
722 {
723     m_response = challenge.failureResponse();
724 }
725
726 void XMLHttpRequest::didReceiveData(SubresourceLoader*, const char* data, int len)
727 {
728     if (m_state < Sent)
729         changeState(Sent);
730   
731     if (!m_decoder) {
732         if (!m_encoding.isEmpty())
733             m_decoder = new TextResourceDecoder("text/plain", m_encoding);
734         // allow TextResourceDecoder to look inside the m_response if it's XML or HTML
735         else if (responseIsXML())
736             m_decoder = new TextResourceDecoder("application/xml");
737         else if (responseMIMEType() == "text/html")
738             m_decoder = new TextResourceDecoder("text/html", "UTF-8");
739         else
740             m_decoder = new TextResourceDecoder("text/plain", "UTF-8");
741     }
742     if (len == 0)
743         return;
744
745     if (len == -1)
746         len = strlen(data);
747
748     String decoded = m_decoder->decode(data, len);
749
750     {
751         KJS::JSLock lock;
752         m_responseText += decoded;
753     }
754
755     if (!m_aborted) {
756         if (m_state != Receiving)
757             changeState(Receiving);
758         else
759             // Firefox calls readyStateChanged every time it receives data, 4449442
760             callReadyStateChangeListener();
761     }
762 }
763
764 void XMLHttpRequest::cancelRequests(Document* m_doc)
765 {
766     RequestsSet* requests = requestsByDocument().get(m_doc);
767     if (!requests)
768         return;
769     RequestsSet copy = *requests;
770     RequestsSet::const_iterator end = copy.end();
771     for (RequestsSet::const_iterator it = copy.begin(); it != end; ++it)
772         (*it)->internalAbort();
773 }
774
775 void XMLHttpRequest::detachRequests(Document* m_doc)
776 {
777     RequestsSet* requests = requestsByDocument().get(m_doc);
778     if (!requests)
779         return;
780     requestsByDocument().remove(m_doc);
781     RequestsSet::const_iterator end = requests->end();
782     for (RequestsSet::const_iterator it = requests->begin(); it != end; ++it) {
783         (*it)->m_doc = 0;
784         (*it)->internalAbort();
785     }
786     delete requests;
787 }
788
789 } // end namespace