cd2033aea4b6fc40298ca8846ccccd45e260d5e3
[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     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     if (!canSetRequestHeader(name)) {
545         if (m_doc && m_doc->frame() && m_doc->frame()->page())
546             m_doc->frame()->page()->chrome()->addMessageToConsole(JSMessageSource, ErrorMessageLevel, "Refused to set unsafe header " + name, 1, String());
547         return;
548     }
549
550     if (!m_requestHeaders.contains(name)) {
551         m_requestHeaders.set(name, value);
552         return;
553     }
554     
555     String oldValue = m_requestHeaders.get(name);
556     m_requestHeaders.set(name, oldValue + ", " + value);
557 }
558
559 String XMLHttpRequest::getRequestHeader(const String& name) const
560 {
561     return m_requestHeaders.get(name);
562 }
563
564 String XMLHttpRequest::getAllResponseHeaders(ExceptionCode& ec) const
565 {
566     if (m_state < Receiving) {
567         ec = INVALID_STATE_ERR;
568         return "";
569     }
570
571     Vector<UChar> stringBuilder;
572     String separator(": ");
573
574     HTTPHeaderMap::const_iterator end = m_response.httpHeaderFields().end();
575     for (HTTPHeaderMap::const_iterator it = m_response.httpHeaderFields().begin(); it!= end; ++it) {
576         stringBuilder.append(it->first.characters(), it->first.length());
577         stringBuilder.append(separator.characters(), separator.length());
578         stringBuilder.append(it->second.characters(), it->second.length());
579         stringBuilder.append((UChar)'\r');
580         stringBuilder.append((UChar)'\n');
581     }
582
583     return String::adopt(stringBuilder);
584 }
585
586 String XMLHttpRequest::getResponseHeader(const String& name, ExceptionCode& ec) const
587 {
588     if (m_state < Receiving) {
589         ec = INVALID_STATE_ERR;
590         return "";
591     }
592
593     if (!isValidToken(name))
594         return "";
595
596     return m_response.httpHeaderField(name);
597 }
598
599 String XMLHttpRequest::responseMIMEType() const
600 {
601     String mimeType = extractMIMETypeFromMediaType(m_mimeTypeOverride);
602     if (mimeType.isEmpty()) {
603         if (m_response.isHTTP())
604             mimeType = extractMIMETypeFromMediaType(m_response.httpHeaderField("Content-Type"));
605         else
606             mimeType = m_response.mimeType();
607     }
608     if (mimeType.isEmpty())
609         mimeType = "text/xml";
610     
611     return mimeType;
612 }
613
614 bool XMLHttpRequest::responseIsXML() const
615 {
616     return DOMImplementation::isXMLMIMEType(responseMIMEType());
617 }
618
619 int XMLHttpRequest::getStatus(ExceptionCode& ec) const
620 {
621     if (m_state == Uninitialized)
622         return 0;
623     
624     if (m_response.httpStatusCode() == 0) {
625         if (m_state != Receiving && m_state != Loaded)
626             // status MUST be available in these states, but we don't get any headers from non-HTTP requests
627             ec = INVALID_STATE_ERR;
628     }
629
630     return m_response.httpStatusCode();
631 }
632
633 String XMLHttpRequest::getStatusText(ExceptionCode& ec) const
634 {
635     if (m_state == Uninitialized)
636         return "";
637     
638     if (m_response.httpStatusCode() == 0) {
639         if (m_state != Receiving && m_state != Loaded)
640             // statusText MUST be available in these states, but we don't get any headers from non-HTTP requests
641             ec = INVALID_STATE_ERR;
642         return String();
643     }
644
645     // FIXME: should try to preserve status text in response
646     return "OK";
647 }
648
649 void XMLHttpRequest::processSyncLoadResults(const Vector<char>& data, const ResourceResponse& response)
650 {
651     if (!urlMatchesDocumentDomain(response.url())) {
652         internalAbort();
653         return;
654     }
655
656     didReceiveResponse(0, response);
657     changeState(Sent);
658     if (m_aborted)
659         return;
660
661     const char* bytes = static_cast<const char*>(data.data());
662     int len = static_cast<int>(data.size());
663
664     didReceiveData(0, bytes, len);
665     if (m_aborted)
666         return;
667
668     didFinishLoading(0);
669 }
670
671 void XMLHttpRequest::didFail(SubresourceLoader* loader, const ResourceError&)
672 {
673     didFinishLoading(loader);
674 }
675
676 void XMLHttpRequest::didFinishLoading(SubresourceLoader* loader)
677 {
678     if (m_aborted)
679         return;
680         
681     ASSERT(loader == m_loader);
682
683     if (m_state < Sent)
684         changeState(Sent);
685
686     {
687         KJS::JSLock lock;
688         if (m_decoder)
689             m_responseText += m_decoder->flush();
690     }
691
692     bool hadLoader = m_loader;
693     m_loader = 0;
694
695     changeState(Loaded);
696     m_decoder = 0;
697
698     if (hadLoader)
699         dropProtection();
700 }
701
702 void XMLHttpRequest::willSendRequest(SubresourceLoader*, ResourceRequest& request, const ResourceResponse& redirectResponse)
703 {
704     if (!urlMatchesDocumentDomain(request.url()))
705         internalAbort();
706 }
707
708 void XMLHttpRequest::didReceiveResponse(SubresourceLoader*, const ResourceResponse& response)
709 {
710     m_response = response;
711     m_encoding = extractCharsetFromMediaType(m_mimeTypeOverride);
712     if (m_encoding.isEmpty())
713         m_encoding = response.textEncodingName();
714
715 }
716
717 void XMLHttpRequest::receivedCancellation(SubresourceLoader*, const AuthenticationChallenge& challenge)
718 {
719     m_response = challenge.failureResponse();
720 }
721
722 void XMLHttpRequest::didReceiveData(SubresourceLoader*, const char* data, int len)
723 {
724     if (m_state < Sent)
725         changeState(Sent);
726   
727     if (!m_decoder) {
728         if (!m_encoding.isEmpty())
729             m_decoder = new TextResourceDecoder("text/plain", m_encoding);
730         // allow TextResourceDecoder to look inside the m_response if it's XML or HTML
731         else if (responseIsXML())
732             m_decoder = new TextResourceDecoder("application/xml");
733         else if (responseMIMEType() == "text/html")
734             m_decoder = new TextResourceDecoder("text/html", "UTF-8");
735         else
736             m_decoder = new TextResourceDecoder("text/plain", "UTF-8");
737     }
738     if (len == 0)
739         return;
740
741     if (len == -1)
742         len = strlen(data);
743
744     String decoded = m_decoder->decode(data, len);
745
746     {
747         KJS::JSLock lock;
748         m_responseText += decoded;
749     }
750
751     if (!m_aborted) {
752         if (m_state != Receiving)
753             changeState(Receiving);
754         else
755             // Firefox calls readyStateChanged every time it receives data, 4449442
756             callReadyStateChangeListener();
757     }
758 }
759
760 void XMLHttpRequest::cancelRequests(Document* m_doc)
761 {
762     RequestsSet* requests = requestsByDocument().get(m_doc);
763     if (!requests)
764         return;
765     RequestsSet copy = *requests;
766     RequestsSet::const_iterator end = copy.end();
767     for (RequestsSet::const_iterator it = copy.begin(); it != end; ++it)
768         (*it)->internalAbort();
769 }
770
771 void XMLHttpRequest::detachRequests(Document* m_doc)
772 {
773     RequestsSet* requests = requestsByDocument().get(m_doc);
774     if (!requests)
775         return;
776     requestsByDocument().remove(m_doc);
777     RequestsSet::const_iterator end = requests->end();
778     for (RequestsSet::const_iterator it = requests->begin(); it != end; ++it) {
779         (*it)->m_doc = 0;
780         (*it)->internalAbort();
781     }
782     delete requests;
783 }
784
785 } // end namespace