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