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