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 "Cache.h"
26 #include "CString.h"
27 #include "CrossOriginAccessControl.h"
28 #include "CrossOriginPreflightResultCache.h"
29 #include "DOMImplementation.h"
30 #include "Document.h"
31 #include "Event.h"
32 #include "EventException.h"
33 #include "EventListener.h"
34 #include "EventNames.h"
35 #include "File.h"
36 #include "HTTPParsers.h"
37 #include "ResourceError.h"
38 #include "ResourceRequest.h"
39 #include "SecurityOrigin.h"
40 #include "Settings.h"
41 #include "TextResourceDecoder.h"
42 #include "ThreadableLoader.h"
43 #include "XMLHttpRequestException.h"
44 #include "XMLHttpRequestProgressEvent.h"
45 #include "XMLHttpRequestUpload.h"
46 #include "markup.h"
47 #include <wtf/StdLibExtras.h>
48
49 #if USE(JSC)
50 #include "JSDOMWindow.h"
51 #endif
52
53 namespace WebCore {
54
55 struct XMLHttpRequestStaticData {
56 XMLHttpRequestStaticData();
57 String m_proxyHeaderPrefix;
58 String m_secHeaderPrefix;
59 HashSet<String, CaseFoldingHash> m_forbiddenRequestHeaders;
60 };
61
XMLHttpRequestStaticData()62 XMLHttpRequestStaticData::XMLHttpRequestStaticData()
63 : m_proxyHeaderPrefix("proxy-")
64 , m_secHeaderPrefix("sec-")
65 {
66 m_forbiddenRequestHeaders.add("accept-charset");
67 m_forbiddenRequestHeaders.add("accept-encoding");
68 m_forbiddenRequestHeaders.add("access-control-request-headers");
69 m_forbiddenRequestHeaders.add("access-control-request-method");
70 m_forbiddenRequestHeaders.add("connection");
71 m_forbiddenRequestHeaders.add("content-length");
72 m_forbiddenRequestHeaders.add("content-transfer-encoding");
73 m_forbiddenRequestHeaders.add("cookie");
74 m_forbiddenRequestHeaders.add("cookie2");
75 m_forbiddenRequestHeaders.add("date");
76 m_forbiddenRequestHeaders.add("expect");
77 m_forbiddenRequestHeaders.add("host");
78 m_forbiddenRequestHeaders.add("keep-alive");
79 m_forbiddenRequestHeaders.add("origin");
80 m_forbiddenRequestHeaders.add("referer");
81 m_forbiddenRequestHeaders.add("te");
82 m_forbiddenRequestHeaders.add("trailer");
83 m_forbiddenRequestHeaders.add("transfer-encoding");
84 m_forbiddenRequestHeaders.add("upgrade");
85 m_forbiddenRequestHeaders.add("user-agent");
86 m_forbiddenRequestHeaders.add("via");
87 }
88
89 // Determines if a string is a valid token, as defined by
90 // "token" in section 2.2 of RFC 2616.
isValidToken(const String & name)91 static bool isValidToken(const String& name)
92 {
93 unsigned length = name.length();
94 for (unsigned i = 0; i < length; i++) {
95 UChar c = name[i];
96
97 if (c >= 127 || c <= 32)
98 return false;
99
100 if (c == '(' || c == ')' || c == '<' || c == '>' || c == '@' ||
101 c == ',' || c == ';' || c == ':' || c == '\\' || c == '\"' ||
102 c == '/' || c == '[' || c == ']' || c == '?' || c == '=' ||
103 c == '{' || c == '}')
104 return false;
105 }
106
107 return true;
108 }
109
isValidHeaderValue(const String & name)110 static bool isValidHeaderValue(const String& name)
111 {
112 // FIXME: This should really match name against
113 // field-value in section 4.2 of RFC 2616.
114
115 return !name.contains('\r') && !name.contains('\n');
116 }
117
isSetCookieHeader(const AtomicString & name)118 static bool isSetCookieHeader(const AtomicString& name)
119 {
120 return equalIgnoringCase(name, "set-cookie") || equalIgnoringCase(name, "set-cookie2");
121 }
122
123 static const XMLHttpRequestStaticData* staticData = 0;
124
createXMLHttpRequestStaticData()125 static const XMLHttpRequestStaticData* createXMLHttpRequestStaticData()
126 {
127 staticData = new XMLHttpRequestStaticData;
128 return staticData;
129 }
130
initializeXMLHttpRequestStaticData()131 static const XMLHttpRequestStaticData* initializeXMLHttpRequestStaticData()
132 {
133 // Uses dummy to avoid warnings about an unused variable.
134 AtomicallyInitializedStatic(const XMLHttpRequestStaticData*, dummy = createXMLHttpRequestStaticData());
135 return dummy;
136 }
137
XMLHttpRequest(ScriptExecutionContext * context)138 XMLHttpRequest::XMLHttpRequest(ScriptExecutionContext* context)
139 : ActiveDOMObject(context, this)
140 , m_async(true)
141 , m_includeCredentials(false)
142 , m_state(UNSENT)
143 , m_responseText("")
144 , m_createdDocument(false)
145 , m_error(false)
146 , m_uploadComplete(false)
147 , m_sameOriginRequest(true)
148 , m_inPreflight(false)
149 , m_didTellLoaderAboutRequest(false)
150 , m_receivedLength(0)
151 , m_lastSendLineNumber(0)
152 , m_exceptionCode(0)
153 {
154 initializeXMLHttpRequestStaticData();
155 }
156
~XMLHttpRequest()157 XMLHttpRequest::~XMLHttpRequest()
158 {
159 if (m_didTellLoaderAboutRequest) {
160 cache()->loader()->nonCacheRequestComplete(m_url);
161 m_didTellLoaderAboutRequest = false;
162 }
163 if (m_upload)
164 m_upload->disconnectXMLHttpRequest();
165 }
166
document() const167 Document* XMLHttpRequest::document() const
168 {
169 ASSERT(scriptExecutionContext()->isDocument());
170 return static_cast<Document*>(scriptExecutionContext());
171 }
172
173 #if ENABLE(DASHBOARD_SUPPORT)
usesDashboardBackwardCompatibilityMode() const174 bool XMLHttpRequest::usesDashboardBackwardCompatibilityMode() const
175 {
176 if (scriptExecutionContext()->isWorkerContext())
177 return false;
178 Settings* settings = document()->settings();
179 return settings && settings->usesDashboardBackwardCompatibilityMode();
180 }
181 #endif
182
readyState() const183 XMLHttpRequest::State XMLHttpRequest::readyState() const
184 {
185 return m_state;
186 }
187
responseText() const188 const ScriptString& XMLHttpRequest::responseText() const
189 {
190 return m_responseText;
191 }
192
responseXML() const193 Document* XMLHttpRequest::responseXML() const
194 {
195 if (m_state != DONE)
196 return 0;
197
198 if (!m_createdDocument) {
199 if ((m_response.isHTTP() && !responseIsXML()) || scriptExecutionContext()->isWorkerContext()) {
200 // The W3C spec requires this.
201 m_responseXML = 0;
202 } else {
203 m_responseXML = document()->implementation()->createDocument(0);
204 m_responseXML->open();
205 m_responseXML->setURL(m_url);
206 // FIXME: Set Last-Modified.
207 m_responseXML->write(String(m_responseText));
208 m_responseXML->finishParsing();
209 m_responseXML->close();
210
211 if (!m_responseXML->wellFormed())
212 m_responseXML = 0;
213 }
214 m_createdDocument = true;
215 }
216
217 return m_responseXML.get();
218 }
219
upload()220 XMLHttpRequestUpload* XMLHttpRequest::upload()
221 {
222 if (!m_upload)
223 m_upload = XMLHttpRequestUpload::create(this);
224 return m_upload.get();
225 }
226
addEventListener(const AtomicString & eventType,PassRefPtr<EventListener> eventListener,bool)227 void XMLHttpRequest::addEventListener(const AtomicString& eventType, PassRefPtr<EventListener> eventListener, bool)
228 {
229 EventListenersMap::iterator iter = m_eventListeners.find(eventType);
230 if (iter == m_eventListeners.end()) {
231 ListenerVector listeners;
232 listeners.append(eventListener);
233 m_eventListeners.add(eventType, listeners);
234 } else {
235 ListenerVector& listeners = iter->second;
236 for (ListenerVector::iterator listenerIter = listeners.begin(); listenerIter != listeners.end(); ++listenerIter)
237 if (*listenerIter == eventListener)
238 return;
239
240 listeners.append(eventListener);
241 m_eventListeners.add(eventType, listeners);
242 }
243 }
244
removeEventListener(const AtomicString & eventType,EventListener * eventListener,bool)245 void XMLHttpRequest::removeEventListener(const AtomicString& eventType, EventListener* eventListener, bool)
246 {
247 EventListenersMap::iterator iter = m_eventListeners.find(eventType);
248 if (iter == m_eventListeners.end())
249 return;
250
251 ListenerVector& listeners = iter->second;
252 for (ListenerVector::const_iterator listenerIter = listeners.begin(); listenerIter != listeners.end(); ++listenerIter)
253 if (*listenerIter == eventListener) {
254 listeners.remove(listenerIter - listeners.begin());
255 return;
256 }
257 }
258
dispatchEvent(PassRefPtr<Event> evt,ExceptionCode & ec)259 bool XMLHttpRequest::dispatchEvent(PassRefPtr<Event> evt, ExceptionCode& ec)
260 {
261 // FIXME: check for other error conditions enumerated in the spec.
262 if (!evt || evt->type().isEmpty()) {
263 ec = EventException::UNSPECIFIED_EVENT_TYPE_ERR;
264 return true;
265 }
266
267 ListenerVector listenersCopy = m_eventListeners.get(evt->type());
268 for (ListenerVector::const_iterator listenerIter = listenersCopy.begin(); listenerIter != listenersCopy.end(); ++listenerIter) {
269 evt->setTarget(this);
270 evt->setCurrentTarget(this);
271 listenerIter->get()->handleEvent(evt.get(), false);
272 }
273
274 return !evt->defaultPrevented();
275 }
276
changeState(State newState)277 void XMLHttpRequest::changeState(State newState)
278 {
279 if (m_state != newState) {
280 m_state = newState;
281 callReadyStateChangeListener();
282 }
283 }
284
callReadyStateChangeListener()285 void XMLHttpRequest::callReadyStateChangeListener()
286 {
287 if (!scriptExecutionContext())
288 return;
289
290 dispatchReadyStateChangeEvent();
291
292 if (m_state == DONE && !m_error)
293 dispatchLoadEvent();
294 }
295
setWithCredentials(bool value,ExceptionCode & ec)296 void XMLHttpRequest::setWithCredentials(bool value, ExceptionCode& ec)
297 {
298 if (m_state != OPENED || m_loader) {
299 ec = INVALID_STATE_ERR;
300 return;
301 }
302
303 m_includeCredentials = value;
304 }
305
open(const String & method,const KURL & url,bool async,ExceptionCode & ec)306 void XMLHttpRequest::open(const String& method, const KURL& url, bool async, ExceptionCode& ec)
307 {
308 internalAbort();
309 State previousState = m_state;
310 m_state = UNSENT;
311 m_error = false;
312
313 m_uploadComplete = false;
314
315 // clear stuff from possible previous load
316 clearResponse();
317 clearRequest();
318
319 ASSERT(m_state == UNSENT);
320
321 if (!isValidToken(method)) {
322 ec = SYNTAX_ERR;
323 return;
324 }
325
326 // Method names are case sensitive. But since Firefox uppercases method names it knows, we'll do the same.
327 String methodUpper(method.upper());
328
329 if (methodUpper == "TRACE" || methodUpper == "TRACK" || methodUpper == "CONNECT") {
330 ec = SECURITY_ERR;
331 return;
332 }
333
334 m_url = url;
335
336 if (methodUpper == "COPY" || methodUpper == "DELETE" || methodUpper == "GET" || methodUpper == "HEAD"
337 || methodUpper == "INDEX" || methodUpper == "LOCK" || methodUpper == "M-POST" || methodUpper == "MKCOL" || methodUpper == "MOVE"
338 || methodUpper == "OPTIONS" || methodUpper == "POST" || methodUpper == "PROPFIND" || methodUpper == "PROPPATCH" || methodUpper == "PUT"
339 || methodUpper == "UNLOCK")
340 m_method = methodUpper;
341 else
342 m_method = method;
343
344 m_async = async;
345
346 ASSERT(!m_loader);
347
348 // Check previous state to avoid dispatching readyState event
349 // when calling open several times in a row.
350 if (previousState != OPENED)
351 changeState(OPENED);
352 else
353 m_state = OPENED;
354 }
355
open(const String & method,const KURL & url,bool async,const String & user,ExceptionCode & ec)356 void XMLHttpRequest::open(const String& method, const KURL& url, bool async, const String& user, ExceptionCode& ec)
357 {
358 KURL urlWithCredentials(url);
359 urlWithCredentials.setUser(user);
360
361 open(method, urlWithCredentials, async, ec);
362 }
363
open(const String & method,const KURL & url,bool async,const String & user,const String & password,ExceptionCode & ec)364 void XMLHttpRequest::open(const String& method, const KURL& url, bool async, const String& user, const String& password, ExceptionCode& ec)
365 {
366 KURL urlWithCredentials(url);
367 urlWithCredentials.setUser(user);
368 urlWithCredentials.setPass(password);
369
370 open(method, urlWithCredentials, async, ec);
371 }
372
initSend(ExceptionCode & ec)373 bool XMLHttpRequest::initSend(ExceptionCode& ec)
374 {
375 if (!scriptExecutionContext())
376 return false;
377
378 if (m_state != OPENED || m_loader) {
379 ec = INVALID_STATE_ERR;
380 return false;
381 }
382
383 m_error = false;
384 return true;
385 }
386
send(ExceptionCode & ec)387 void XMLHttpRequest::send(ExceptionCode& ec)
388 {
389 send(String(), ec);
390 }
391
send(Document * document,ExceptionCode & ec)392 void XMLHttpRequest::send(Document* document, ExceptionCode& ec)
393 {
394 ASSERT(document);
395
396 if (!initSend(ec))
397 return;
398
399 if (m_method != "GET" && m_method != "HEAD" && m_url.protocolInHTTPFamily()) {
400 String contentType = getRequestHeader("Content-Type");
401 if (contentType.isEmpty()) {
402 #if ENABLE(DASHBOARD_SUPPORT)
403 if (usesDashboardBackwardCompatibilityMode())
404 setRequestHeaderInternal("Content-Type", "application/x-www-form-urlencoded");
405 else
406 #endif
407 // FIXME: this should include the charset used for encoding.
408 setRequestHeaderInternal("Content-Type", "application/xml");
409 }
410
411 // FIXME: According to XMLHttpRequest Level 2, this should use the Document.innerHTML algorithm
412 // from the HTML5 specification to serialize the document.
413 String body = createMarkup(document);
414
415 // FIXME: this should use value of document.inputEncoding to determine the encoding to use.
416 TextEncoding encoding = UTF8Encoding();
417 m_requestEntityBody = FormData::create(encoding.encode(body.characters(), body.length(), EntitiesForUnencodables));
418 if (m_upload)
419 m_requestEntityBody->setAlwaysStream(true);
420 }
421
422 createRequest(ec);
423 }
424
send(const String & body,ExceptionCode & ec)425 void XMLHttpRequest::send(const String& body, ExceptionCode& ec)
426 {
427 if (!initSend(ec))
428 return;
429
430 if (!body.isNull() && m_method != "GET" && m_method != "HEAD" && m_url.protocolInHTTPFamily()) {
431 String contentType = getRequestHeader("Content-Type");
432 if (contentType.isEmpty()) {
433 #if ENABLE(DASHBOARD_SUPPORT)
434 if (usesDashboardBackwardCompatibilityMode())
435 setRequestHeaderInternal("Content-Type", "application/x-www-form-urlencoded");
436 else
437 #endif
438 setRequestHeaderInternal("Content-Type", "application/xml");
439 }
440
441 m_requestEntityBody = FormData::create(UTF8Encoding().encode(body.characters(), body.length(), EntitiesForUnencodables));
442 if (m_upload)
443 m_requestEntityBody->setAlwaysStream(true);
444 }
445
446 createRequest(ec);
447 }
448
send(File * body,ExceptionCode & ec)449 void XMLHttpRequest::send(File* body, ExceptionCode& ec)
450 {
451 if (!initSend(ec))
452 return;
453
454 if (m_method != "GET" && m_method != "HEAD" && m_url.protocolInHTTPFamily()) {
455 // FIXME: Should we set a Content-Type if one is not set.
456 // FIXME: add support for uploading bundles.
457 m_requestEntityBody = FormData::create();
458 m_requestEntityBody->appendFile(body->path(), false);
459 }
460
461 createRequest(ec);
462 }
463
createRequest(ExceptionCode & ec)464 void XMLHttpRequest::createRequest(ExceptionCode& ec)
465 {
466 // Upload event listeners should be disallowed for simple cross-origin requests, because POSTing to an URL that does not
467 // permit cross origin requests should look exactly like POSTing to an URL that does not respond at all. If a listener exists
468 // when creating the request, it will force preflight.
469 // Also, only async requests support upload progress events.
470 m_uploadEventsAllowed = false;
471 if (m_async) {
472 dispatchLoadStartEvent();
473 if (m_requestEntityBody && m_upload) {
474 m_uploadEventsAllowed = m_upload->hasListeners();
475 m_upload->dispatchLoadStartEvent();
476 }
477 }
478
479 m_sameOriginRequest = scriptExecutionContext()->securityOrigin()->canRequest(m_url);
480
481 if (!m_sameOriginRequest) {
482 makeCrossOriginAccessRequest(ec);
483 return;
484 }
485
486 m_uploadEventsAllowed = true;
487
488 makeSameOriginRequest(ec);
489 }
490
makeSameOriginRequest(ExceptionCode & ec)491 void XMLHttpRequest::makeSameOriginRequest(ExceptionCode& ec)
492 {
493 ASSERT(m_sameOriginRequest);
494
495 ResourceRequest request(m_url);
496 request.setHTTPMethod(m_method);
497
498 if (m_requestEntityBody) {
499 ASSERT(m_method != "GET");
500 ASSERT(m_method != "HEAD");
501 request.setHTTPBody(m_requestEntityBody.release());
502 }
503
504 if (m_requestHeaders.size() > 0)
505 request.addHTTPHeaderFields(m_requestHeaders);
506
507 if (m_async)
508 loadRequestAsynchronously(request);
509 else
510 loadRequestSynchronously(request, ec);
511 }
512
makeCrossOriginAccessRequest(ExceptionCode & ec)513 void XMLHttpRequest::makeCrossOriginAccessRequest(ExceptionCode& ec)
514 {
515 ASSERT(!m_sameOriginRequest);
516
517 if (!m_uploadEventsAllowed && isSimpleCrossOriginAccessRequest(m_method, m_requestHeaders))
518 makeSimpleCrossOriginAccessRequest(ec);
519 else
520 makeCrossOriginAccessRequestWithPreflight(ec);
521 }
522
makeSimpleCrossOriginAccessRequest(ExceptionCode & ec)523 void XMLHttpRequest::makeSimpleCrossOriginAccessRequest(ExceptionCode& ec)
524 {
525 ASSERT(isSimpleCrossOriginAccessRequest(m_method, m_requestHeaders));
526
527 // Cross-origin requests are only defined for HTTP. We would catch this when checking response headers later, but there is no reason to send a request that's guaranteed to be denied.
528 if (!m_url.protocolInHTTPFamily()) {
529 ec = XMLHttpRequestException::NETWORK_ERR;
530 networkError();
531 return;
532 }
533
534 KURL url = m_url;
535 url.setUser(String());
536 url.setPass(String());
537
538 ResourceRequest request(url);
539 request.setHTTPMethod(m_method);
540 request.setAllowHTTPCookies(m_includeCredentials);
541 request.setHTTPOrigin(scriptExecutionContext()->securityOrigin()->toString());
542
543 if (m_requestHeaders.size() > 0)
544 request.addHTTPHeaderFields(m_requestHeaders);
545
546 if (m_requestEntityBody) {
547 ASSERT(m_method != "GET");
548 ASSERT(m_method != "HEAD");
549 request.setHTTPBody(m_requestEntityBody.release());
550 }
551
552 if (m_async)
553 loadRequestAsynchronously(request);
554 else
555 loadRequestSynchronously(request, ec);
556 }
557
makeCrossOriginAccessRequestWithPreflight(ExceptionCode & ec)558 void XMLHttpRequest::makeCrossOriginAccessRequestWithPreflight(ExceptionCode& ec)
559 {
560 String origin = scriptExecutionContext()->securityOrigin()->toString();
561 KURL url = m_url;
562 url.setUser(String());
563 url.setPass(String());
564
565 if (!CrossOriginPreflightResultCache::shared().canSkipPreflight(origin, url, m_includeCredentials, m_method, m_requestHeaders)) {
566 m_inPreflight = true;
567 ResourceRequest preflightRequest(url);
568 preflightRequest.setHTTPMethod("OPTIONS");
569 preflightRequest.setHTTPHeaderField("Origin", origin);
570 preflightRequest.setHTTPHeaderField("Access-Control-Request-Method", m_method);
571
572 if (m_requestHeaders.size() > 0) {
573 Vector<UChar> headerBuffer;
574 HTTPHeaderMap::const_iterator it = m_requestHeaders.begin();
575 append(headerBuffer, it->first);
576 ++it;
577
578 HTTPHeaderMap::const_iterator end = m_requestHeaders.end();
579 for (; it != end; ++it) {
580 headerBuffer.append(',');
581 headerBuffer.append(' ');
582 append(headerBuffer, it->first);
583 }
584
585 preflightRequest.setHTTPHeaderField("Access-Control-Request-Headers", String::adopt(headerBuffer));
586 preflightRequest.addHTTPHeaderFields(m_requestHeaders);
587 }
588
589 if (m_async) {
590 m_uploadEventsAllowed = true;
591 loadRequestAsynchronously(preflightRequest);
592 return;
593 }
594
595 loadRequestSynchronously(preflightRequest, ec);
596 m_inPreflight = false;
597
598 if (ec)
599 return;
600 }
601
602 // Send the actual request.
603 ResourceRequest request(url);
604 request.setHTTPMethod(m_method);
605 request.setAllowHTTPCookies(m_includeCredentials);
606 request.setHTTPHeaderField("Origin", origin);
607
608 if (m_requestHeaders.size() > 0)
609 request.addHTTPHeaderFields(m_requestHeaders);
610
611 if (m_requestEntityBody) {
612 ASSERT(m_method != "GET");
613 ASSERT(m_method != "HEAD");
614 request.setHTTPBody(m_requestEntityBody.release());
615 }
616
617 if (m_async) {
618 m_uploadEventsAllowed = true;
619 loadRequestAsynchronously(request);
620 return;
621 }
622
623 loadRequestSynchronously(request, ec);
624 }
625
handleAsynchronousPreflightResult()626 void XMLHttpRequest::handleAsynchronousPreflightResult()
627 {
628 ASSERT(m_inPreflight);
629 ASSERT(m_async);
630
631 m_inPreflight = false;
632
633 KURL url = m_url;
634 url.setUser(String());
635 url.setPass(String());
636
637 ResourceRequest request(url);
638 request.setHTTPMethod(m_method);
639 request.setAllowHTTPCookies(m_includeCredentials);
640 request.setHTTPOrigin(scriptExecutionContext()->securityOrigin()->toString());
641
642 if (m_requestHeaders.size() > 0)
643 request.addHTTPHeaderFields(m_requestHeaders);
644
645 if (m_requestEntityBody) {
646 ASSERT(m_method != "GET");
647 ASSERT(m_method != "HEAD");
648 request.setHTTPBody(m_requestEntityBody.release());
649 }
650
651 m_uploadEventsAllowed = true;
652 loadRequestAsynchronously(request);
653 }
654
loadRequestSynchronously(ResourceRequest & request,ExceptionCode & ec)655 void XMLHttpRequest::loadRequestSynchronously(ResourceRequest& request, ExceptionCode& ec)
656 {
657 ASSERT(!m_async);
658
659 m_loader = 0;
660 m_exceptionCode = 0;
661 StoredCredentials storedCredentials = (m_sameOriginRequest || m_includeCredentials) ? AllowStoredCredentials : DoNotAllowStoredCredentials;
662
663 ThreadableLoader::loadResourceSynchronously(scriptExecutionContext(), request, *this, storedCredentials);
664 if (!m_exceptionCode && m_error)
665 m_exceptionCode = XMLHttpRequestException::NETWORK_ERR;
666 ec = m_exceptionCode;
667 }
668
loadRequestAsynchronously(ResourceRequest & request)669 void XMLHttpRequest::loadRequestAsynchronously(ResourceRequest& request)
670 {
671 ASSERT(m_async);
672 m_exceptionCode = 0;
673 // SubresourceLoader::create can return null here, for example if we're no longer attached to a page.
674 // This is true while running onunload handlers.
675 // FIXME: We need to be able to send XMLHttpRequests from onunload, <http://bugs.webkit.org/show_bug.cgi?id=10904>.
676 // FIXME: Maybe create can return null for other reasons too?
677 LoadCallbacks callbacks = m_inPreflight ? DoNotSendLoadCallbacks : SendLoadCallbacks;
678 StoredCredentials storedCredentials = (m_sameOriginRequest || m_includeCredentials) ? AllowStoredCredentials : DoNotAllowStoredCredentials;
679
680 if (m_upload)
681 request.setReportUploadProgress(true);
682
683 m_loader = ThreadableLoader::create(scriptExecutionContext(), this, request, callbacks, DoNotSniffContent, storedCredentials, DenyCrossOriginRedirect);
684
685 if (m_loader) {
686 // Neither this object nor the JavaScript wrapper should be deleted while
687 // a request is in progress because we need to keep the listeners alive,
688 // and they are referenced by the JavaScript wrapper.
689 setPendingActivity(this);
690
691 // For now we should only balance the nonCached request count for main-thread XHRs and not
692 // Worker XHRs, as the Cache is not thread-safe.
693 // This will become irrelevant after https://bugs.webkit.org/show_bug.cgi?id=27165 is resolved.
694 if (!scriptExecutionContext()->isWorkerContext()) {
695 ASSERT(isMainThread());
696 ASSERT(!m_didTellLoaderAboutRequest);
697 cache()->loader()->nonCacheRequestInFlight(m_url);
698 m_didTellLoaderAboutRequest = true;
699 }
700 }
701 }
702
abort()703 void XMLHttpRequest::abort()
704 {
705 // internalAbort() calls dropProtection(), which may release the last reference.
706 RefPtr<XMLHttpRequest> protect(this);
707
708 bool sendFlag = m_loader;
709
710 internalAbort();
711
712 // Clear headers as required by the spec
713 m_requestHeaders.clear();
714
715 if ((m_state <= OPENED && !sendFlag) || m_state == DONE)
716 m_state = UNSENT;
717 else {
718 ASSERT(!m_loader);
719 changeState(DONE);
720 m_state = UNSENT;
721 }
722
723 dispatchAbortEvent();
724 if (!m_uploadComplete) {
725 m_uploadComplete = true;
726 if (m_upload && m_uploadEventsAllowed)
727 m_upload->dispatchAbortEvent();
728 }
729 }
730
internalAbort()731 void XMLHttpRequest::internalAbort()
732 {
733 bool hadLoader = m_loader;
734
735 m_error = true;
736
737 // FIXME: when we add the support for multi-part XHR, we will have to think be careful with this initialization.
738 m_receivedLength = 0;
739
740 if (hadLoader) {
741 m_loader->cancel();
742 m_loader = 0;
743 }
744
745 m_decoder = 0;
746
747 if (hadLoader)
748 dropProtection();
749 }
750
clearResponse()751 void XMLHttpRequest::clearResponse()
752 {
753 m_response = ResourceResponse();
754 m_responseText = "";
755 m_createdDocument = false;
756 m_responseXML = 0;
757 }
758
clearRequest()759 void XMLHttpRequest::clearRequest()
760 {
761 m_requestHeaders.clear();
762 m_requestEntityBody = 0;
763 }
764
genericError()765 void XMLHttpRequest::genericError()
766 {
767 clearResponse();
768 clearRequest();
769 m_error = true;
770
771 changeState(DONE);
772 }
773
networkError()774 void XMLHttpRequest::networkError()
775 {
776 genericError();
777 dispatchErrorEvent();
778 if (!m_uploadComplete) {
779 m_uploadComplete = true;
780 if (m_upload && m_uploadEventsAllowed)
781 m_upload->dispatchErrorEvent();
782 }
783 internalAbort();
784 }
785
abortError()786 void XMLHttpRequest::abortError()
787 {
788 genericError();
789 dispatchAbortEvent();
790 if (!m_uploadComplete) {
791 m_uploadComplete = true;
792 if (m_upload && m_uploadEventsAllowed)
793 m_upload->dispatchAbortEvent();
794 }
795 }
796
dropProtection()797 void XMLHttpRequest::dropProtection()
798 {
799 #if USE(JSC)
800 // The XHR object itself holds on to the responseText, and
801 // thus has extra cost even independent of any
802 // responseText or responseXML objects it has handed
803 // out. But it is protected from GC while loading, so this
804 // can't be recouped until the load is done, so only
805 // report the extra cost at that point.
806
807 if (JSDOMGlobalObject* globalObject = toJSDOMGlobalObject(scriptExecutionContext()))
808 if (DOMObject* wrapper = getCachedDOMObjectWrapper(*globalObject->globalData(), this))
809 JSC::Heap::heap(wrapper)->reportExtraMemoryCost(m_responseText.size() * 2);
810 #endif
811
812 unsetPendingActivity(this);
813 }
814
overrideMimeType(const String & override)815 void XMLHttpRequest::overrideMimeType(const String& override)
816 {
817 m_mimeTypeOverride = override;
818 }
819
reportUnsafeUsage(ScriptExecutionContext * context,const String & message)820 static void reportUnsafeUsage(ScriptExecutionContext* context, const String& message)
821 {
822 if (!context)
823 return;
824 // FIXME: It's not good to report the bad usage without indicating what source line it came from.
825 // We should pass additional parameters so we can tell the console where the mistake occurred.
826 context->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, message, 1, String());
827 }
828
setRequestHeader(const AtomicString & name,const String & value,ExceptionCode & ec)829 void XMLHttpRequest::setRequestHeader(const AtomicString& name, const String& value, ExceptionCode& ec)
830 {
831 if (m_state != OPENED || m_loader) {
832 #if ENABLE(DASHBOARD_SUPPORT)
833 if (usesDashboardBackwardCompatibilityMode())
834 return;
835 #endif
836
837 ec = INVALID_STATE_ERR;
838 return;
839 }
840
841 if (!isValidToken(name) || !isValidHeaderValue(value)) {
842 ec = SYNTAX_ERR;
843 return;
844 }
845
846 // A privileged script (e.g. a Dashboard widget) can set any headers.
847 if (!scriptExecutionContext()->securityOrigin()->canLoadLocalResources() && !isSafeRequestHeader(name)) {
848 reportUnsafeUsage(scriptExecutionContext(), "Refused to set unsafe header \"" + name + "\"");
849 return;
850 }
851
852 setRequestHeaderInternal(name, value);
853 }
854
setRequestHeaderInternal(const AtomicString & name,const String & value)855 void XMLHttpRequest::setRequestHeaderInternal(const AtomicString& name, const String& value)
856 {
857 pair<HTTPHeaderMap::iterator, bool> result = m_requestHeaders.add(name, value);
858 if (!result.second)
859 result.first->second += ", " + value;
860 }
861
isSafeRequestHeader(const String & name) const862 bool XMLHttpRequest::isSafeRequestHeader(const String& name) const
863 {
864 return !staticData->m_forbiddenRequestHeaders.contains(name) && !name.startsWith(staticData->m_proxyHeaderPrefix, false)
865 && !name.startsWith(staticData->m_secHeaderPrefix, false);
866 }
867
getRequestHeader(const AtomicString & name) const868 String XMLHttpRequest::getRequestHeader(const AtomicString& name) const
869 {
870 return m_requestHeaders.get(name);
871 }
872
getAllResponseHeaders(ExceptionCode & ec) const873 String XMLHttpRequest::getAllResponseHeaders(ExceptionCode& ec) const
874 {
875 if (m_state < LOADING) {
876 ec = INVALID_STATE_ERR;
877 return "";
878 }
879
880 Vector<UChar> stringBuilder;
881
882 HTTPHeaderMap::const_iterator end = m_response.httpHeaderFields().end();
883 for (HTTPHeaderMap::const_iterator it = m_response.httpHeaderFields().begin(); it!= end; ++it) {
884 // Hide Set-Cookie header fields from the XMLHttpRequest client for these reasons:
885 // 1) If the client did have access to the fields, then it could read HTTP-only
886 // cookies; those cookies are supposed to be hidden from scripts.
887 // 2) There's no known harm in hiding Set-Cookie header fields entirely; we don't
888 // know any widely used technique that requires access to them.
889 // 3) Firefox has implemented this policy.
890 if (isSetCookieHeader(it->first) && !scriptExecutionContext()->securityOrigin()->canLoadLocalResources())
891 continue;
892
893 if (!m_sameOriginRequest && !isOnAccessControlResponseHeaderWhitelist(it->first))
894 continue;
895
896 stringBuilder.append(it->first.characters(), it->first.length());
897 stringBuilder.append(':');
898 stringBuilder.append(' ');
899 stringBuilder.append(it->second.characters(), it->second.length());
900 stringBuilder.append('\r');
901 stringBuilder.append('\n');
902 }
903
904 return String::adopt(stringBuilder);
905 }
906
getResponseHeader(const AtomicString & name,ExceptionCode & ec) const907 String XMLHttpRequest::getResponseHeader(const AtomicString& name, ExceptionCode& ec) const
908 {
909 if (m_state < LOADING) {
910 ec = INVALID_STATE_ERR;
911 return "";
912 }
913
914 if (!isValidToken(name))
915 return "";
916
917 // See comment in getAllResponseHeaders above.
918 if (isSetCookieHeader(name) && !scriptExecutionContext()->securityOrigin()->canLoadLocalResources()) {
919 reportUnsafeUsage(scriptExecutionContext(), "Refused to get unsafe header \"" + name + "\"");
920 return "";
921 }
922
923 if (!m_sameOriginRequest && !isOnAccessControlResponseHeaderWhitelist(name)) {
924 reportUnsafeUsage(scriptExecutionContext(), "Refused to get unsafe header \"" + name + "\"");
925 return "";
926 }
927
928 return m_response.httpHeaderField(name);
929 }
930
responseMIMEType() const931 String XMLHttpRequest::responseMIMEType() const
932 {
933 String mimeType = extractMIMETypeFromMediaType(m_mimeTypeOverride);
934 if (mimeType.isEmpty()) {
935 if (m_response.isHTTP())
936 mimeType = extractMIMETypeFromMediaType(m_response.httpHeaderField("Content-Type"));
937 else
938 mimeType = m_response.mimeType();
939 }
940 if (mimeType.isEmpty())
941 mimeType = "text/xml";
942
943 return mimeType;
944 }
945
responseIsXML() const946 bool XMLHttpRequest::responseIsXML() const
947 {
948 return DOMImplementation::isXMLMIMEType(responseMIMEType());
949 }
950
status(ExceptionCode & ec) const951 int XMLHttpRequest::status(ExceptionCode& ec) const
952 {
953 if (m_response.httpStatusCode())
954 return m_response.httpStatusCode();
955
956 if (m_state == OPENED) {
957 // Firefox only raises an exception in this state; we match it.
958 // 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.
959 ec = INVALID_STATE_ERR;
960 }
961
962 return 0;
963 }
964
statusText(ExceptionCode & ec) const965 String XMLHttpRequest::statusText(ExceptionCode& ec) const
966 {
967 if (!m_response.httpStatusText().isNull())
968 return m_response.httpStatusText();
969
970 if (m_state == OPENED) {
971 // See comments in status() above.
972 ec = INVALID_STATE_ERR;
973 }
974
975 return String();
976 }
977
didFail(const ResourceError & error)978 void XMLHttpRequest::didFail(const ResourceError& error)
979 {
980 if (m_didTellLoaderAboutRequest) {
981 cache()->loader()->nonCacheRequestComplete(m_url);
982 m_didTellLoaderAboutRequest = false;
983 }
984
985 // If we are already in an error state, for instance we called abort(), bail out early.
986 if (m_error)
987 return;
988
989 if (error.isCancellation()) {
990 m_exceptionCode = XMLHttpRequestException::ABORT_ERR;
991 abortError();
992 return;
993 }
994
995 m_exceptionCode = XMLHttpRequestException::NETWORK_ERR;
996 networkError();
997 }
998
didFailRedirectCheck()999 void XMLHttpRequest::didFailRedirectCheck()
1000 {
1001 networkError();
1002 }
1003
didFinishLoading(unsigned long identifier)1004 void XMLHttpRequest::didFinishLoading(unsigned long identifier)
1005 {
1006 if (m_didTellLoaderAboutRequest) {
1007 cache()->loader()->nonCacheRequestComplete(m_url);
1008 m_didTellLoaderAboutRequest = false;
1009 }
1010
1011 if (m_error)
1012 return;
1013
1014 if (m_inPreflight) {
1015 didFinishLoadingPreflight();
1016 return;
1017 }
1018
1019 if (m_state < HEADERS_RECEIVED)
1020 changeState(HEADERS_RECEIVED);
1021
1022 if (m_decoder)
1023 m_responseText += m_decoder->flush();
1024
1025 scriptExecutionContext()->resourceRetrievedByXMLHttpRequest(identifier, m_responseText);
1026 scriptExecutionContext()->addMessage(InspectorControllerDestination, JSMessageSource, LogMessageType, LogMessageLevel, "XHR finished loading: \"" + m_url + "\".", m_lastSendLineNumber, m_lastSendURL);
1027
1028 bool hadLoader = m_loader;
1029 m_loader = 0;
1030
1031 changeState(DONE);
1032 m_decoder = 0;
1033
1034 if (hadLoader)
1035 dropProtection();
1036 }
1037
didFinishLoadingPreflight()1038 void XMLHttpRequest::didFinishLoadingPreflight()
1039 {
1040 ASSERT(m_inPreflight);
1041 ASSERT(!m_sameOriginRequest);
1042
1043 // FIXME: this can probably be moved to didReceiveResponsePreflight.
1044 if (m_async)
1045 handleAsynchronousPreflightResult();
1046
1047 if (m_loader)
1048 unsetPendingActivity(this);
1049 }
1050
didSendData(unsigned long long bytesSent,unsigned long long totalBytesToBeSent)1051 void XMLHttpRequest::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
1052 {
1053 if (!m_upload)
1054 return;
1055
1056 if (m_uploadEventsAllowed)
1057 m_upload->dispatchProgressEvent(bytesSent, totalBytesToBeSent);
1058
1059 if (bytesSent == totalBytesToBeSent && !m_uploadComplete) {
1060 m_uploadComplete = true;
1061 if (m_uploadEventsAllowed)
1062 m_upload->dispatchLoadEvent();
1063 }
1064 }
1065
didReceiveResponse(const ResourceResponse & response)1066 void XMLHttpRequest::didReceiveResponse(const ResourceResponse& response)
1067 {
1068 if (m_inPreflight) {
1069 didReceiveResponsePreflight(response);
1070 return;
1071 }
1072
1073 if (!m_sameOriginRequest) {
1074 if (!passesAccessControlCheck(response, m_includeCredentials, scriptExecutionContext()->securityOrigin())) {
1075 networkError();
1076 return;
1077 }
1078 }
1079
1080 m_response = response;
1081 m_responseEncoding = extractCharsetFromMediaType(m_mimeTypeOverride);
1082 if (m_responseEncoding.isEmpty())
1083 m_responseEncoding = response.textEncodingName();
1084 }
1085
didReceiveResponsePreflight(const ResourceResponse & response)1086 void XMLHttpRequest::didReceiveResponsePreflight(const ResourceResponse& response)
1087 {
1088 ASSERT(m_inPreflight);
1089 ASSERT(!m_sameOriginRequest);
1090
1091 if (!passesAccessControlCheck(response, m_includeCredentials, scriptExecutionContext()->securityOrigin())) {
1092 networkError();
1093 return;
1094 }
1095
1096 OwnPtr<CrossOriginPreflightResultCacheItem> preflightResult(new CrossOriginPreflightResultCacheItem(m_includeCredentials));
1097 if (!preflightResult->parse(response)
1098 || !preflightResult->allowsCrossOriginMethod(m_method)
1099 || !preflightResult->allowsCrossOriginHeaders(m_requestHeaders)) {
1100 networkError();
1101 return;
1102 }
1103
1104 CrossOriginPreflightResultCache::shared().appendEntry(scriptExecutionContext()->securityOrigin()->toString(), m_url, preflightResult.release());
1105 }
1106
didReceiveAuthenticationCancellation(const ResourceResponse & failureResponse)1107 void XMLHttpRequest::didReceiveAuthenticationCancellation(const ResourceResponse& failureResponse)
1108 {
1109 m_response = failureResponse;
1110 }
1111
didReceiveData(const char * data,int len)1112 void XMLHttpRequest::didReceiveData(const char* data, int len)
1113 {
1114 if (m_inPreflight || m_error)
1115 return;
1116
1117 if (m_state < HEADERS_RECEIVED)
1118 changeState(HEADERS_RECEIVED);
1119
1120 if (!m_decoder) {
1121 if (!m_responseEncoding.isEmpty())
1122 m_decoder = TextResourceDecoder::create("text/plain", m_responseEncoding);
1123 // allow TextResourceDecoder to look inside the m_response if it's XML or HTML
1124 else if (responseIsXML()) {
1125 m_decoder = TextResourceDecoder::create("application/xml");
1126 // 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.
1127 m_decoder->useLenientXMLDecoding();
1128 } else if (responseMIMEType() == "text/html")
1129 m_decoder = TextResourceDecoder::create("text/html", "UTF-8");
1130 else
1131 m_decoder = TextResourceDecoder::create("text/plain", "UTF-8");
1132 }
1133
1134 if (!len)
1135 return;
1136
1137 if (len == -1)
1138 len = strlen(data);
1139
1140 m_responseText += m_decoder->decode(data, len);
1141
1142 if (!m_error) {
1143 updateAndDispatchOnProgress(len);
1144
1145 if (m_state != LOADING)
1146 changeState(LOADING);
1147 else
1148 // Firefox calls readyStateChanged every time it receives data, 4449442
1149 callReadyStateChangeListener();
1150 }
1151 }
1152
updateAndDispatchOnProgress(unsigned int len)1153 void XMLHttpRequest::updateAndDispatchOnProgress(unsigned int len)
1154 {
1155 long long expectedLength = m_response.expectedContentLength();
1156 m_receivedLength += len;
1157
1158 // FIXME: the spec requires that we dispatch the event according to the least
1159 // frequent method between every 350ms (+/-200ms) and for every byte received.
1160 dispatchProgressEvent(expectedLength);
1161 }
1162
dispatchReadyStateChangeEvent()1163 void XMLHttpRequest::dispatchReadyStateChangeEvent()
1164 {
1165 RefPtr<Event> evt = Event::create(eventNames().readystatechangeEvent, false, false);
1166 if (m_onReadyStateChangeListener) {
1167 evt->setTarget(this);
1168 evt->setCurrentTarget(this);
1169 m_onReadyStateChangeListener->handleEvent(evt.get(), false);
1170 }
1171
1172 ExceptionCode ec = 0;
1173 dispatchEvent(evt.release(), ec);
1174 ASSERT(!ec);
1175 }
1176
dispatchXMLHttpRequestProgressEvent(EventListener * listener,const AtomicString & type,bool lengthComputable,unsigned loaded,unsigned total)1177 void XMLHttpRequest::dispatchXMLHttpRequestProgressEvent(EventListener* listener, const AtomicString& type, bool lengthComputable, unsigned loaded, unsigned total)
1178 {
1179 RefPtr<XMLHttpRequestProgressEvent> evt = XMLHttpRequestProgressEvent::create(type, lengthComputable, loaded, total);
1180 if (listener) {
1181 evt->setTarget(this);
1182 evt->setCurrentTarget(this);
1183 listener->handleEvent(evt.get(), false);
1184 }
1185
1186 ExceptionCode ec = 0;
1187 dispatchEvent(evt.release(), ec);
1188 ASSERT(!ec);
1189 }
1190
dispatchAbortEvent()1191 void XMLHttpRequest::dispatchAbortEvent()
1192 {
1193 dispatchXMLHttpRequestProgressEvent(m_onAbortListener.get(), eventNames().abortEvent, false, 0, 0);
1194 }
1195
dispatchErrorEvent()1196 void XMLHttpRequest::dispatchErrorEvent()
1197 {
1198 dispatchXMLHttpRequestProgressEvent(m_onErrorListener.get(), eventNames().errorEvent, false, 0, 0);
1199 }
1200
dispatchLoadEvent()1201 void XMLHttpRequest::dispatchLoadEvent()
1202 {
1203 dispatchXMLHttpRequestProgressEvent(m_onLoadListener.get(), eventNames().loadEvent, false, 0, 0);
1204 }
1205
dispatchLoadStartEvent()1206 void XMLHttpRequest::dispatchLoadStartEvent()
1207 {
1208 dispatchXMLHttpRequestProgressEvent(m_onLoadStartListener.get(), eventNames().loadstartEvent, false, 0, 0);
1209 }
1210
dispatchProgressEvent(long long expectedLength)1211 void XMLHttpRequest::dispatchProgressEvent(long long expectedLength)
1212 {
1213 dispatchXMLHttpRequestProgressEvent(m_onProgressListener.get(), eventNames().progressEvent, expectedLength && m_receivedLength <= expectedLength,
1214 static_cast<unsigned>(m_receivedLength), static_cast<unsigned>(expectedLength));
1215 }
1216
canSuspend() const1217 bool XMLHttpRequest::canSuspend() const
1218 {
1219 return !m_loader;
1220 }
1221
stop()1222 void XMLHttpRequest::stop()
1223 {
1224 internalAbort();
1225 }
1226
contextDestroyed()1227 void XMLHttpRequest::contextDestroyed()
1228 {
1229 ASSERT(!m_loader);
1230 ActiveDOMObject::contextDestroyed();
1231 }
1232
scriptExecutionContext() const1233 ScriptExecutionContext* XMLHttpRequest::scriptExecutionContext() const
1234 {
1235 return ActiveDOMObject::scriptExecutionContext();
1236 }
1237
1238 } // namespace WebCore
1239