source: webkit/trunk/WebCore/xml/XMLHttpRequest.cpp @ 28934

Last change on this file since 28934 was 28934, checked in by ap@webkit.org, 16 years ago

Reviewed by Oliver.

<rdar://problem/5629995> Incorrect display of Danish characters on web site.

Test: http/tests/xmlhttprequest/response-encoding.html

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