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