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