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, 2008 Julien Chaffraix <jchaffraix@webkit.org>
5 *  Copyright (C) 2008 David Levin <levin@chromium.org>
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 "ArrayBuffer.h"
26#include "Blob.h"
27#include "MemoryCache.h"
28#include "CrossOriginAccessControl.h"
29#include "DOMFormData.h"
30#include "DOMImplementation.h"
31#include "Document.h"
32#include "Event.h"
33#include "EventException.h"
34#include "EventListener.h"
35#include "EventNames.h"
36#include "File.h"
37#include "HTTPParsers.h"
38#include "InspectorInstrumentation.h"
39#include "ResourceError.h"
40#include "ResourceRequest.h"
41#include "ScriptCallStack.h"
42#include "SecurityOrigin.h"
43#include "Settings.h"
44#include "SharedBuffer.h"
45#include "TextResourceDecoder.h"
46#include "ThreadableLoader.h"
47#include "XMLHttpRequestException.h"
48#include "XMLHttpRequestProgressEvent.h"
49#include "XMLHttpRequestUpload.h"
50#include "markup.h"
51#include <wtf/text/CString.h>
52#include <wtf/StdLibExtras.h>
53#include <wtf/RefCountedLeakCounter.h>
54#include <wtf/UnusedParam.h>
55
56#if USE(JSC)
57#include "JSDOMBinding.h"
58#include "JSDOMWindow.h"
59#include <heap/Strong.h>
60#include <runtime/JSLock.h>
61#endif
62
63namespace WebCore {
64
65#ifndef NDEBUG
66static WTF::RefCountedLeakCounter xmlHttpRequestCounter("XMLHttpRequest");
67#endif
68
69struct XMLHttpRequestStaticData {
70    WTF_MAKE_NONCOPYABLE(XMLHttpRequestStaticData); WTF_MAKE_FAST_ALLOCATED;
71public:
72    XMLHttpRequestStaticData();
73    String m_proxyHeaderPrefix;
74    String m_secHeaderPrefix;
75    HashSet<String, CaseFoldingHash> m_forbiddenRequestHeaders;
76};
77
78XMLHttpRequestStaticData::XMLHttpRequestStaticData()
79    : m_proxyHeaderPrefix("proxy-")
80    , m_secHeaderPrefix("sec-")
81{
82    m_forbiddenRequestHeaders.add("accept-charset");
83    m_forbiddenRequestHeaders.add("accept-encoding");
84    m_forbiddenRequestHeaders.add("access-control-request-headers");
85    m_forbiddenRequestHeaders.add("access-control-request-method");
86    m_forbiddenRequestHeaders.add("connection");
87    m_forbiddenRequestHeaders.add("content-length");
88    m_forbiddenRequestHeaders.add("content-transfer-encoding");
89    m_forbiddenRequestHeaders.add("cookie");
90    m_forbiddenRequestHeaders.add("cookie2");
91    m_forbiddenRequestHeaders.add("date");
92    m_forbiddenRequestHeaders.add("expect");
93    m_forbiddenRequestHeaders.add("host");
94    m_forbiddenRequestHeaders.add("keep-alive");
95    m_forbiddenRequestHeaders.add("origin");
96    m_forbiddenRequestHeaders.add("referer");
97    m_forbiddenRequestHeaders.add("te");
98    m_forbiddenRequestHeaders.add("trailer");
99    m_forbiddenRequestHeaders.add("transfer-encoding");
100    m_forbiddenRequestHeaders.add("upgrade");
101    m_forbiddenRequestHeaders.add("user-agent");
102    m_forbiddenRequestHeaders.add("via");
103}
104
105// Determines if a string is a valid token, as defined by
106// "token" in section 2.2 of RFC 2616.
107static bool isValidToken(const String& name)
108{
109    unsigned length = name.length();
110    for (unsigned i = 0; i < length; i++) {
111        UChar c = name[i];
112
113        if (c >= 127 || c <= 32)
114            return false;
115
116        if (c == '(' || c == ')' || c == '<' || c == '>' || c == '@' ||
117            c == ',' || c == ';' || c == ':' || c == '\\' || c == '\"' ||
118            c == '/' || c == '[' || c == ']' || c == '?' || c == '=' ||
119            c == '{' || c == '}')
120            return false;
121    }
122
123    return length > 0;
124}
125
126static bool isValidHeaderValue(const String& name)
127{
128    // FIXME: This should really match name against
129    // field-value in section 4.2 of RFC 2616.
130
131    return !name.contains('\r') && !name.contains('\n');
132}
133
134static bool isSetCookieHeader(const AtomicString& name)
135{
136    return equalIgnoringCase(name, "set-cookie") || equalIgnoringCase(name, "set-cookie2");
137}
138
139static void replaceCharsetInMediaType(String& mediaType, const String& charsetValue)
140{
141    unsigned int pos = 0, len = 0;
142
143    findCharsetInMediaType(mediaType, pos, len);
144
145    if (!len) {
146        // When no charset found, do nothing.
147        return;
148    }
149
150    // Found at least one existing charset, replace all occurrences with new charset.
151    while (len) {
152        mediaType.replace(pos, len, charsetValue);
153        unsigned int start = pos + charsetValue.length();
154        findCharsetInMediaType(mediaType, pos, len, start);
155    }
156}
157
158static const XMLHttpRequestStaticData* staticData = 0;
159
160static const XMLHttpRequestStaticData* createXMLHttpRequestStaticData()
161{
162    staticData = new XMLHttpRequestStaticData;
163    return staticData;
164}
165
166static const XMLHttpRequestStaticData* initializeXMLHttpRequestStaticData()
167{
168    // Uses dummy to avoid warnings about an unused variable.
169    AtomicallyInitializedStatic(const XMLHttpRequestStaticData*, dummy = createXMLHttpRequestStaticData());
170    return dummy;
171}
172
173XMLHttpRequest::XMLHttpRequest(ScriptExecutionContext* context)
174    : ActiveDOMObject(context, this)
175    , m_async(true)
176    , m_includeCredentials(false)
177    , m_state(UNSENT)
178    , m_createdDocument(false)
179    , m_error(false)
180    , m_uploadEventsAllowed(true)
181    , m_uploadComplete(false)
182    , m_sameOriginRequest(true)
183    , m_receivedLength(0)
184    , m_lastSendLineNumber(0)
185    , m_exceptionCode(0)
186    , m_progressEventThrottle(this)
187    , m_responseTypeCode(ResponseTypeDefault)
188{
189    initializeXMLHttpRequestStaticData();
190#ifndef NDEBUG
191    xmlHttpRequestCounter.increment();
192#endif
193}
194
195XMLHttpRequest::~XMLHttpRequest()
196{
197    if (m_upload)
198        m_upload->disconnectXMLHttpRequest();
199
200#ifndef NDEBUG
201    xmlHttpRequestCounter.decrement();
202#endif
203}
204
205Document* XMLHttpRequest::document() const
206{
207    ASSERT(scriptExecutionContext()->isDocument());
208    return static_cast<Document*>(scriptExecutionContext());
209}
210
211#if ENABLE(DASHBOARD_SUPPORT)
212bool XMLHttpRequest::usesDashboardBackwardCompatibilityMode() const
213{
214    if (scriptExecutionContext()->isWorkerContext())
215        return false;
216    Settings* settings = document()->settings();
217    return settings && settings->usesDashboardBackwardCompatibilityMode();
218}
219#endif
220
221XMLHttpRequest::State XMLHttpRequest::readyState() const
222{
223    return m_state;
224}
225
226String XMLHttpRequest::responseText(ExceptionCode& ec)
227{
228    if (responseTypeCode() != ResponseTypeDefault && responseTypeCode() != ResponseTypeText) {
229        ec = INVALID_STATE_ERR;
230        return "";
231    }
232    return m_responseBuilder.toStringPreserveCapacity();
233}
234
235Document* XMLHttpRequest::responseXML(ExceptionCode& ec)
236{
237    if (responseTypeCode() != ResponseTypeDefault && responseTypeCode() != ResponseTypeText && responseTypeCode() != ResponseTypeDocument) {
238        ec = INVALID_STATE_ERR;
239        return 0;
240    }
241
242    if (m_state != DONE)
243        return 0;
244
245    if (!m_createdDocument) {
246        if ((m_response.isHTTP() && !responseIsXML()) || scriptExecutionContext()->isWorkerContext()) {
247            // The W3C spec requires this.
248            m_responseXML = 0;
249        } else {
250            m_responseXML = Document::create(0, m_url);
251            // FIXME: Set Last-Modified.
252            m_responseXML->setContent(m_responseBuilder.toStringPreserveCapacity());
253            m_responseXML->setSecurityOrigin(document()->securityOrigin());
254            if (!m_responseXML->wellFormed())
255                m_responseXML = 0;
256        }
257        m_createdDocument = true;
258    }
259
260    return m_responseXML.get();
261}
262
263#if ENABLE(XHR_RESPONSE_BLOB)
264Blob* XMLHttpRequest::responseBlob(ExceptionCode& ec) const
265{
266    if (responseTypeCode() != ResponseTypeBlob) {
267        ec = INVALID_STATE_ERR;
268        return 0;
269    }
270    return m_responseBlob.get();
271}
272#endif
273
274ArrayBuffer* XMLHttpRequest::responseArrayBuffer(ExceptionCode& ec)
275{
276    if (m_responseTypeCode != ResponseTypeArrayBuffer) {
277        ec = INVALID_STATE_ERR;
278        return 0;
279    }
280
281    if (m_state != DONE)
282        return 0;
283
284    if (!m_responseArrayBuffer.get() && m_binaryResponseBuilder.get() && m_binaryResponseBuilder->size() > 0) {
285        m_responseArrayBuffer = ArrayBuffer::create(const_cast<char*>(m_binaryResponseBuilder->data()), static_cast<unsigned>(m_binaryResponseBuilder->size()));
286        m_binaryResponseBuilder.clear();
287    }
288
289    if (m_responseArrayBuffer.get())
290        return m_responseArrayBuffer.get();
291
292    return 0;
293}
294
295void XMLHttpRequest::setResponseType(const String& responseType, ExceptionCode& ec)
296{
297    if (m_state != OPENED || m_loader) {
298        ec = INVALID_STATE_ERR;
299        return;
300    }
301
302    if (responseType == "")
303        m_responseTypeCode = ResponseTypeDefault;
304    else if (responseType == "text")
305        m_responseTypeCode = ResponseTypeText;
306    else if (responseType == "document")
307        m_responseTypeCode = ResponseTypeDocument;
308    else if (responseType == "blob") {
309#if ENABLE(XHR_RESPONSE_BLOB)
310        m_responseTypeCode = ResponseTypeBlob;
311#endif
312    } else if (responseType == "arraybuffer") {
313        m_responseTypeCode = ResponseTypeArrayBuffer;
314    } else
315        ec = SYNTAX_ERR;
316}
317
318String XMLHttpRequest::responseType()
319{
320    switch (m_responseTypeCode) {
321    case ResponseTypeDefault:
322        return "";
323    case ResponseTypeText:
324        return "text";
325    case ResponseTypeDocument:
326        return "document";
327    case ResponseTypeBlob:
328        return "blob";
329    case ResponseTypeArrayBuffer:
330        return "arraybuffer";
331    }
332    return "";
333}
334
335XMLHttpRequestUpload* XMLHttpRequest::upload()
336{
337    if (!m_upload)
338        m_upload = XMLHttpRequestUpload::create(this);
339    return m_upload.get();
340}
341
342void XMLHttpRequest::changeState(State newState)
343{
344    if (m_state != newState) {
345        m_state = newState;
346        callReadyStateChangeListener();
347    }
348}
349
350void XMLHttpRequest::callReadyStateChangeListener()
351{
352    if (!scriptExecutionContext())
353        return;
354
355    InspectorInstrumentationCookie cookie = InspectorInstrumentation::willChangeXHRReadyState(scriptExecutionContext(), this);
356
357    if (m_async || (m_state <= OPENED || m_state == DONE))
358        m_progressEventThrottle.dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().readystatechangeEvent), m_state == DONE ? FlushProgressEvent : DoNotFlushProgressEvent);
359
360    InspectorInstrumentation::didChangeXHRReadyState(cookie);
361
362    if (m_state == DONE && !m_error) {
363        InspectorInstrumentationCookie cookie = InspectorInstrumentation::willLoadXHR(scriptExecutionContext(), this);
364        m_progressEventThrottle.dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().loadEvent));
365        InspectorInstrumentation::didLoadXHR(cookie);
366    }
367}
368
369void XMLHttpRequest::setWithCredentials(bool value, ExceptionCode& ec)
370{
371    if (m_state != OPENED || m_loader) {
372        ec = INVALID_STATE_ERR;
373        return;
374    }
375
376    m_includeCredentials = value;
377}
378
379#if ENABLE(XHR_RESPONSE_BLOB)
380void XMLHttpRequest::setAsBlob(bool value, ExceptionCode& ec)
381{
382    if (m_state != OPENED || m_loader) {
383        ec = INVALID_STATE_ERR;
384        return;
385    }
386
387    m_responseTypeCode = value ? ResponseTypeBlob : ResponseTypeDefault;
388}
389#endif
390
391void XMLHttpRequest::open(const String& method, const KURL& url, ExceptionCode& ec)
392{
393    open(method, url, true, ec);
394}
395
396void XMLHttpRequest::open(const String& method, const KURL& url, bool async, ExceptionCode& ec)
397{
398    internalAbort();
399    State previousState = m_state;
400    m_state = UNSENT;
401    m_error = false;
402    m_responseTypeCode = ResponseTypeDefault;
403    m_uploadComplete = false;
404
405    // clear stuff from possible previous load
406    clearResponse();
407    clearRequest();
408
409    ASSERT(m_state == UNSENT);
410
411    if (!isValidToken(method)) {
412        ec = SYNTAX_ERR;
413        return;
414    }
415
416    // Method names are case sensitive. But since Firefox uppercases method names it knows, we'll do the same.
417    String methodUpper(method.upper());
418
419    if (methodUpper == "TRACE" || methodUpper == "TRACK" || methodUpper == "CONNECT") {
420        ec = SECURITY_ERR;
421        return;
422    }
423
424    m_url = url;
425
426    if (methodUpper == "COPY" || methodUpper == "DELETE" || methodUpper == "GET" || methodUpper == "HEAD"
427        || methodUpper == "INDEX" || methodUpper == "LOCK" || methodUpper == "M-POST" || methodUpper == "MKCOL" || methodUpper == "MOVE"
428        || methodUpper == "OPTIONS" || methodUpper == "POST" || methodUpper == "PROPFIND" || methodUpper == "PROPPATCH" || methodUpper == "PUT"
429        || methodUpper == "UNLOCK")
430        m_method = methodUpper;
431    else
432        m_method = method;
433
434    m_async = async;
435
436    ASSERT(!m_loader);
437
438    // Check previous state to avoid dispatching readyState event
439    // when calling open several times in a row.
440    if (previousState != OPENED)
441        changeState(OPENED);
442    else
443        m_state = OPENED;
444}
445
446void XMLHttpRequest::open(const String& method, const KURL& url, bool async, const String& user, ExceptionCode& ec)
447{
448    KURL urlWithCredentials(url);
449    urlWithCredentials.setUser(user);
450
451    open(method, urlWithCredentials, async, ec);
452}
453
454void XMLHttpRequest::open(const String& method, const KURL& url, bool async, const String& user, const String& password, ExceptionCode& ec)
455{
456    KURL urlWithCredentials(url);
457    urlWithCredentials.setUser(user);
458    urlWithCredentials.setPass(password);
459
460    open(method, urlWithCredentials, async, ec);
461}
462
463bool XMLHttpRequest::initSend(ExceptionCode& ec)
464{
465    if (!scriptExecutionContext())
466        return false;
467
468    if (m_state != OPENED || m_loader) {
469        ec = INVALID_STATE_ERR;
470        return false;
471    }
472
473    m_error = false;
474    return true;
475}
476
477void XMLHttpRequest::send(ExceptionCode& ec)
478{
479    send(String(), ec);
480}
481
482void XMLHttpRequest::send(Document* document, ExceptionCode& ec)
483{
484    ASSERT(document);
485
486    if (!initSend(ec))
487        return;
488
489    if (m_method != "GET" && m_method != "HEAD" && m_url.protocolInHTTPFamily()) {
490        String contentType = getRequestHeader("Content-Type");
491        if (contentType.isEmpty()) {
492#if ENABLE(DASHBOARD_SUPPORT)
493            if (usesDashboardBackwardCompatibilityMode())
494                setRequestHeaderInternal("Content-Type", "application/x-www-form-urlencoded");
495            else
496#endif
497                // FIXME: this should include the charset used for encoding.
498                setRequestHeaderInternal("Content-Type", "application/xml");
499        }
500
501        // FIXME: According to XMLHttpRequest Level 2, this should use the Document.innerHTML algorithm
502        // from the HTML5 specification to serialize the document.
503        String body = createMarkup(document);
504
505        // FIXME: this should use value of document.inputEncoding to determine the encoding to use.
506        TextEncoding encoding = UTF8Encoding();
507        m_requestEntityBody = FormData::create(encoding.encode(body.characters(), body.length(), EntitiesForUnencodables));
508        if (m_upload)
509            m_requestEntityBody->setAlwaysStream(true);
510    }
511
512    createRequest(ec);
513}
514
515void XMLHttpRequest::send(const String& body, ExceptionCode& ec)
516{
517    if (!initSend(ec))
518        return;
519
520    if (!body.isNull() && m_method != "GET" && m_method != "HEAD" && m_url.protocolInHTTPFamily()) {
521        String contentType = getRequestHeader("Content-Type");
522        if (contentType.isEmpty()) {
523#if ENABLE(DASHBOARD_SUPPORT)
524            if (usesDashboardBackwardCompatibilityMode())
525                setRequestHeaderInternal("Content-Type", "application/x-www-form-urlencoded");
526            else
527#endif
528                setRequestHeaderInternal("Content-Type", "application/xml");
529        } else {
530            replaceCharsetInMediaType(contentType, "UTF-8");
531            m_requestHeaders.set("Content-Type", contentType);
532        }
533
534        m_requestEntityBody = FormData::create(UTF8Encoding().encode(body.characters(), body.length(), EntitiesForUnencodables));
535        if (m_upload)
536            m_requestEntityBody->setAlwaysStream(true);
537    }
538
539    createRequest(ec);
540}
541
542void XMLHttpRequest::send(Blob* body, ExceptionCode& ec)
543{
544    if (!initSend(ec))
545        return;
546
547    if (m_method != "GET" && m_method != "HEAD" && m_url.protocolInHTTPFamily()) {
548        // FIXME: Should we set a Content-Type if one is not set.
549        // FIXME: add support for uploading bundles.
550        m_requestEntityBody = FormData::create();
551        if (body->isFile())
552            m_requestEntityBody->appendFile(static_cast<File*>(body)->path());
553#if ENABLE(BLOB)
554        else
555            m_requestEntityBody->appendBlob(body->url());
556#endif
557    }
558
559    createRequest(ec);
560}
561
562void XMLHttpRequest::send(DOMFormData* body, ExceptionCode& ec)
563{
564    if (!initSend(ec))
565        return;
566
567    if (m_method != "GET" && m_method != "HEAD" && m_url.protocolInHTTPFamily()) {
568        m_requestEntityBody = FormData::createMultiPart(*(static_cast<FormDataList*>(body)), body->encoding(), document());
569
570        // We need to ask the client to provide the generated file names if needed. When FormData fills the element
571        // for the file, it could set a flag to use the generated file name, i.e. a package file on Mac.
572        m_requestEntityBody->generateFiles(document());
573
574        String contentType = getRequestHeader("Content-Type");
575        if (contentType.isEmpty()) {
576            contentType = "multipart/form-data; boundary=";
577            contentType += m_requestEntityBody->boundary().data();
578            setRequestHeaderInternal("Content-Type", contentType);
579        }
580    }
581
582    createRequest(ec);
583}
584
585void XMLHttpRequest::send(ArrayBuffer* body, ExceptionCode& ec)
586{
587    if (!initSend(ec))
588        return;
589
590    if (m_method != "GET" && m_method != "HEAD" && m_url.protocolInHTTPFamily()) {
591        m_requestEntityBody = FormData::create(body->data(), body->byteLength());
592        if (m_upload)
593            m_requestEntityBody->setAlwaysStream(true);
594    }
595
596    createRequest(ec);
597}
598
599void XMLHttpRequest::createRequest(ExceptionCode& ec)
600{
601#if ENABLE(BLOB)
602    // Only GET request is supported for blob URL.
603    if (m_url.protocolIs("blob") && m_method != "GET") {
604        ec = XMLHttpRequestException::NETWORK_ERR;
605        return;
606    }
607#endif
608
609    // The presence of upload event listeners forces us to use preflighting because POSTing to an URL that does not
610    // permit cross origin requests should look exactly like POSTing to an URL that does not respond at all.
611    // Also, only async requests support upload progress events.
612    bool uploadEvents = false;
613    if (m_async) {
614        m_progressEventThrottle.dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().loadstartEvent));
615        if (m_requestEntityBody && m_upload) {
616            uploadEvents = m_upload->hasEventListeners();
617            m_upload->dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().loadstartEvent));
618        }
619    }
620
621    m_sameOriginRequest = scriptExecutionContext()->securityOrigin()->canRequest(m_url);
622
623    // We also remember whether upload events should be allowed for this request in case the upload listeners are
624    // added after the request is started.
625    m_uploadEventsAllowed = m_sameOriginRequest || uploadEvents || !isSimpleCrossOriginAccessRequest(m_method, m_requestHeaders);
626
627    ResourceRequest request(m_url);
628    request.setHTTPMethod(m_method);
629
630    if (m_requestEntityBody) {
631        ASSERT(m_method != "GET");
632        ASSERT(m_method != "HEAD");
633        request.setHTTPBody(m_requestEntityBody.release());
634    }
635
636    if (m_requestHeaders.size() > 0)
637        request.addHTTPHeaderFields(m_requestHeaders);
638
639    ThreadableLoaderOptions options;
640    options.sendLoadCallbacks = true;
641    options.sniffContent = false;
642    options.forcePreflight = uploadEvents;
643    options.allowCredentials = m_sameOriginRequest || m_includeCredentials;
644    options.crossOriginRequestPolicy = UseAccessControl;
645
646    m_exceptionCode = 0;
647    m_error = false;
648
649    if (m_async) {
650        if (m_upload)
651            request.setReportUploadProgress(true);
652
653        // ThreadableLoader::create can return null here, for example if we're no longer attached to a page.
654        // This is true while running onunload handlers.
655        // FIXME: Maybe we need to be able to send XMLHttpRequests from onunload, <http://bugs.webkit.org/show_bug.cgi?id=10904>.
656        // FIXME: Maybe create() can return null for other reasons too?
657        m_loader = ThreadableLoader::create(scriptExecutionContext(), this, request, options);
658        if (m_loader) {
659            // Neither this object nor the JavaScript wrapper should be deleted while
660            // a request is in progress because we need to keep the listeners alive,
661            // and they are referenced by the JavaScript wrapper.
662            setPendingActivity(this);
663        }
664    } else
665        ThreadableLoader::loadResourceSynchronously(scriptExecutionContext(), request, *this, options);
666
667    if (!m_exceptionCode && m_error)
668        m_exceptionCode = XMLHttpRequestException::NETWORK_ERR;
669    ec = m_exceptionCode;
670}
671
672void XMLHttpRequest::abort()
673{
674    // internalAbort() calls dropProtection(), which may release the last reference.
675    RefPtr<XMLHttpRequest> protect(this);
676
677    bool sendFlag = m_loader;
678
679    internalAbort();
680
681    clearResponseBuffers();
682
683    // Clear headers as required by the spec
684    m_requestHeaders.clear();
685
686    if ((m_state <= OPENED && !sendFlag) || m_state == DONE)
687        m_state = UNSENT;
688    else {
689        ASSERT(!m_loader);
690        changeState(DONE);
691        m_state = UNSENT;
692    }
693
694    m_progressEventThrottle.dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().abortEvent));
695    if (!m_uploadComplete) {
696        m_uploadComplete = true;
697        if (m_upload && m_uploadEventsAllowed)
698            m_upload->dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().abortEvent));
699    }
700}
701
702void XMLHttpRequest::internalAbort()
703{
704    bool hadLoader = m_loader;
705
706    m_error = true;
707
708    // FIXME: when we add the support for multi-part XHR, we will have to think be careful with this initialization.
709    m_receivedLength = 0;
710
711    if (hadLoader) {
712        m_loader->cancel();
713        m_loader = 0;
714    }
715
716    m_decoder = 0;
717
718    if (hadLoader)
719        dropProtection();
720}
721
722void XMLHttpRequest::clearResponse()
723{
724    m_response = ResourceResponse();
725    clearResponseBuffers();
726}
727
728void XMLHttpRequest::clearResponseBuffers()
729{
730    m_responseBuilder.clear();
731    m_createdDocument = false;
732    m_responseXML = 0;
733#if ENABLE(XHR_RESPONSE_BLOB)
734    m_responseBlob = 0;
735#endif
736    m_binaryResponseBuilder.clear();
737    m_responseArrayBuffer.clear();
738}
739
740void XMLHttpRequest::clearRequest()
741{
742    m_requestHeaders.clear();
743    m_requestEntityBody = 0;
744}
745
746void XMLHttpRequest::genericError()
747{
748    clearResponse();
749    clearRequest();
750    m_error = true;
751
752    changeState(DONE);
753}
754
755void XMLHttpRequest::networkError()
756{
757    genericError();
758    m_progressEventThrottle.dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().errorEvent));
759    if (!m_uploadComplete) {
760        m_uploadComplete = true;
761        if (m_upload && m_uploadEventsAllowed)
762            m_upload->dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().errorEvent));
763    }
764    internalAbort();
765}
766
767void XMLHttpRequest::abortError()
768{
769    genericError();
770    m_progressEventThrottle.dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().abortEvent));
771    if (!m_uploadComplete) {
772        m_uploadComplete = true;
773        if (m_upload && m_uploadEventsAllowed)
774            m_upload->dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().abortEvent));
775    }
776}
777
778void XMLHttpRequest::dropProtection()
779{
780#if USE(JSC)
781    // The XHR object itself holds on to the responseText, and
782    // thus has extra cost even independent of any
783    // responseText or responseXML objects it has handed
784    // out. But it is protected from GC while loading, so this
785    // can't be recouped until the load is done, so only
786    // report the extra cost at that point.
787    JSC::JSLock lock(JSC::SilenceAssertionsOnly);
788    JSC::JSGlobalData* globalData = scriptExecutionContext()->globalData();
789    globalData->heap.reportExtraMemoryCost(m_responseBuilder.length() * 2);
790#endif
791
792    unsetPendingActivity(this);
793}
794
795void XMLHttpRequest::overrideMimeType(const String& override)
796{
797    m_mimeTypeOverride = override;
798}
799
800static void reportUnsafeUsage(ScriptExecutionContext* context, const String& message)
801{
802    if (!context)
803        return;
804    // FIXME: It's not good to report the bad usage without indicating what source line it came from.
805    // We should pass additional parameters so we can tell the console where the mistake occurred.
806    context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, message, 1, String(), 0);
807}
808
809void XMLHttpRequest::setRequestHeader(const AtomicString& name, const String& value, ExceptionCode& ec)
810{
811    if (m_state != OPENED || m_loader) {
812#if ENABLE(DASHBOARD_SUPPORT)
813        if (usesDashboardBackwardCompatibilityMode())
814            return;
815#endif
816
817        ec = INVALID_STATE_ERR;
818        return;
819    }
820
821    if (!isValidToken(name) || !isValidHeaderValue(value)) {
822        ec = SYNTAX_ERR;
823        return;
824    }
825
826    // A privileged script (e.g. a Dashboard widget) can set any headers.
827    if (!scriptExecutionContext()->securityOrigin()->canLoadLocalResources() && !isSafeRequestHeader(name)) {
828        reportUnsafeUsage(scriptExecutionContext(), "Refused to set unsafe header \"" + name + "\"");
829        return;
830    }
831
832    setRequestHeaderInternal(name, value);
833}
834
835void XMLHttpRequest::setRequestHeaderInternal(const AtomicString& name, const String& value)
836{
837    pair<HTTPHeaderMap::iterator, bool> result = m_requestHeaders.add(name, value);
838    if (!result.second)
839        result.first->second += ", " + value;
840}
841
842bool XMLHttpRequest::isSafeRequestHeader(const String& name) const
843{
844    return !staticData->m_forbiddenRequestHeaders.contains(name) && !name.startsWith(staticData->m_proxyHeaderPrefix, false)
845        && !name.startsWith(staticData->m_secHeaderPrefix, false);
846}
847
848String XMLHttpRequest::getRequestHeader(const AtomicString& name) const
849{
850    return m_requestHeaders.get(name);
851}
852
853String XMLHttpRequest::getAllResponseHeaders(ExceptionCode& ec) const
854{
855    if (m_state < HEADERS_RECEIVED) {
856        ec = INVALID_STATE_ERR;
857        return "";
858    }
859
860    Vector<UChar> stringBuilder;
861
862    HTTPHeaderMap::const_iterator end = m_response.httpHeaderFields().end();
863    for (HTTPHeaderMap::const_iterator it = m_response.httpHeaderFields().begin(); it!= end; ++it) {
864        // Hide Set-Cookie header fields from the XMLHttpRequest client for these reasons:
865        //     1) If the client did have access to the fields, then it could read HTTP-only
866        //        cookies; those cookies are supposed to be hidden from scripts.
867        //     2) There's no known harm in hiding Set-Cookie header fields entirely; we don't
868        //        know any widely used technique that requires access to them.
869        //     3) Firefox has implemented this policy.
870        if (isSetCookieHeader(it->first) && !scriptExecutionContext()->securityOrigin()->canLoadLocalResources())
871            continue;
872
873        if (!m_sameOriginRequest && !isOnAccessControlResponseHeaderWhitelist(it->first))
874            continue;
875
876        stringBuilder.append(it->first.characters(), it->first.length());
877        stringBuilder.append(':');
878        stringBuilder.append(' ');
879        stringBuilder.append(it->second.characters(), it->second.length());
880        stringBuilder.append('\r');
881        stringBuilder.append('\n');
882    }
883
884    return String::adopt(stringBuilder);
885}
886
887String XMLHttpRequest::getResponseHeader(const AtomicString& name, ExceptionCode& ec) const
888{
889    if (m_state < HEADERS_RECEIVED) {
890        ec = INVALID_STATE_ERR;
891        return String();
892    }
893
894    // See comment in getAllResponseHeaders above.
895    if (isSetCookieHeader(name) && !scriptExecutionContext()->securityOrigin()->canLoadLocalResources()) {
896        reportUnsafeUsage(scriptExecutionContext(), "Refused to get unsafe header \"" + name + "\"");
897        return String();
898    }
899
900    if (!m_sameOriginRequest && !isOnAccessControlResponseHeaderWhitelist(name)) {
901        reportUnsafeUsage(scriptExecutionContext(), "Refused to get unsafe header \"" + name + "\"");
902        return String();
903    }
904    return m_response.httpHeaderField(name);
905}
906
907String XMLHttpRequest::responseMIMEType() const
908{
909    String mimeType = extractMIMETypeFromMediaType(m_mimeTypeOverride);
910    if (mimeType.isEmpty()) {
911        if (m_response.isHTTP())
912            mimeType = extractMIMETypeFromMediaType(m_response.httpHeaderField("Content-Type"));
913        else
914            mimeType = m_response.mimeType();
915    }
916    if (mimeType.isEmpty())
917        mimeType = "text/xml";
918
919    return mimeType;
920}
921
922bool XMLHttpRequest::responseIsXML() const
923{
924    return DOMImplementation::isXMLMIMEType(responseMIMEType());
925}
926
927int XMLHttpRequest::status(ExceptionCode& ec) const
928{
929    if (m_response.httpStatusCode())
930        return m_response.httpStatusCode();
931
932    if (m_state == OPENED) {
933        // Firefox only raises an exception in this state; we match it.
934        // Note the case of local file requests, where we have no HTTP response code! Firefox never raises an exception for those, but we match HTTP case for consistency.
935        ec = INVALID_STATE_ERR;
936    }
937
938    return 0;
939}
940
941String XMLHttpRequest::statusText(ExceptionCode& ec) const
942{
943    if (!m_response.httpStatusText().isNull())
944        return m_response.httpStatusText();
945
946    if (m_state == OPENED) {
947        // See comments in status() above.
948        ec = INVALID_STATE_ERR;
949    }
950
951    return String();
952}
953
954void XMLHttpRequest::didFail(const ResourceError& error)
955{
956
957    // If we are already in an error state, for instance we called abort(), bail out early.
958    if (m_error)
959        return;
960
961    if (error.isCancellation()) {
962        m_exceptionCode = XMLHttpRequestException::ABORT_ERR;
963        abortError();
964        return;
965    }
966
967    // Network failures are already reported to Web Inspector by ResourceLoader.
968    if (error.domain() == errorDomainWebKitInternal)
969        reportUnsafeUsage(scriptExecutionContext(), "XMLHttpRequest cannot load " + error.failingURL() + ". " + error.localizedDescription());
970
971    m_exceptionCode = XMLHttpRequestException::NETWORK_ERR;
972    networkError();
973}
974
975void XMLHttpRequest::didFailRedirectCheck()
976{
977    networkError();
978}
979
980void XMLHttpRequest::didFinishLoading(unsigned long identifier, double)
981{
982    if (m_error)
983        return;
984
985    if (m_state < HEADERS_RECEIVED)
986        changeState(HEADERS_RECEIVED);
987
988    if (m_decoder)
989        m_responseBuilder.append(m_decoder->flush());
990
991    m_responseBuilder.shrinkToFit();
992
993#if ENABLE(XHR_RESPONSE_BLOB)
994    // FIXME: Set m_responseBlob to something here in the ResponseTypeBlob case.
995#endif
996
997    InspectorInstrumentation::resourceRetrievedByXMLHttpRequest(scriptExecutionContext(), identifier, m_responseBuilder.toStringPreserveCapacity(), m_url, m_lastSendURL, m_lastSendLineNumber);
998
999    bool hadLoader = m_loader;
1000    m_loader = 0;
1001
1002    changeState(DONE);
1003    m_decoder = 0;
1004
1005    if (hadLoader)
1006        dropProtection();
1007}
1008
1009void XMLHttpRequest::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
1010{
1011    if (!m_upload)
1012        return;
1013
1014    if (m_uploadEventsAllowed)
1015        m_upload->dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().progressEvent, true, bytesSent, totalBytesToBeSent));
1016
1017    if (bytesSent == totalBytesToBeSent && !m_uploadComplete) {
1018        m_uploadComplete = true;
1019        if (m_uploadEventsAllowed)
1020            m_upload->dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().loadEvent));
1021    }
1022}
1023
1024void XMLHttpRequest::didReceiveResponse(const ResourceResponse& response)
1025{
1026    m_response = response;
1027    m_responseEncoding = extractCharsetFromMediaType(m_mimeTypeOverride);
1028    if (m_responseEncoding.isEmpty())
1029        m_responseEncoding = response.textEncodingName();
1030}
1031
1032void XMLHttpRequest::didReceiveAuthenticationCancellation(const ResourceResponse& failureResponse)
1033{
1034    m_response = failureResponse;
1035}
1036
1037void XMLHttpRequest::didReceiveData(const char* data, int len)
1038{
1039    if (m_error)
1040        return;
1041
1042    if (m_state < HEADERS_RECEIVED)
1043        changeState(HEADERS_RECEIVED);
1044
1045    bool useDecoder = responseTypeCode() == ResponseTypeDefault || responseTypeCode() == ResponseTypeText || responseTypeCode() == ResponseTypeDocument;
1046
1047    if (useDecoder && !m_decoder) {
1048        if (!m_responseEncoding.isEmpty())
1049            m_decoder = TextResourceDecoder::create("text/plain", m_responseEncoding);
1050        // allow TextResourceDecoder to look inside the m_response if it's XML or HTML
1051        else if (responseIsXML()) {
1052            m_decoder = TextResourceDecoder::create("application/xml");
1053            // Don't stop on encoding errors, unlike it is done for other kinds of XML resources. This matches the behavior of previous WebKit versions, Firefox and Opera.
1054            m_decoder->useLenientXMLDecoding();
1055        } else if (responseMIMEType() == "text/html")
1056            m_decoder = TextResourceDecoder::create("text/html", "UTF-8");
1057        else
1058            m_decoder = TextResourceDecoder::create("text/plain", "UTF-8");
1059    }
1060
1061    if (!len)
1062        return;
1063
1064    if (len == -1)
1065        len = strlen(data);
1066
1067    if (useDecoder)
1068        m_responseBuilder.append(m_decoder->decode(data, len));
1069    else if (responseTypeCode() == ResponseTypeArrayBuffer) {
1070        // Buffer binary data.
1071        if (!m_binaryResponseBuilder)
1072            m_binaryResponseBuilder = SharedBuffer::create();
1073        m_binaryResponseBuilder->append(data, len);
1074    }
1075
1076    if (!m_error) {
1077        long long expectedLength = m_response.expectedContentLength();
1078        m_receivedLength += len;
1079
1080        if (m_async) {
1081            bool lengthComputable = expectedLength && m_receivedLength <= expectedLength;
1082            m_progressEventThrottle.dispatchProgressEvent(lengthComputable, m_receivedLength, expectedLength);
1083        }
1084
1085        if (m_state != LOADING)
1086            changeState(LOADING);
1087        else
1088            // Firefox calls readyStateChanged every time it receives data, 4449442
1089            callReadyStateChangeListener();
1090    }
1091}
1092
1093bool XMLHttpRequest::canSuspend() const
1094{
1095    return !m_loader;
1096}
1097
1098void XMLHttpRequest::suspend(ReasonForSuspension)
1099{
1100    m_progressEventThrottle.suspend();
1101}
1102
1103void XMLHttpRequest::resume()
1104{
1105    m_progressEventThrottle.resume();
1106}
1107
1108void XMLHttpRequest::stop()
1109{
1110    internalAbort();
1111}
1112
1113void XMLHttpRequest::contextDestroyed()
1114{
1115    ASSERT(!m_loader);
1116    ActiveDOMObject::contextDestroyed();
1117}
1118
1119ScriptExecutionContext* XMLHttpRequest::scriptExecutionContext() const
1120{
1121    return ActiveDOMObject::scriptExecutionContext();
1122}
1123
1124EventTargetData* XMLHttpRequest::eventTargetData()
1125{
1126    return &m_eventTargetData;
1127}
1128
1129EventTargetData* XMLHttpRequest::ensureEventTargetData()
1130{
1131    return &m_eventTargetData;
1132}
1133
1134} // namespace WebCore
1135