1 | /* |
---|
2 | * This file is part of the KDE libraries |
---|
3 | * Copyright (C) 2004, 2006 Apple Computer, Inc. |
---|
4 | * Copyright (C) 2005-2007 Alexey Proskuryakov <ap@webkit.org> |
---|
5 | * Copyright (C) 2007 Julien Chaffraix <julien.chaffraix@gmail.com> |
---|
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 "CString.h" |
---|
26 | #include "Cache.h" |
---|
27 | #include "DOMImplementation.h" |
---|
28 | #include "TextResourceDecoder.h" |
---|
29 | #include "Event.h" |
---|
30 | #include "EventListener.h" |
---|
31 | #include "EventNames.h" |
---|
32 | #include "ExceptionCode.h" |
---|
33 | #include "FormData.h" |
---|
34 | #include "Frame.h" |
---|
35 | #include "FrameLoader.h" |
---|
36 | #include "HTMLDocument.h" |
---|
37 | #include "HTTPParsers.h" |
---|
38 | #include "Page.h" |
---|
39 | #include "PlatformString.h" |
---|
40 | #include "RegularExpression.h" |
---|
41 | #include "ResourceHandle.h" |
---|
42 | #include "ResourceRequest.h" |
---|
43 | #include "Settings.h" |
---|
44 | #include "SubresourceLoader.h" |
---|
45 | #include "TextEncoding.h" |
---|
46 | #include "kjs_binding.h" |
---|
47 | #include <kjs/protect.h> |
---|
48 | #include <wtf/Vector.h> |
---|
49 | |
---|
50 | namespace WebCore { |
---|
51 | |
---|
52 | using namespace EventNames; |
---|
53 | |
---|
54 | typedef HashSet<XMLHttpRequest*> RequestsSet; |
---|
55 | |
---|
56 | static HashMap<Document*, RequestsSet*>& requestsByDocument() |
---|
57 | { |
---|
58 | static HashMap<Document*, RequestsSet*> map; |
---|
59 | return map; |
---|
60 | } |
---|
61 | |
---|
62 | static void addToRequestsByDocument(Document* doc, XMLHttpRequest* req) |
---|
63 | { |
---|
64 | ASSERT(doc); |
---|
65 | ASSERT(req); |
---|
66 | |
---|
67 | RequestsSet* requests = requestsByDocument().get(doc); |
---|
68 | if (!requests) { |
---|
69 | requests = new RequestsSet; |
---|
70 | requestsByDocument().set(doc, requests); |
---|
71 | } |
---|
72 | |
---|
73 | ASSERT(!requests->contains(req)); |
---|
74 | requests->add(req); |
---|
75 | } |
---|
76 | |
---|
77 | static void removeFromRequestsByDocument(Document* doc, XMLHttpRequest* req) |
---|
78 | { |
---|
79 | ASSERT(doc); |
---|
80 | ASSERT(req); |
---|
81 | |
---|
82 | RequestsSet* requests = requestsByDocument().get(doc); |
---|
83 | ASSERT(requests); |
---|
84 | ASSERT(requests->contains(req)); |
---|
85 | requests->remove(req); |
---|
86 | if (requests->isEmpty()) { |
---|
87 | requestsByDocument().remove(doc); |
---|
88 | delete requests; |
---|
89 | } |
---|
90 | } |
---|
91 | |
---|
92 | static bool canSetRequestHeader(const String& name) |
---|
93 | { |
---|
94 | static HashSet<String, CaseFoldingHash> forbiddenHeaders; |
---|
95 | static String proxyString("proxy-"); |
---|
96 | |
---|
97 | if (forbiddenHeaders.isEmpty()) { |
---|
98 | forbiddenHeaders.add("accept-charset"); |
---|
99 | forbiddenHeaders.add("accept-encoding"); |
---|
100 | forbiddenHeaders.add("connection"); |
---|
101 | forbiddenHeaders.add("content-length"); |
---|
102 | forbiddenHeaders.add("content-transfer-encoding"); |
---|
103 | forbiddenHeaders.add("date"); |
---|
104 | forbiddenHeaders.add("expect"); |
---|
105 | forbiddenHeaders.add("host"); |
---|
106 | forbiddenHeaders.add("keep-alive"); |
---|
107 | forbiddenHeaders.add("referer"); |
---|
108 | forbiddenHeaders.add("te"); |
---|
109 | forbiddenHeaders.add("trailer"); |
---|
110 | forbiddenHeaders.add("transfer-encoding"); |
---|
111 | forbiddenHeaders.add("upgrade"); |
---|
112 | forbiddenHeaders.add("via"); |
---|
113 | } |
---|
114 | |
---|
115 | return !forbiddenHeaders.contains(name) && !name.startsWith(proxyString, false); |
---|
116 | } |
---|
117 | |
---|
118 | // Determines if a string is a valid token, as defined by |
---|
119 | // "token" in section 2.2 of RFC 2616. |
---|
120 | static bool isValidToken(const String& name) |
---|
121 | { |
---|
122 | unsigned length = name.length(); |
---|
123 | for (unsigned i = 0; i < length; i++) { |
---|
124 | UChar c = name[i]; |
---|
125 | |
---|
126 | if (c >= 127 || c <= 32) |
---|
127 | return false; |
---|
128 | |
---|
129 | if (c == '(' || c == ')' || c == '<' || c == '>' || c == '@' || |
---|
130 | c == ',' || c == ';' || c == ':' || c == '\\' || c == '\"' || |
---|
131 | c == '/' || c == '[' || c == ']' || c == '?' || c == '=' || |
---|
132 | c == '{' || c == '}') |
---|
133 | return false; |
---|
134 | } |
---|
135 | |
---|
136 | return true; |
---|
137 | } |
---|
138 | |
---|
139 | static bool isValidHeaderValue(const String& name) |
---|
140 | { |
---|
141 | // FIXME: This should really match name against |
---|
142 | // field-value in section 4.2 of RFC 2616. |
---|
143 | |
---|
144 | return !name.contains('\r') && !name.contains('\n'); |
---|
145 | } |
---|
146 | |
---|
147 | XMLHttpRequestState XMLHttpRequest::getReadyState() const |
---|
148 | { |
---|
149 | return m_state; |
---|
150 | } |
---|
151 | |
---|
152 | const KJS::UString& XMLHttpRequest::getResponseText(ExceptionCode& ec) const |
---|
153 | { |
---|
154 | if (m_state < Receiving) |
---|
155 | ec = INVALID_STATE_ERR; |
---|
156 | |
---|
157 | return m_responseText; |
---|
158 | } |
---|
159 | |
---|
160 | Document* XMLHttpRequest::getResponseXML(ExceptionCode& ec) const |
---|
161 | { |
---|
162 | if (m_state != Loaded) { |
---|
163 | ec = INVALID_STATE_ERR; |
---|
164 | return 0; |
---|
165 | } |
---|
166 | |
---|
167 | if (!m_createdDocument) { |
---|
168 | if (m_response.isHTTP() && !responseIsXML()) { |
---|
169 | // The W3C spec requires this. |
---|
170 | m_responseXML = 0; |
---|
171 | } else { |
---|
172 | m_responseXML = m_doc->implementation()->createDocument(0); |
---|
173 | m_responseXML->open(); |
---|
174 | m_responseXML->setURL(m_url.deprecatedString()); |
---|
175 | // FIXME: set Last-Modified and cookies (currently, those are only available for HTMLDocuments). |
---|
176 | m_responseXML->write(String(m_responseText)); |
---|
177 | m_responseXML->finishParsing(); |
---|
178 | m_responseXML->close(); |
---|
179 | |
---|
180 | if (!m_responseXML->wellFormed()) |
---|
181 | m_responseXML = 0; |
---|
182 | } |
---|
183 | m_createdDocument = true; |
---|
184 | } |
---|
185 | |
---|
186 | return m_responseXML.get(); |
---|
187 | } |
---|
188 | |
---|
189 | EventListener* XMLHttpRequest::onReadyStateChangeListener() const |
---|
190 | { |
---|
191 | return m_onReadyStateChangeListener.get(); |
---|
192 | } |
---|
193 | |
---|
194 | void XMLHttpRequest::setOnReadyStateChangeListener(EventListener* eventListener) |
---|
195 | { |
---|
196 | m_onReadyStateChangeListener = eventListener; |
---|
197 | } |
---|
198 | |
---|
199 | EventListener* XMLHttpRequest::onLoadListener() const |
---|
200 | { |
---|
201 | return m_onLoadListener.get(); |
---|
202 | } |
---|
203 | |
---|
204 | void XMLHttpRequest::setOnLoadListener(EventListener* eventListener) |
---|
205 | { |
---|
206 | m_onLoadListener = eventListener; |
---|
207 | } |
---|
208 | |
---|
209 | void XMLHttpRequest::addEventListener(const AtomicString& eventType, PassRefPtr<EventListener> eventListener, bool) |
---|
210 | { |
---|
211 | EventListenersMap::iterator iter = m_eventListeners.find(eventType.impl()); |
---|
212 | if (iter == m_eventListeners.end()) { |
---|
213 | ListenerVector listeners; |
---|
214 | listeners.append(eventListener); |
---|
215 | m_eventListeners.add(eventType.impl(), listeners); |
---|
216 | } else { |
---|
217 | ListenerVector& listeners = iter->second; |
---|
218 | for (ListenerVector::iterator listenerIter = listeners.begin(); listenerIter != listeners.end(); ++listenerIter) |
---|
219 | if (*listenerIter == eventListener) |
---|
220 | return; |
---|
221 | |
---|
222 | listeners.append(eventListener); |
---|
223 | m_eventListeners.add(eventType.impl(), listeners); |
---|
224 | } |
---|
225 | } |
---|
226 | |
---|
227 | void XMLHttpRequest::removeEventListener(const AtomicString& eventType, EventListener* eventListener, bool) |
---|
228 | { |
---|
229 | EventListenersMap::iterator iter = m_eventListeners.find(eventType.impl()); |
---|
230 | if (iter == m_eventListeners.end()) |
---|
231 | return; |
---|
232 | |
---|
233 | ListenerVector& listeners = iter->second; |
---|
234 | for (ListenerVector::const_iterator listenerIter = listeners.begin(); listenerIter != listeners.end(); ++listenerIter) |
---|
235 | if (*listenerIter == eventListener) { |
---|
236 | listeners.remove(listenerIter - listeners.begin()); |
---|
237 | return; |
---|
238 | } |
---|
239 | } |
---|
240 | |
---|
241 | bool XMLHttpRequest::dispatchEvent(PassRefPtr<Event> evt, ExceptionCode& ec, bool /*tempEvent*/) |
---|
242 | { |
---|
243 | // FIXME: check for other error conditions enumerated in the spec. |
---|
244 | if (evt->type().isEmpty()) { |
---|
245 | ec = UNSPECIFIED_EVENT_TYPE_ERR; |
---|
246 | return true; |
---|
247 | } |
---|
248 | |
---|
249 | ListenerVector listenersCopy = m_eventListeners.get(evt->type().impl()); |
---|
250 | for (ListenerVector::const_iterator listenerIter = listenersCopy.begin(); listenerIter != listenersCopy.end(); ++listenerIter) { |
---|
251 | evt->setTarget(this); |
---|
252 | evt->setCurrentTarget(this); |
---|
253 | listenerIter->get()->handleEvent(evt.get(), false); |
---|
254 | } |
---|
255 | |
---|
256 | return !evt->defaultPrevented(); |
---|
257 | } |
---|
258 | |
---|
259 | XMLHttpRequest::XMLHttpRequest(Document* d) |
---|
260 | : m_doc(d) |
---|
261 | , m_async(true) |
---|
262 | , m_loader(0) |
---|
263 | , m_state(Uninitialized) |
---|
264 | , m_responseText("") |
---|
265 | , m_createdDocument(false) |
---|
266 | , m_aborted(false) |
---|
267 | { |
---|
268 | ASSERT(m_doc); |
---|
269 | addToRequestsByDocument(m_doc, this); |
---|
270 | } |
---|
271 | |
---|
272 | XMLHttpRequest::~XMLHttpRequest() |
---|
273 | { |
---|
274 | if (m_doc) |
---|
275 | removeFromRequestsByDocument(m_doc, this); |
---|
276 | } |
---|
277 | |
---|
278 | void XMLHttpRequest::changeState(XMLHttpRequestState newState) |
---|
279 | { |
---|
280 | if (m_state != newState) { |
---|
281 | m_state = newState; |
---|
282 | callReadyStateChangeListener(); |
---|
283 | } |
---|
284 | } |
---|
285 | |
---|
286 | void XMLHttpRequest::callReadyStateChangeListener() |
---|
287 | { |
---|
288 | if (!m_doc || !m_doc->frame()) |
---|
289 | return; |
---|
290 | |
---|
291 | RefPtr<Event> evt = new Event(readystatechangeEvent, false, false); |
---|
292 | if (m_onReadyStateChangeListener) { |
---|
293 | evt->setTarget(this); |
---|
294 | evt->setCurrentTarget(this); |
---|
295 | m_onReadyStateChangeListener->handleEvent(evt.get(), false); |
---|
296 | } |
---|
297 | |
---|
298 | ExceptionCode ec = 0; |
---|
299 | dispatchEvent(evt.release(), ec, false); |
---|
300 | ASSERT(!ec); |
---|
301 | |
---|
302 | if (m_state == Loaded) { |
---|
303 | evt = new Event(loadEvent, false, false); |
---|
304 | if (m_onLoadListener) { |
---|
305 | evt->setTarget(this); |
---|
306 | evt->setCurrentTarget(this); |
---|
307 | m_onLoadListener->handleEvent(evt.get(), false); |
---|
308 | } |
---|
309 | |
---|
310 | dispatchEvent(evt, ec, false); |
---|
311 | ASSERT(!ec); |
---|
312 | } |
---|
313 | } |
---|
314 | |
---|
315 | bool XMLHttpRequest::urlMatchesDocumentDomain(const KURL& url) const |
---|
316 | { |
---|
317 | // a local file can load anything |
---|
318 | if (m_doc->isAllowedToLoadLocalResources()) |
---|
319 | return true; |
---|
320 | |
---|
321 | // but a remote document can only load from the same port on the server |
---|
322 | KURL documentURL = m_doc->url(); |
---|
323 | if (documentURL.protocol().lower() == url.protocol().lower() |
---|
324 | && documentURL.host().lower() == url.host().lower() |
---|
325 | && documentURL.port() == url.port()) |
---|
326 | return true; |
---|
327 | |
---|
328 | return false; |
---|
329 | } |
---|
330 | |
---|
331 | void XMLHttpRequest::open(const String& method, const KURL& url, bool async, ExceptionCode& ec) |
---|
332 | { |
---|
333 | abort(); |
---|
334 | m_aborted = false; |
---|
335 | |
---|
336 | // clear stuff from possible previous load |
---|
337 | m_requestHeaders.clear(); |
---|
338 | m_response = ResourceResponse(); |
---|
339 | { |
---|
340 | KJS::JSLock lock; |
---|
341 | m_responseText = ""; |
---|
342 | } |
---|
343 | m_createdDocument = false; |
---|
344 | m_responseXML = 0; |
---|
345 | |
---|
346 | ASSERT(m_state == Uninitialized); |
---|
347 | |
---|
348 | if (!urlMatchesDocumentDomain(url)) { |
---|
349 | ec = PERMISSION_DENIED; |
---|
350 | return; |
---|
351 | } |
---|
352 | |
---|
353 | if (!isValidToken(method)) { |
---|
354 | ec = SYNTAX_ERR; |
---|
355 | return; |
---|
356 | } |
---|
357 | |
---|
358 | // Method names are case sensitive. But since Firefox uppercases method names it knows, we'll do the same. |
---|
359 | String methodUpper(method.upper()); |
---|
360 | |
---|
361 | if (methodUpper == "TRACE" || methodUpper == "TRACK" || methodUpper == "CONNECT") { |
---|
362 | ec = PERMISSION_DENIED; |
---|
363 | return; |
---|
364 | } |
---|
365 | |
---|
366 | m_url = url; |
---|
367 | |
---|
368 | if (methodUpper == "COPY" || methodUpper == "DELETE" || methodUpper == "GET" || methodUpper == "HEAD" |
---|
369 | || methodUpper == "INDEX" || methodUpper == "LOCK" || methodUpper == "M-POST" || methodUpper == "MKCOL" || methodUpper == "MOVE" |
---|
370 | || methodUpper == "OPTIONS" || methodUpper == "POST" || methodUpper == "PROPFIND" || methodUpper == "PROPPATCH" || methodUpper == "PUT" |
---|
371 | || methodUpper == "UNLOCK") |
---|
372 | m_method = methodUpper.deprecatedString(); |
---|
373 | else |
---|
374 | m_method = method.deprecatedString(); |
---|
375 | |
---|
376 | m_async = async; |
---|
377 | |
---|
378 | changeState(Open); |
---|
379 | } |
---|
380 | |
---|
381 | void XMLHttpRequest::open(const String& method, const KURL& url, bool async, const String& user, ExceptionCode& ec) |
---|
382 | { |
---|
383 | KURL urlWithCredentials(url); |
---|
384 | urlWithCredentials.setUser(user.deprecatedString()); |
---|
385 | |
---|
386 | open(method, urlWithCredentials, async, ec); |
---|
387 | } |
---|
388 | |
---|
389 | void XMLHttpRequest::open(const String& method, const KURL& url, bool async, const String& user, const String& password, ExceptionCode& ec) |
---|
390 | { |
---|
391 | KURL urlWithCredentials(url); |
---|
392 | urlWithCredentials.setUser(user.deprecatedString()); |
---|
393 | urlWithCredentials.setPass(password.deprecatedString()); |
---|
394 | |
---|
395 | open(method, urlWithCredentials, async, ec); |
---|
396 | } |
---|
397 | |
---|
398 | void XMLHttpRequest::send(const String& body, ExceptionCode& ec) |
---|
399 | { |
---|
400 | if (!m_doc) |
---|
401 | return; |
---|
402 | |
---|
403 | if (m_state != Open) { |
---|
404 | ec = INVALID_STATE_ERR; |
---|
405 | return; |
---|
406 | } |
---|
407 | |
---|
408 | // FIXME: Should this abort or raise an exception instead if we already have a m_loader going? |
---|
409 | if (m_loader) |
---|
410 | return; |
---|
411 | |
---|
412 | m_aborted = false; |
---|
413 | |
---|
414 | ResourceRequest request(m_url); |
---|
415 | request.setHTTPMethod(m_method); |
---|
416 | |
---|
417 | if (!body.isNull() && m_method != "GET" && m_method != "HEAD" && (m_url.protocol().lower() == "http" || m_url.protocol().lower() == "https")) { |
---|
418 | String contentType = getRequestHeader("Content-Type"); |
---|
419 | if (contentType.isEmpty()) { |
---|
420 | ExceptionCode ec = 0; |
---|
421 | Settings* settings = m_doc->settings(); |
---|
422 | if (settings && settings->usesDashboardBackwardCompatibilityMode()) |
---|
423 | setRequestHeader("Content-Type", "application/x-www-form-urlencoded", ec); |
---|
424 | else |
---|
425 | setRequestHeader("Content-Type", "application/xml", ec); |
---|
426 | ASSERT(ec == 0); |
---|
427 | } |
---|
428 | |
---|
429 | // FIXME: must use xmlEncoding for documents. |
---|
430 | String charset = "UTF-8"; |
---|
431 | |
---|
432 | TextEncoding m_encoding(charset); |
---|
433 | if (!m_encoding.isValid()) // FIXME: report an error? |
---|
434 | m_encoding = UTF8Encoding(); |
---|
435 | |
---|
436 | request.setHTTPBody(PassRefPtr<FormData>(new FormData(m_encoding.encode(body.characters(), body.length())))); |
---|
437 | } |
---|
438 | |
---|
439 | if (m_requestHeaders.size() > 0) |
---|
440 | request.addHTTPHeaderFields(m_requestHeaders); |
---|
441 | |
---|
442 | if (!m_async) { |
---|
443 | Vector<char> data; |
---|
444 | ResourceError error; |
---|
445 | ResourceResponse response; |
---|
446 | |
---|
447 | { |
---|
448 | // avoid deadlock in case the loader wants to use JS on a background thread |
---|
449 | KJS::JSLock::DropAllLocks dropLocks; |
---|
450 | if (m_doc->frame()) |
---|
451 | m_doc->frame()->loader()->loadResourceSynchronously(request, error, response, data); |
---|
452 | } |
---|
453 | |
---|
454 | m_loader = 0; |
---|
455 | |
---|
456 | // No exception for file:/// resources, see <rdar://problem/4962298>. |
---|
457 | // Also, if we have an HTTP response, then it wasn't a network error in fact. |
---|
458 | if (error.isNull() || request.url().isLocalFile() || response.httpStatusCode() > 0) |
---|
459 | processSyncLoadResults(data, response); |
---|
460 | else |
---|
461 | ec = NETWORK_ERR; |
---|
462 | |
---|
463 | return; |
---|
464 | } |
---|
465 | |
---|
466 | // Neither this object nor the JavaScript wrapper should be deleted while |
---|
467 | // a request is in progress because we need to keep the listeners alive, |
---|
468 | // and they are referenced by the JavaScript wrapper. |
---|
469 | ref(); |
---|
470 | { |
---|
471 | KJS::JSLock lock; |
---|
472 | gcProtectNullTolerant(KJS::ScriptInterpreter::getDOMObject(this)); |
---|
473 | } |
---|
474 | |
---|
475 | // create can return null here, for example if we're no longer attached to a page. |
---|
476 | // this is true while running onunload handlers |
---|
477 | // FIXME: Maybe create can return false for other reasons too? |
---|
478 | m_loader = SubresourceLoader::create(m_doc->frame(), this, request, false, true, false); |
---|
479 | } |
---|
480 | |
---|
481 | void XMLHttpRequest::abort() |
---|
482 | { |
---|
483 | bool hadLoader = m_loader; |
---|
484 | |
---|
485 | m_aborted = true; |
---|
486 | |
---|
487 | if (hadLoader) { |
---|
488 | m_loader->cancel(); |
---|
489 | m_loader = 0; |
---|
490 | } |
---|
491 | |
---|
492 | m_decoder = 0; |
---|
493 | |
---|
494 | if (hadLoader) |
---|
495 | dropProtection(); |
---|
496 | |
---|
497 | m_state = Uninitialized; |
---|
498 | } |
---|
499 | |
---|
500 | void XMLHttpRequest::dropProtection() |
---|
501 | { |
---|
502 | { |
---|
503 | KJS::JSLock lock; |
---|
504 | KJS::JSValue* wrapper = KJS::ScriptInterpreter::getDOMObject(this); |
---|
505 | KJS::gcUnprotectNullTolerant(wrapper); |
---|
506 | |
---|
507 | // the XHR object itself holds on to the responseText, and |
---|
508 | // thus has extra cost even independent of any |
---|
509 | // responseText or responseXML objects it has handed |
---|
510 | // out. But it is protected from GC while loading, so this |
---|
511 | // can't be recouped until the load is done, so only |
---|
512 | // report the extra cost at that point. |
---|
513 | |
---|
514 | if (wrapper) |
---|
515 | KJS::Collector::reportExtraMemoryCost(m_responseText.size() * 2); |
---|
516 | } |
---|
517 | |
---|
518 | deref(); |
---|
519 | } |
---|
520 | |
---|
521 | void XMLHttpRequest::overrideMIMEType(const String& override) |
---|
522 | { |
---|
523 | m_mimeTypeOverride = override; |
---|
524 | } |
---|
525 | |
---|
526 | void XMLHttpRequest::setRequestHeader(const String& name, const String& value, ExceptionCode& ec) |
---|
527 | { |
---|
528 | if (m_state != Open) { |
---|
529 | Settings* settings = m_doc ? m_doc->settings() : 0; |
---|
530 | if (settings && settings->usesDashboardBackwardCompatibilityMode()) |
---|
531 | return; |
---|
532 | |
---|
533 | ec = INVALID_STATE_ERR; |
---|
534 | return; |
---|
535 | } |
---|
536 | |
---|
537 | if (!isValidToken(name) || !isValidHeaderValue(value)) { |
---|
538 | ec = SYNTAX_ERR; |
---|
539 | return; |
---|
540 | } |
---|
541 | |
---|
542 | if (!canSetRequestHeader(name)) { |
---|
543 | if (m_doc && m_doc->frame() && m_doc->frame()->page()) |
---|
544 | m_doc->frame()->page()->chrome()->addMessageToConsole(JSMessageSource, ErrorMessageLevel, "Refused to set unsafe header " + name, 1, String()); |
---|
545 | return; |
---|
546 | } |
---|
547 | |
---|
548 | if (!m_requestHeaders.contains(name)) { |
---|
549 | m_requestHeaders.set(name, value); |
---|
550 | return; |
---|
551 | } |
---|
552 | |
---|
553 | String oldValue = m_requestHeaders.get(name); |
---|
554 | m_requestHeaders.set(name, oldValue + ", " + value); |
---|
555 | } |
---|
556 | |
---|
557 | String XMLHttpRequest::getRequestHeader(const String& name) const |
---|
558 | { |
---|
559 | return m_requestHeaders.get(name); |
---|
560 | } |
---|
561 | |
---|
562 | String XMLHttpRequest::getAllResponseHeaders(ExceptionCode& ec) const |
---|
563 | { |
---|
564 | if (m_state < Receiving) { |
---|
565 | ec = INVALID_STATE_ERR; |
---|
566 | return ""; |
---|
567 | } |
---|
568 | |
---|
569 | Vector<UChar> stringBuilder; |
---|
570 | String separator(": "); |
---|
571 | |
---|
572 | HTTPHeaderMap::const_iterator end = m_response.httpHeaderFields().end(); |
---|
573 | for (HTTPHeaderMap::const_iterator it = m_response.httpHeaderFields().begin(); it!= end; ++it) { |
---|
574 | stringBuilder.append(it->first.characters(), it->first.length()); |
---|
575 | stringBuilder.append(separator.characters(), separator.length()); |
---|
576 | stringBuilder.append(it->second.characters(), it->second.length()); |
---|
577 | stringBuilder.append((UChar)'\r'); |
---|
578 | stringBuilder.append((UChar)'\n'); |
---|
579 | } |
---|
580 | |
---|
581 | return String::adopt(stringBuilder); |
---|
582 | } |
---|
583 | |
---|
584 | String XMLHttpRequest::getResponseHeader(const String& name, ExceptionCode& ec) const |
---|
585 | { |
---|
586 | if (m_state < Receiving) { |
---|
587 | ec = INVALID_STATE_ERR; |
---|
588 | return ""; |
---|
589 | } |
---|
590 | |
---|
591 | if (!isValidToken(name)) |
---|
592 | return ""; |
---|
593 | |
---|
594 | return m_response.httpHeaderField(name); |
---|
595 | } |
---|
596 | |
---|
597 | String XMLHttpRequest::responseMIMEType() const |
---|
598 | { |
---|
599 | String mimeType = extractMIMETypeFromMediaType(m_mimeTypeOverride); |
---|
600 | if (mimeType.isEmpty()) { |
---|
601 | if (m_response.isHTTP()) |
---|
602 | mimeType = extractMIMETypeFromMediaType(m_response.httpHeaderField("Content-Type")); |
---|
603 | else |
---|
604 | mimeType = m_response.mimeType(); |
---|
605 | } |
---|
606 | if (mimeType.isEmpty()) |
---|
607 | mimeType = "text/xml"; |
---|
608 | |
---|
609 | return mimeType; |
---|
610 | } |
---|
611 | |
---|
612 | bool XMLHttpRequest::responseIsXML() const |
---|
613 | { |
---|
614 | return DOMImplementation::isXMLMIMEType(responseMIMEType()); |
---|
615 | } |
---|
616 | |
---|
617 | int XMLHttpRequest::getStatus(ExceptionCode& ec) const |
---|
618 | { |
---|
619 | if (m_state == Uninitialized) |
---|
620 | return 0; |
---|
621 | |
---|
622 | if (m_response.httpStatusCode() == 0) { |
---|
623 | if (m_state != Receiving && m_state != Loaded) |
---|
624 | // status MUST be available in these states, but we don't get any headers from non-HTTP requests |
---|
625 | ec = INVALID_STATE_ERR; |
---|
626 | } |
---|
627 | |
---|
628 | return m_response.httpStatusCode(); |
---|
629 | } |
---|
630 | |
---|
631 | String XMLHttpRequest::getStatusText(ExceptionCode& ec) const |
---|
632 | { |
---|
633 | if (m_state == Uninitialized) |
---|
634 | return ""; |
---|
635 | |
---|
636 | if (m_response.httpStatusCode() == 0) { |
---|
637 | if (m_state != Receiving && m_state != Loaded) |
---|
638 | // statusText MUST be available in these states, but we don't get any headers from non-HTTP requests |
---|
639 | ec = INVALID_STATE_ERR; |
---|
640 | return String(); |
---|
641 | } |
---|
642 | |
---|
643 | // FIXME: should try to preserve status text in response |
---|
644 | return "OK"; |
---|
645 | } |
---|
646 | |
---|
647 | void XMLHttpRequest::processSyncLoadResults(const Vector<char>& data, const ResourceResponse& response) |
---|
648 | { |
---|
649 | if (!urlMatchesDocumentDomain(response.url())) { |
---|
650 | abort(); |
---|
651 | return; |
---|
652 | } |
---|
653 | |
---|
654 | didReceiveResponse(0, response); |
---|
655 | changeState(Sent); |
---|
656 | if (m_aborted) |
---|
657 | return; |
---|
658 | |
---|
659 | const char* bytes = static_cast<const char*>(data.data()); |
---|
660 | int len = static_cast<int>(data.size()); |
---|
661 | |
---|
662 | didReceiveData(0, bytes, len); |
---|
663 | if (m_aborted) |
---|
664 | return; |
---|
665 | |
---|
666 | didFinishLoading(0); |
---|
667 | } |
---|
668 | |
---|
669 | void XMLHttpRequest::didFail(SubresourceLoader* loader, const ResourceError&) |
---|
670 | { |
---|
671 | didFinishLoading(loader); |
---|
672 | } |
---|
673 | |
---|
674 | void XMLHttpRequest::didFinishLoading(SubresourceLoader* loader) |
---|
675 | { |
---|
676 | if (m_aborted) |
---|
677 | return; |
---|
678 | |
---|
679 | ASSERT(loader == m_loader); |
---|
680 | |
---|
681 | if (m_state < Sent) |
---|
682 | changeState(Sent); |
---|
683 | |
---|
684 | { |
---|
685 | KJS::JSLock lock; |
---|
686 | if (m_decoder) |
---|
687 | m_responseText += m_decoder->flush(); |
---|
688 | } |
---|
689 | |
---|
690 | bool hadLoader = m_loader; |
---|
691 | m_loader = 0; |
---|
692 | |
---|
693 | changeState(Loaded); |
---|
694 | m_decoder = 0; |
---|
695 | |
---|
696 | if (hadLoader) |
---|
697 | dropProtection(); |
---|
698 | } |
---|
699 | |
---|
700 | void XMLHttpRequest::willSendRequest(SubresourceLoader*, ResourceRequest& request, const ResourceResponse& redirectResponse) |
---|
701 | { |
---|
702 | if (!urlMatchesDocumentDomain(request.url())) |
---|
703 | abort(); |
---|
704 | } |
---|
705 | |
---|
706 | void XMLHttpRequest::didReceiveResponse(SubresourceLoader*, const ResourceResponse& response) |
---|
707 | { |
---|
708 | m_response = response; |
---|
709 | m_encoding = extractCharsetFromMediaType(m_mimeTypeOverride); |
---|
710 | if (m_encoding.isEmpty()) |
---|
711 | m_encoding = response.textEncodingName(); |
---|
712 | |
---|
713 | } |
---|
714 | |
---|
715 | void XMLHttpRequest::receivedCancellation(SubresourceLoader*, const AuthenticationChallenge& challenge) |
---|
716 | { |
---|
717 | m_response = challenge.failureResponse(); |
---|
718 | } |
---|
719 | |
---|
720 | void XMLHttpRequest::didReceiveData(SubresourceLoader*, const char* data, int len) |
---|
721 | { |
---|
722 | if (m_state < Sent) |
---|
723 | changeState(Sent); |
---|
724 | |
---|
725 | if (!m_decoder) { |
---|
726 | if (!m_encoding.isEmpty()) |
---|
727 | m_decoder = new TextResourceDecoder("text/plain", m_encoding); |
---|
728 | // allow TextResourceDecoder to look inside the m_response if it's XML or HTML |
---|
729 | else if (responseIsXML()) |
---|
730 | m_decoder = new TextResourceDecoder("application/xml"); |
---|
731 | else if (responseMIMEType() == "text/html") |
---|
732 | m_decoder = new TextResourceDecoder("text/html", "UTF-8"); |
---|
733 | else |
---|
734 | m_decoder = new TextResourceDecoder("text/plain", "UTF-8"); |
---|
735 | } |
---|
736 | if (len == 0) |
---|
737 | return; |
---|
738 | |
---|
739 | if (len == -1) |
---|
740 | len = strlen(data); |
---|
741 | |
---|
742 | String decoded = m_decoder->decode(data, len); |
---|
743 | |
---|
744 | { |
---|
745 | KJS::JSLock lock; |
---|
746 | m_responseText += decoded; |
---|
747 | } |
---|
748 | |
---|
749 | if (!m_aborted) { |
---|
750 | if (m_state != Receiving) |
---|
751 | changeState(Receiving); |
---|
752 | else |
---|
753 | // Firefox calls readyStateChanged every time it receives data, 4449442 |
---|
754 | callReadyStateChangeListener(); |
---|
755 | } |
---|
756 | } |
---|
757 | |
---|
758 | void XMLHttpRequest::cancelRequests(Document* m_doc) |
---|
759 | { |
---|
760 | RequestsSet* requests = requestsByDocument().get(m_doc); |
---|
761 | if (!requests) |
---|
762 | return; |
---|
763 | RequestsSet copy = *requests; |
---|
764 | RequestsSet::const_iterator end = copy.end(); |
---|
765 | for (RequestsSet::const_iterator it = copy.begin(); it != end; ++it) |
---|
766 | (*it)->abort(); |
---|
767 | } |
---|
768 | |
---|
769 | void XMLHttpRequest::detachRequests(Document* m_doc) |
---|
770 | { |
---|
771 | RequestsSet* requests = requestsByDocument().get(m_doc); |
---|
772 | if (!requests) |
---|
773 | return; |
---|
774 | requestsByDocument().remove(m_doc); |
---|
775 | RequestsSet::const_iterator end = requests->end(); |
---|
776 | for (RequestsSet::const_iterator it = requests->begin(); it != end; ++it) { |
---|
777 | (*it)->m_doc = 0; |
---|
778 | (*it)->abort(); |
---|
779 | } |
---|
780 | delete requests; |
---|
781 | } |
---|
782 | |
---|
783 | } // end namespace |
---|