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