1/* 2 * Copyright (C) 2004, 2006, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2005-2007 Alexey Proskuryakov <ap@webkit.org> 4 * Copyright (C) 2007, 2008 Julien Chaffraix <jchaffraix@webkit.org> 5 * Copyright (C) 2008 David Levin <levin@chromium.org> 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this library; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 */ 21 22#include "config.h" 23#include "XMLHttpRequest.h" 24 25#include "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 63namespace WebCore { 64 65#ifndef NDEBUG 66static WTF::RefCountedLeakCounter xmlHttpRequestCounter("XMLHttpRequest"); 67#endif 68 69struct XMLHttpRequestStaticData { 70 WTF_MAKE_NONCOPYABLE(XMLHttpRequestStaticData); WTF_MAKE_FAST_ALLOCATED; 71public: 72 XMLHttpRequestStaticData(); 73 String m_proxyHeaderPrefix; 74 String m_secHeaderPrefix; 75 HashSet<String, CaseFoldingHash> m_forbiddenRequestHeaders; 76}; 77 78XMLHttpRequestStaticData::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. 107static 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 126static 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 134static bool isSetCookieHeader(const AtomicString& name) 135{ 136 return equalIgnoringCase(name, "set-cookie") || equalIgnoringCase(name, "set-cookie2"); 137} 138 139static 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 158static const XMLHttpRequestStaticData* staticData = 0; 159 160static const XMLHttpRequestStaticData* createXMLHttpRequestStaticData() 161{ 162 staticData = new XMLHttpRequestStaticData; 163 return staticData; 164} 165 166static 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 173XMLHttpRequest::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 195XMLHttpRequest::~XMLHttpRequest() 196{ 197 if (m_upload) 198 m_upload->disconnectXMLHttpRequest(); 199 200#ifndef NDEBUG 201 xmlHttpRequestCounter.decrement(); 202#endif 203} 204 205Document* XMLHttpRequest::document() const 206{ 207 ASSERT(scriptExecutionContext()->isDocument()); 208 return static_cast<Document*>(scriptExecutionContext()); 209} 210 211#if ENABLE(DASHBOARD_SUPPORT) 212bool 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 221XMLHttpRequest::State XMLHttpRequest::readyState() const 222{ 223 return m_state; 224} 225 226String 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 235Document* 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) 264Blob* 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 274ArrayBuffer* 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 295void 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 318String 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 335XMLHttpRequestUpload* XMLHttpRequest::upload() 336{ 337 if (!m_upload) 338 m_upload = XMLHttpRequestUpload::create(this); 339 return m_upload.get(); 340} 341 342void XMLHttpRequest::changeState(State newState) 343{ 344 if (m_state != newState) { 345 m_state = newState; 346 callReadyStateChangeListener(); 347 } 348} 349 350void 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 369void 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) 380void 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 391void XMLHttpRequest::open(const String& method, const KURL& url, ExceptionCode& ec) 392{ 393 open(method, url, true, ec); 394} 395 396void 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 446void 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 454void 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 463bool 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 477void XMLHttpRequest::send(ExceptionCode& ec) 478{ 479 send(String(), ec); 480} 481 482void 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 515void 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 542void 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 562void 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 585void 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 599void 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 672void 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 702void 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 722void XMLHttpRequest::clearResponse() 723{ 724 m_response = ResourceResponse(); 725 clearResponseBuffers(); 726} 727 728void 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 740void XMLHttpRequest::clearRequest() 741{ 742 m_requestHeaders.clear(); 743 m_requestEntityBody = 0; 744} 745 746void XMLHttpRequest::genericError() 747{ 748 clearResponse(); 749 clearRequest(); 750 m_error = true; 751 752 changeState(DONE); 753} 754 755void 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 767void 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 778void 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 795void XMLHttpRequest::overrideMimeType(const String& override) 796{ 797 m_mimeTypeOverride = override; 798} 799 800static 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 809void 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 835void 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 842bool 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 848String XMLHttpRequest::getRequestHeader(const AtomicString& name) const 849{ 850 return m_requestHeaders.get(name); 851} 852 853String 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 887String 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 907String 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 922bool XMLHttpRequest::responseIsXML() const 923{ 924 return DOMImplementation::isXMLMIMEType(responseMIMEType()); 925} 926 927int 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 941String 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 954void 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 975void XMLHttpRequest::didFailRedirectCheck() 976{ 977 networkError(); 978} 979 980void 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 1009void 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 1024void 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 1032void XMLHttpRequest::didReceiveAuthenticationCancellation(const ResourceResponse& failureResponse) 1033{ 1034 m_response = failureResponse; 1035} 1036 1037void 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 1093bool XMLHttpRequest::canSuspend() const 1094{ 1095 return !m_loader; 1096} 1097 1098void XMLHttpRequest::suspend(ReasonForSuspension) 1099{ 1100 m_progressEventThrottle.suspend(); 1101} 1102 1103void XMLHttpRequest::resume() 1104{ 1105 m_progressEventThrottle.resume(); 1106} 1107 1108void XMLHttpRequest::stop() 1109{ 1110 internalAbort(); 1111} 1112 1113void XMLHttpRequest::contextDestroyed() 1114{ 1115 ASSERT(!m_loader); 1116 ActiveDOMObject::contextDestroyed(); 1117} 1118 1119ScriptExecutionContext* XMLHttpRequest::scriptExecutionContext() const 1120{ 1121 return ActiveDOMObject::scriptExecutionContext(); 1122} 1123 1124EventTargetData* XMLHttpRequest::eventTargetData() 1125{ 1126 return &m_eventTargetData; 1127} 1128 1129EventTargetData* XMLHttpRequest::ensureEventTargetData() 1130{ 1131 return &m_eventTargetData; 1132} 1133 1134} // namespace WebCore 1135