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 if (m_upload)
198 m_upload->disconnectXMLHttpRequest();
199
200 #ifndef NDEBUG
201 xmlHttpRequestCounter.decrement();
202 #endif
203 }
204
document() const205 Document* XMLHttpRequest::document() const
206 {
207 ASSERT(scriptExecutionContext()->isDocument());
208 return static_cast<Document*>(scriptExecutionContext());
209 }
210
211 #if ENABLE(DASHBOARD_SUPPORT)
usesDashboardBackwardCompatibilityMode() const212 bool 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
readyState() const221 XMLHttpRequest::State XMLHttpRequest::readyState() const
222 {
223 return m_state;
224 }
225
responseText(ExceptionCode & ec)226 String 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
responseXML(ExceptionCode & ec)235 Document* 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)
responseBlob(ExceptionCode & ec) const264 Blob* 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
responseArrayBuffer(ExceptionCode & ec)274 ArrayBuffer* 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
setResponseType(const String & responseType,ExceptionCode & ec)295 void 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
responseType()318 String 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
upload()335 XMLHttpRequestUpload* XMLHttpRequest::upload()
336 {
337 if (!m_upload)
338 m_upload = XMLHttpRequestUpload::create(this);
339 return m_upload.get();
340 }
341
changeState(State newState)342 void XMLHttpRequest::changeState(State newState)
343 {
344 if (m_state != newState) {
345 m_state = newState;
346 callReadyStateChangeListener();
347 }
348 }
349
callReadyStateChangeListener()350 void 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
setWithCredentials(bool value,ExceptionCode & ec)369 void 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)
setAsBlob(bool value,ExceptionCode & ec)380 void 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
open(const String & method,const KURL & url,ExceptionCode & ec)391 void XMLHttpRequest::open(const String& method, const KURL& url, ExceptionCode& ec)
392 {
393 open(method, url, true, ec);
394 }
395
open(const String & method,const KURL & url,bool async,ExceptionCode & ec)396 void 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
open(const String & method,const KURL & url,bool async,const String & user,ExceptionCode & ec)446 void 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
open(const String & method,const KURL & url,bool async,const String & user,const String & password,ExceptionCode & ec)454 void 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
initSend(ExceptionCode & ec)463 bool 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
send(ExceptionCode & ec)477 void XMLHttpRequest::send(ExceptionCode& ec)
478 {
479 send(String(), ec);
480 }
481
send(Document * document,ExceptionCode & ec)482 void 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
send(const String & body,ExceptionCode & ec)515 void 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
send(Blob * body,ExceptionCode & ec)542 void 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
send(DOMFormData * body,ExceptionCode & ec)562 void 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
send(ArrayBuffer * body,ExceptionCode & ec)585 void 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
createRequest(ExceptionCode & ec)599 void 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
abort()672 void 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
internalAbort()702 void 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
clearResponse()722 void XMLHttpRequest::clearResponse()
723 {
724 m_response = ResourceResponse();
725 clearResponseBuffers();
726 }
727
clearResponseBuffers()728 void 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
clearRequest()740 void XMLHttpRequest::clearRequest()
741 {
742 m_requestHeaders.clear();
743 m_requestEntityBody = 0;
744 }
745
genericError()746 void XMLHttpRequest::genericError()
747 {
748 clearResponse();
749 clearRequest();
750 m_error = true;
751
752 changeState(DONE);
753 }
754
networkError()755 void 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
abortError()767 void 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
dropProtection()778 void 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
overrideMimeType(const String & override)795 void XMLHttpRequest::overrideMimeType(const String& override)
796 {
797 m_mimeTypeOverride = override;
798 }
799
reportUnsafeUsage(ScriptExecutionContext * context,const String & message)800 static 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
setRequestHeader(const AtomicString & name,const String & value,ExceptionCode & ec)809 void 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
setRequestHeaderInternal(const AtomicString & name,const String & value)835 void 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
isSafeRequestHeader(const String & name) const842 bool 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
getRequestHeader(const AtomicString & name) const848 String XMLHttpRequest::getRequestHeader(const AtomicString& name) const
849 {
850 return m_requestHeaders.get(name);
851 }
852
getAllResponseHeaders(ExceptionCode & ec) const853 String 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
getResponseHeader(const AtomicString & name,ExceptionCode & ec) const887 String 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
responseMIMEType() const907 String 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
responseIsXML() const922 bool XMLHttpRequest::responseIsXML() const
923 {
924 return DOMImplementation::isXMLMIMEType(responseMIMEType());
925 }
926
status(ExceptionCode & ec) const927 int 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
statusText(ExceptionCode & ec) const941 String 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
didFail(const ResourceError & error)954 void 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
didFailRedirectCheck()975 void XMLHttpRequest::didFailRedirectCheck()
976 {
977 networkError();
978 }
979
didFinishLoading(unsigned long identifier,double)980 void 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
didSendData(unsigned long long bytesSent,unsigned long long totalBytesToBeSent)1009 void 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
didReceiveResponse(const ResourceResponse & response)1024 void 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
didReceiveAuthenticationCancellation(const ResourceResponse & failureResponse)1032 void XMLHttpRequest::didReceiveAuthenticationCancellation(const ResourceResponse& failureResponse)
1033 {
1034 m_response = failureResponse;
1035 }
1036
didReceiveData(const char * data,int len)1037 void 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
canSuspend() const1093 bool XMLHttpRequest::canSuspend() const
1094 {
1095 return !m_loader;
1096 }
1097
suspend(ReasonForSuspension)1098 void XMLHttpRequest::suspend(ReasonForSuspension)
1099 {
1100 m_progressEventThrottle.suspend();
1101 }
1102
resume()1103 void XMLHttpRequest::resume()
1104 {
1105 m_progressEventThrottle.resume();
1106 }
1107
stop()1108 void XMLHttpRequest::stop()
1109 {
1110 internalAbort();
1111 }
1112
contextDestroyed()1113 void XMLHttpRequest::contextDestroyed()
1114 {
1115 ASSERT(!m_loader);
1116 ActiveDOMObject::contextDestroyed();
1117 }
1118
scriptExecutionContext() const1119 ScriptExecutionContext* XMLHttpRequest::scriptExecutionContext() const
1120 {
1121 return ActiveDOMObject::scriptExecutionContext();
1122 }
1123
eventTargetData()1124 EventTargetData* XMLHttpRequest::eventTargetData()
1125 {
1126 return &m_eventTargetData;
1127 }
1128
ensureEventTargetData()1129 EventTargetData* XMLHttpRequest::ensureEventTargetData()
1130 {
1131 return &m_eventTargetData;
1132 }
1133
1134 } // namespace WebCore
1135