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