This source file includes following definitions.
- m_secHeaderPrefix
- isSetCookieHeader
- replaceCharsetInMediaType
- createXMLHttpRequestStaticData
- initializeXMLHttpRequestStaticData
- logConsoleError
- create
- m_securityOrigin
- document
- securityOrigin
- readyState
- responseText
- responseJSONSource
- responseXML
- responseBlob
- responseArrayBuffer
- responseStream
- setTimeout
- setResponseType
- responseType
- upload
- trackProgress
- changeState
- dispatchReadyStateChangeEvent
- setWithCredentials
- isAllowedHTTPMethod
- uppercaseKnownHTTPMethod
- isAllowedHTTPHeader
- open
- open
- open
- open
- initSend
- send
- areMethodAndURLValidForSend
- send
- send
- send
- send
- send
- send
- sendBytesData
- sendForInspectorXHRReplay
- createRequest
- abort
- clearVariablesForLoading
- internalAbort
- clearResponse
- clearRequest
- handleDidFailGeneric
- dispatchEventAndLoadEnd
- dispatchThrottledProgressEvent
- dispatchThrottledProgressEventSnapshot
- handleNetworkError
- handleDidCancel
- handleRequestError
- dropProtectionSoon
- dropProtection
- overrideMimeType
- setRequestHeader
- setRequestHeaderInternal
- getRequestHeader
- getAllResponseHeaders
- getResponseHeader
- responseMIMEType
- responseIsXML
- status
- statusText
- didFail
- didFailRedirectCheck
- didFinishLoading
- didSendData
- didReceiveResponse
- didReceiveData
- handleDidTimeout
- suspend
- resume
- stop
- contextDestroyed
- interfaceName
- executionContext
- trace
#include "config.h"
#include "core/xml/XMLHttpRequest.h"
#include "FetchInitiatorTypeNames.h"
#include "RuntimeEnabledFeatures.h"
#include "bindings/v8/ExceptionState.h"
#include "core/dom/ContextFeatures.h"
#include "core/dom/DOMImplementation.h"
#include "core/dom/ExceptionCode.h"
#include "core/dom/XMLDocument.h"
#include "core/editing/markup.h"
#include "core/events/Event.h"
#include "core/fetch/CrossOriginAccessControl.h"
#include "core/fileapi/Blob.h"
#include "core/fileapi/File.h"
#include "core/fileapi/Stream.h"
#include "core/frame/csp/ContentSecurityPolicy.h"
#include "core/html/DOMFormData.h"
#include "core/html/HTMLDocument.h"
#include "core/html/parser/TextResourceDecoder.h"
#include "core/inspector/InspectorInstrumentation.h"
#include "core/loader/ThreadableLoader.h"
#include "core/frame/Settings.h"
#include "core/xml/XMLHttpRequestProgressEvent.h"
#include "core/xml/XMLHttpRequestUpload.h"
#include "platform/Logging.h"
#include "platform/SharedBuffer.h"
#include "platform/blob/BlobData.h"
#include "platform/network/HTTPParsers.h"
#include "platform/network/ParsedContentType.h"
#include "platform/network/ResourceError.h"
#include "platform/network/ResourceRequest.h"
#include "public/platform/Platform.h"
#include "wtf/ArrayBuffer.h"
#include "wtf/ArrayBufferView.h"
#include "wtf/RefCountedLeakCounter.h"
#include "wtf/StdLibExtras.h"
#include "wtf/text/CString.h"
namespace WebCore {
DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, xmlHttpRequestCounter, ("XMLHttpRequest"));
enum XMLHttpRequestSendArrayBufferOrView {
XMLHttpRequestSendArrayBuffer,
XMLHttpRequestSendArrayBufferView,
XMLHttpRequestSendArrayBufferOrViewMax,
};
struct XMLHttpRequestStaticData {
WTF_MAKE_NONCOPYABLE(XMLHttpRequestStaticData); WTF_MAKE_FAST_ALLOCATED;
public:
XMLHttpRequestStaticData();
String m_proxyHeaderPrefix;
String m_secHeaderPrefix;
HashSet<String, CaseFoldingHash> m_forbiddenRequestHeaders;
};
XMLHttpRequestStaticData::XMLHttpRequestStaticData()
: m_proxyHeaderPrefix("proxy-")
, m_secHeaderPrefix("sec-")
{
m_forbiddenRequestHeaders.add("accept-charset");
m_forbiddenRequestHeaders.add("accept-encoding");
m_forbiddenRequestHeaders.add("access-control-request-headers");
m_forbiddenRequestHeaders.add("access-control-request-method");
m_forbiddenRequestHeaders.add("connection");
m_forbiddenRequestHeaders.add("content-length");
m_forbiddenRequestHeaders.add("content-transfer-encoding");
m_forbiddenRequestHeaders.add("cookie");
m_forbiddenRequestHeaders.add("cookie2");
m_forbiddenRequestHeaders.add("date");
m_forbiddenRequestHeaders.add("expect");
m_forbiddenRequestHeaders.add("host");
m_forbiddenRequestHeaders.add("keep-alive");
m_forbiddenRequestHeaders.add("origin");
m_forbiddenRequestHeaders.add("referer");
m_forbiddenRequestHeaders.add("te");
m_forbiddenRequestHeaders.add("trailer");
m_forbiddenRequestHeaders.add("transfer-encoding");
m_forbiddenRequestHeaders.add("upgrade");
m_forbiddenRequestHeaders.add("user-agent");
m_forbiddenRequestHeaders.add("via");
}
static bool isSetCookieHeader(const AtomicString& name)
{
return equalIgnoringCase(name, "set-cookie") || equalIgnoringCase(name, "set-cookie2");
}
static void replaceCharsetInMediaType(String& mediaType, const String& charsetValue)
{
unsigned pos = 0, len = 0;
findCharsetInMediaType(mediaType, pos, len);
if (!len) {
return;
}
while (len) {
mediaType.replace(pos, len, charsetValue);
unsigned start = pos + charsetValue.length();
findCharsetInMediaType(mediaType, pos, len, start);
}
}
static const XMLHttpRequestStaticData* staticData = 0;
static const XMLHttpRequestStaticData* createXMLHttpRequestStaticData()
{
staticData = new XMLHttpRequestStaticData;
return staticData;
}
static const XMLHttpRequestStaticData* initializeXMLHttpRequestStaticData()
{
AtomicallyInitializedStatic(const XMLHttpRequestStaticData*, dummy = createXMLHttpRequestStaticData());
return dummy;
}
static void logConsoleError(ExecutionContext* context, const String& message)
{
if (!context)
return;
context->addConsoleMessage(JSMessageSource, ErrorMessageLevel, message);
}
PassRefPtrWillBeRawPtr<XMLHttpRequest> XMLHttpRequest::create(ExecutionContext* context, PassRefPtr<SecurityOrigin> securityOrigin)
{
RefPtrWillBeRawPtr<XMLHttpRequest> xmlHttpRequest = adoptRefWillBeRefCountedGarbageCollected(new XMLHttpRequest(context, securityOrigin));
xmlHttpRequest->suspendIfNeeded();
return xmlHttpRequest.release();
}
XMLHttpRequest::XMLHttpRequest(ExecutionContext* context, PassRefPtr<SecurityOrigin> securityOrigin)
: ActiveDOMObject(context)
, m_async(true)
, m_includeCredentials(false)
, m_timeoutMilliseconds(0)
, m_state(UNSENT)
, m_createdDocument(false)
, m_error(false)
, m_uploadEventsAllowed(true)
, m_uploadComplete(false)
, m_sameOriginRequest(true)
, m_receivedLength(0)
, m_lastSendLineNumber(0)
, m_exceptionCode(0)
, m_progressEventThrottle(this)
, m_responseTypeCode(ResponseTypeDefault)
, m_dropProtectionRunner(this, &XMLHttpRequest::dropProtection)
, m_securityOrigin(securityOrigin)
{
initializeXMLHttpRequestStaticData();
#ifndef NDEBUG
xmlHttpRequestCounter.increment();
#endif
ScriptWrappable::init(this);
}
XMLHttpRequest::~XMLHttpRequest()
{
#ifndef NDEBUG
xmlHttpRequestCounter.decrement();
#endif
}
Document* XMLHttpRequest::document() const
{
ASSERT(executionContext()->isDocument());
return toDocument(executionContext());
}
SecurityOrigin* XMLHttpRequest::securityOrigin() const
{
return m_securityOrigin ? m_securityOrigin.get() : executionContext()->securityOrigin();
}
XMLHttpRequest::State XMLHttpRequest::readyState() const
{
return m_state;
}
ScriptString XMLHttpRequest::responseText(ExceptionState& exceptionState)
{
if (m_responseTypeCode != ResponseTypeDefault && m_responseTypeCode != ResponseTypeText) {
exceptionState.throwDOMException(InvalidStateError, "The value is only accessible if the object's 'responseType' is '' or 'text' (was '" + responseType() + "').");
return ScriptString();
}
if (m_error || (m_state != LOADING && m_state != DONE))
return ScriptString();
return m_responseText;
}
ScriptString XMLHttpRequest::responseJSONSource()
{
ASSERT(m_responseTypeCode == ResponseTypeJSON);
if (m_error || m_state != DONE)
return ScriptString();
return m_responseText;
}
Document* XMLHttpRequest::responseXML(ExceptionState& exceptionState)
{
if (m_responseTypeCode != ResponseTypeDefault && m_responseTypeCode != ResponseTypeDocument) {
exceptionState.throwDOMException(InvalidStateError, "The value is only accessible if the object's 'responseType' is '' or 'document' (was '" + responseType() + "').");
return 0;
}
if (m_error || m_state != DONE)
return 0;
if (!m_createdDocument) {
AtomicString mimeType = responseMIMEType();
bool isHTML = equalIgnoringCase(mimeType, "text/html");
if ((m_response.isHTTP() && !responseIsXML() && !isHTML)
|| (isHTML && m_responseTypeCode == ResponseTypeDefault)
|| executionContext()->isWorkerGlobalScope()) {
m_responseDocument = nullptr;
} else {
DocumentInit init = DocumentInit::fromContext(document()->contextDocument(), m_url);
if (isHTML)
m_responseDocument = HTMLDocument::create(init);
else
m_responseDocument = XMLDocument::create(init);
m_responseDocument->setContent(m_responseText.flattenToString());
m_responseDocument->setSecurityOrigin(securityOrigin());
m_responseDocument->setContextFeatures(document()->contextFeatures());
m_responseDocument->setMimeType(mimeType);
if (!m_responseDocument->wellFormed())
m_responseDocument = nullptr;
}
m_createdDocument = true;
}
return m_responseDocument.get();
}
Blob* XMLHttpRequest::responseBlob()
{
ASSERT(m_responseTypeCode == ResponseTypeBlob);
if (m_error || m_state != DONE)
return 0;
if (!m_responseBlob) {
OwnPtr<BlobData> blobData = BlobData::create();
size_t size = 0;
if (m_binaryResponseBuilder.get() && m_binaryResponseBuilder->size() > 0) {
RefPtr<RawData> rawData = RawData::create();
size = m_binaryResponseBuilder->size();
rawData->mutableData()->append(m_binaryResponseBuilder->data(), size);
blobData->appendData(rawData, 0, BlobDataItem::toEndOfFile);
blobData->setContentType(responseMIMEType());
m_binaryResponseBuilder.clear();
}
m_responseBlob = Blob::create(BlobDataHandle::create(blobData.release(), size));
}
return m_responseBlob.get();
}
ArrayBuffer* XMLHttpRequest::responseArrayBuffer()
{
ASSERT(m_responseTypeCode == ResponseTypeArrayBuffer);
if (m_error || m_state != DONE)
return 0;
if (!m_responseArrayBuffer.get()) {
if (m_binaryResponseBuilder.get() && m_binaryResponseBuilder->size() > 0) {
m_responseArrayBuffer = m_binaryResponseBuilder->getAsArrayBuffer();
m_binaryResponseBuilder.clear();
} else {
m_responseArrayBuffer = ArrayBuffer::create(static_cast<void*>(0), 0);
}
}
return m_responseArrayBuffer.get();
}
Stream* XMLHttpRequest::responseStream()
{
ASSERT(m_responseTypeCode == ResponseTypeStream);
if (m_error || (m_state != LOADING && m_state != DONE))
return 0;
return m_responseStream.get();
}
void XMLHttpRequest::setTimeout(unsigned long timeout, ExceptionState& exceptionState)
{
if (executionContext()->isDocument() && !m_async) {
exceptionState.throwDOMException(InvalidAccessError, "Timeouts cannot be set for synchronous requests made from a document.");
return;
}
m_timeoutMilliseconds = timeout;
}
void XMLHttpRequest::setResponseType(const String& responseType, ExceptionState& exceptionState)
{
if (m_state >= LOADING) {
exceptionState.throwDOMException(InvalidStateError, "The response type cannot be set if the object's state is LOADING or DONE.");
return;
}
if (!m_async && executionContext()->isDocument()) {
exceptionState.throwDOMException(InvalidAccessError, "The response type can only be changed for asynchronous HTTP requests made from a document.");
return;
}
if (responseType == "") {
m_responseTypeCode = ResponseTypeDefault;
} else if (responseType == "text") {
m_responseTypeCode = ResponseTypeText;
} else if (responseType == "json") {
m_responseTypeCode = ResponseTypeJSON;
} else if (responseType == "document") {
m_responseTypeCode = ResponseTypeDocument;
} else if (responseType == "blob") {
m_responseTypeCode = ResponseTypeBlob;
} else if (responseType == "arraybuffer") {
m_responseTypeCode = ResponseTypeArrayBuffer;
} else if (responseType == "stream") {
if (RuntimeEnabledFeatures::streamEnabled())
m_responseTypeCode = ResponseTypeStream;
else
return;
} else {
ASSERT_NOT_REACHED();
}
}
String XMLHttpRequest::responseType()
{
switch (m_responseTypeCode) {
case ResponseTypeDefault:
return "";
case ResponseTypeText:
return "text";
case ResponseTypeJSON:
return "json";
case ResponseTypeDocument:
return "document";
case ResponseTypeBlob:
return "blob";
case ResponseTypeArrayBuffer:
return "arraybuffer";
case ResponseTypeStream:
return "stream";
}
return "";
}
XMLHttpRequestUpload* XMLHttpRequest::upload()
{
if (!m_upload)
m_upload = XMLHttpRequestUpload::create(this);
return m_upload.get();
}
void XMLHttpRequest::trackProgress(int length)
{
m_receivedLength += length;
if (m_async)
dispatchThrottledProgressEventSnapshot(EventTypeNames::progress);
if (m_state != LOADING) {
changeState(LOADING);
} else {
dispatchReadyStateChangeEvent();
}
}
void XMLHttpRequest::changeState(State newState)
{
if (m_state != newState) {
m_state = newState;
dispatchReadyStateChangeEvent();
}
}
void XMLHttpRequest::dispatchReadyStateChangeEvent()
{
if (!executionContext())
return;
InspectorInstrumentationCookie cookie = InspectorInstrumentation::willDispatchXHRReadyStateChangeEvent(executionContext(), this);
if (m_async || (m_state <= OPENED || m_state == DONE)) {
ProgressEventAction flushAction = DoNotFlushProgressEvent;
if (m_state == DONE) {
if (m_error)
flushAction = FlushDeferredProgressEvent;
else
flushAction = FlushProgressEvent;
}
m_progressEventThrottle.dispatchReadyStateChangeEvent(XMLHttpRequestProgressEvent::create(EventTypeNames::readystatechange), flushAction);
}
InspectorInstrumentation::didDispatchXHRReadyStateChangeEvent(cookie);
if (m_state == DONE && !m_error) {
InspectorInstrumentationCookie cookie = InspectorInstrumentation::willDispatchXHRLoadEvent(executionContext(), this);
dispatchThrottledProgressEventSnapshot(EventTypeNames::load);
InspectorInstrumentation::didDispatchXHRLoadEvent(cookie);
dispatchThrottledProgressEventSnapshot(EventTypeNames::loadend);
}
}
void XMLHttpRequest::setWithCredentials(bool value, ExceptionState& exceptionState)
{
if (m_state > OPENED || m_loader) {
exceptionState.throwDOMException(InvalidStateError, "The value may only be set if the object's state is UNSENT or OPENED.");
return;
}
m_includeCredentials = value;
}
bool XMLHttpRequest::isAllowedHTTPMethod(const String& method)
{
return !equalIgnoringCase(method, "TRACE")
&& !equalIgnoringCase(method, "TRACK")
&& !equalIgnoringCase(method, "CONNECT");
}
AtomicString XMLHttpRequest::uppercaseKnownHTTPMethod(const AtomicString& method)
{
const char* const methods[] = {
"COPY",
"DELETE",
"GET",
"HEAD",
"INDEX",
"LOCK",
"M-POST",
"MKCOL",
"MOVE",
"OPTIONS",
"POST",
"PROPFIND",
"PROPPATCH",
"PUT",
"UNLOCK" };
for (unsigned i = 0; i < WTF_ARRAY_LENGTH(methods); ++i) {
if (equalIgnoringCase(method, methods[i])) {
if (method == methods[i])
return method;
return methods[i];
}
}
return method;
}
bool XMLHttpRequest::isAllowedHTTPHeader(const String& name)
{
initializeXMLHttpRequestStaticData();
return !staticData->m_forbiddenRequestHeaders.contains(name) && !name.startsWith(staticData->m_proxyHeaderPrefix, false)
&& !name.startsWith(staticData->m_secHeaderPrefix, false);
}
void XMLHttpRequest::open(const AtomicString& method, const KURL& url, ExceptionState& exceptionState)
{
open(method, url, true, exceptionState);
}
void XMLHttpRequest::open(const AtomicString& method, const KURL& url, bool async, ExceptionState& exceptionState)
{
WTF_LOG(Network, "XMLHttpRequest %p open('%s', '%s', %d)", this, method.utf8().data(), url.elidedString().utf8().data(), async);
if (!internalAbort())
return;
State previousState = m_state;
m_state = UNSENT;
m_error = false;
m_uploadComplete = false;
clearResponse();
clearRequest();
ASSERT(m_state == UNSENT);
if (!isValidHTTPToken(method)) {
exceptionState.throwDOMException(SyntaxError, "'" + method + "' is not a valid HTTP method.");
return;
}
if (!isAllowedHTTPMethod(method)) {
exceptionState.throwSecurityError("'" + method + "' HTTP method is unsupported.");
return;
}
if (!ContentSecurityPolicy::shouldBypassMainWorld(executionContext()) && !executionContext()->contentSecurityPolicy()->allowConnectToSource(url)) {
exceptionState.throwSecurityError("Refused to connect to '" + url.elidedString() + "' because it violates the document's Content Security Policy.");
return;
}
if (!async && executionContext()->isDocument()) {
if (document()->settings() && !document()->settings()->syncXHRInDocumentsEnabled()) {
exceptionState.throwDOMException(InvalidAccessError, "Synchronous requests are disabled for this page.");
return;
}
if (m_responseTypeCode != ResponseTypeDefault) {
exceptionState.throwDOMException(InvalidAccessError, "Synchronous requests from a document must not set a response type.");
return;
}
if (m_timeoutMilliseconds > 0) {
exceptionState.throwDOMException(InvalidAccessError, "Synchronous requests must not set a timeout.");
return;
}
}
m_method = uppercaseKnownHTTPMethod(method);
m_url = url;
m_async = async;
ASSERT(!m_loader);
if (previousState != OPENED)
changeState(OPENED);
else
m_state = OPENED;
}
void XMLHttpRequest::open(const AtomicString& method, const KURL& url, bool async, const String& user, ExceptionState& exceptionState)
{
KURL urlWithCredentials(url);
urlWithCredentials.setUser(user);
open(method, urlWithCredentials, async, exceptionState);
}
void XMLHttpRequest::open(const AtomicString& method, const KURL& url, bool async, const String& user, const String& password, ExceptionState& exceptionState)
{
KURL urlWithCredentials(url);
urlWithCredentials.setUser(user);
urlWithCredentials.setPass(password);
open(method, urlWithCredentials, async, exceptionState);
}
bool XMLHttpRequest::initSend(ExceptionState& exceptionState)
{
if (!executionContext())
return false;
if (m_state != OPENED || m_loader) {
exceptionState.throwDOMException(InvalidStateError, "The object's state must be OPENED.");
return false;
}
m_error = false;
return true;
}
void XMLHttpRequest::send(ExceptionState& exceptionState)
{
send(String(), exceptionState);
}
bool XMLHttpRequest::areMethodAndURLValidForSend()
{
return m_method != "GET" && m_method != "HEAD" && m_url.protocolIsInHTTPFamily();
}
void XMLHttpRequest::send(Document* document, ExceptionState& exceptionState)
{
WTF_LOG(Network, "XMLHttpRequest %p send() Document %p", this, document);
ASSERT(document);
if (!initSend(exceptionState))
return;
if (areMethodAndURLValidForSend()) {
if (getRequestHeader("Content-Type").isEmpty()) {
setRequestHeaderInternal("Content-Type", "application/xml");
}
String body = createMarkup(document);
m_requestEntityBody = FormData::create(UTF8Encoding().encode(body, WTF::EntitiesForUnencodables));
if (m_upload)
m_requestEntityBody->setAlwaysStream(true);
}
createRequest(exceptionState);
}
void XMLHttpRequest::send(const String& body, ExceptionState& exceptionState)
{
WTF_LOG(Network, "XMLHttpRequest %p send() String '%s'", this, body.utf8().data());
if (!initSend(exceptionState))
return;
if (!body.isNull() && areMethodAndURLValidForSend()) {
String contentType = getRequestHeader("Content-Type");
if (contentType.isEmpty()) {
setRequestHeaderInternal("Content-Type", "text/plain;charset=UTF-8");
} else {
replaceCharsetInMediaType(contentType, "UTF-8");
m_requestHeaders.set("Content-Type", AtomicString(contentType));
}
m_requestEntityBody = FormData::create(UTF8Encoding().encode(body, WTF::EntitiesForUnencodables));
if (m_upload)
m_requestEntityBody->setAlwaysStream(true);
}
createRequest(exceptionState);
}
void XMLHttpRequest::send(Blob* body, ExceptionState& exceptionState)
{
WTF_LOG(Network, "XMLHttpRequest %p send() Blob '%s'", this, body->uuid().utf8().data());
if (!initSend(exceptionState))
return;
if (areMethodAndURLValidForSend()) {
if (getRequestHeader("Content-Type").isEmpty()) {
const String& blobType = body->type();
if (!blobType.isEmpty() && isValidContentType(blobType))
setRequestHeaderInternal("Content-Type", AtomicString(blobType));
else {
setRequestHeaderInternal("Content-Type", "");
}
}
m_requestEntityBody = FormData::create();
if (body->hasBackingFile())
m_requestEntityBody->appendFile(toFile(body)->path());
else
m_requestEntityBody->appendBlob(body->uuid(), body->blobDataHandle());
}
createRequest(exceptionState);
}
void XMLHttpRequest::send(DOMFormData* body, ExceptionState& exceptionState)
{
WTF_LOG(Network, "XMLHttpRequest %p send() DOMFormData %p", this, body);
if (!initSend(exceptionState))
return;
if (areMethodAndURLValidForSend()) {
m_requestEntityBody = body->createMultiPartFormData(body->encoding());
if (getRequestHeader("Content-Type").isEmpty()) {
AtomicString contentType = AtomicString("multipart/form-data; boundary=", AtomicString::ConstructFromLiteral) + m_requestEntityBody->boundary().data();
setRequestHeaderInternal("Content-Type", contentType);
}
}
createRequest(exceptionState);
}
void XMLHttpRequest::send(ArrayBuffer* body, ExceptionState& exceptionState)
{
WTF_LOG(Network, "XMLHttpRequest %p send() ArrayBuffer %p", this, body);
String consoleMessage("ArrayBuffer is deprecated in XMLHttpRequest.send(). Use ArrayBufferView instead.");
executionContext()->addConsoleMessage(JSMessageSource, WarningMessageLevel, consoleMessage);
blink::Platform::current()->histogramEnumeration("WebCore.XHR.send.ArrayBufferOrView", XMLHttpRequestSendArrayBuffer, XMLHttpRequestSendArrayBufferOrViewMax);
sendBytesData(body->data(), body->byteLength(), exceptionState);
}
void XMLHttpRequest::send(ArrayBufferView* body, ExceptionState& exceptionState)
{
WTF_LOG(Network, "XMLHttpRequest %p send() ArrayBufferView %p", this, body);
blink::Platform::current()->histogramEnumeration("WebCore.XHR.send.ArrayBufferOrView", XMLHttpRequestSendArrayBufferView, XMLHttpRequestSendArrayBufferOrViewMax);
sendBytesData(body->baseAddress(), body->byteLength(), exceptionState);
}
void XMLHttpRequest::sendBytesData(const void* data, size_t length, ExceptionState& exceptionState)
{
if (!initSend(exceptionState))
return;
if (areMethodAndURLValidForSend()) {
m_requestEntityBody = FormData::create(data, length);
if (m_upload)
m_requestEntityBody->setAlwaysStream(true);
}
createRequest(exceptionState);
}
void XMLHttpRequest::sendForInspectorXHRReplay(PassRefPtr<FormData> formData, ExceptionState& exceptionState)
{
m_requestEntityBody = formData ? formData->deepCopy() : nullptr;
createRequest(exceptionState);
m_exceptionCode = exceptionState.code();
}
void XMLHttpRequest::createRequest(ExceptionState& exceptionState)
{
if (m_url.protocolIs("blob") && m_method != "GET") {
exceptionState.throwDOMException(NetworkError, "'GET' is the only method allowed for 'blob:' URLs.");
return;
}
bool uploadEvents = false;
if (m_async) {
m_progressEventThrottle.dispatchEvent(XMLHttpRequestProgressEvent::create(EventTypeNames::loadstart));
if (m_requestEntityBody && m_upload) {
uploadEvents = m_upload->hasEventListeners();
m_upload->dispatchEvent(XMLHttpRequestProgressEvent::create(EventTypeNames::loadstart));
}
}
m_sameOriginRequest = securityOrigin()->canRequest(m_url);
m_uploadEventsAllowed = m_sameOriginRequest || uploadEvents || !isSimpleCrossOriginAccessRequest(m_method, m_requestHeaders);
ResourceRequest request(m_url);
request.setHTTPMethod(m_method);
request.setTargetType(ResourceRequest::TargetIsXHR);
InspectorInstrumentation::willLoadXHR(executionContext(), this, this, m_method, m_url, m_async, m_requestEntityBody ? m_requestEntityBody->deepCopy() : nullptr, m_requestHeaders, m_includeCredentials);
if (m_requestEntityBody) {
ASSERT(m_method != "GET");
ASSERT(m_method != "HEAD");
request.setHTTPBody(m_requestEntityBody.release());
}
if (m_requestHeaders.size() > 0)
request.addHTTPHeaderFields(m_requestHeaders);
ThreadableLoaderOptions options;
options.sniffContent = DoNotSniffContent;
options.preflightPolicy = uploadEvents ? ForcePreflight : ConsiderPreflight;
options.allowCredentials = (m_sameOriginRequest || m_includeCredentials) ? AllowStoredCredentials : DoNotAllowStoredCredentials;
options.credentialsRequested = m_includeCredentials ? ClientRequestedCredentials : ClientDidNotRequestCredentials;
options.crossOriginRequestPolicy = UseAccessControl;
options.securityOrigin = securityOrigin();
options.initiator = FetchInitiatorTypeNames::xmlhttprequest;
options.contentSecurityPolicyEnforcement = ContentSecurityPolicy::shouldBypassMainWorld(executionContext()) ? DoNotEnforceContentSecurityPolicy : EnforceConnectSrcDirective;
options.mixedContentBlockingTreatment = TreatAsPassiveContent;
options.timeoutMilliseconds = m_timeoutMilliseconds;
m_exceptionCode = 0;
m_error = false;
if (m_async) {
if (m_upload)
request.setReportUploadProgress(true);
ASSERT(!m_loader);
m_loader = ThreadableLoader::create(executionContext(), this, request, options);
if (m_loader) {
setPendingActivity(this);
}
} else {
ThreadableLoader::loadResourceSynchronously(executionContext(), request, *this, options);
}
if (!m_exceptionCode && m_error)
m_exceptionCode = NetworkError;
if (m_exceptionCode)
exceptionState.throwDOMException(m_exceptionCode, "Failed to load '" + m_url.elidedString() + "'.");
}
void XMLHttpRequest::abort()
{
WTF_LOG(Network, "XMLHttpRequest %p abort()", this);
RefPtrWillBeRawPtr<XMLHttpRequest> protect(this);
bool sendFlag = m_loader;
long long expectedLength = m_response.expectedContentLength();
long long receivedLength = m_receivedLength;
if (!internalAbort())
return;
clearResponse();
m_requestHeaders.clear();
if (!((m_state <= OPENED && !sendFlag) || m_state == DONE)) {
ASSERT(!m_loader);
handleRequestError(0, EventTypeNames::abort, receivedLength, expectedLength);
}
m_state = UNSENT;
}
void XMLHttpRequest::clearVariablesForLoading()
{
m_decoder.clear();
m_responseEncoding = String();
}
bool XMLHttpRequest::internalAbort(DropProtection async)
{
m_error = true;
clearVariablesForLoading();
InspectorInstrumentation::didFailXHRLoading(executionContext(), this, this);
if (m_responseStream && m_state != DONE)
m_responseStream->abort();
if (!m_loader)
return true;
RefPtr<ThreadableLoader> loader = m_loader.release();
loader->cancel();
bool newLoadStarted = m_loader;
if (!newLoadStarted)
m_error = true;
if (async == DropProtectionAsync)
dropProtectionSoon();
else
dropProtection();
return !newLoadStarted;
}
void XMLHttpRequest::clearResponse()
{
m_receivedLength = 0;
m_response = ResourceResponse();
m_responseText.clear();
m_createdDocument = false;
m_responseDocument = nullptr;
m_responseBlob = nullptr;
m_responseStream = nullptr;
m_binaryResponseBuilder.clear();
m_responseArrayBuffer.clear();
}
void XMLHttpRequest::clearRequest()
{
m_requestHeaders.clear();
m_requestEntityBody = nullptr;
}
void XMLHttpRequest::handleDidFailGeneric()
{
clearResponse();
clearRequest();
m_error = true;
}
void XMLHttpRequest::dispatchEventAndLoadEnd(const AtomicString& type, long long receivedLength, long long expectedLength)
{
bool lengthComputable = expectedLength > 0 && receivedLength <= expectedLength;
unsigned long long loaded = receivedLength >= 0 ? static_cast<unsigned long long>(receivedLength) : 0;
unsigned long long total = lengthComputable ? static_cast<unsigned long long>(expectedLength) : 0;
m_progressEventThrottle.dispatchEventAndLoadEnd(type, lengthComputable, loaded, total);
}
void XMLHttpRequest::dispatchThrottledProgressEvent(const AtomicString& type, long long receivedLength, long long expectedLength)
{
bool lengthComputable = expectedLength > 0 && receivedLength <= expectedLength;
unsigned long long loaded = receivedLength >= 0 ? static_cast<unsigned long long>(receivedLength) : 0;
unsigned long long total = lengthComputable ? static_cast<unsigned long long>(expectedLength) : 0;
if (type == EventTypeNames::progress)
m_progressEventThrottle.dispatchProgressEvent(lengthComputable, loaded, total);
else
m_progressEventThrottle.dispatchEvent(XMLHttpRequestProgressEvent::create(type, lengthComputable, loaded, total));
}
void XMLHttpRequest::dispatchThrottledProgressEventSnapshot(const AtomicString& type)
{
return dispatchThrottledProgressEvent(type, m_receivedLength, m_response.expectedContentLength());
}
void XMLHttpRequest::handleNetworkError()
{
WTF_LOG(Network, "XMLHttpRequest %p handleNetworkError()", this);
long long expectedLength = m_response.expectedContentLength();
long long receivedLength = m_receivedLength;
handleDidFailGeneric();
handleRequestError(NetworkError, EventTypeNames::error, receivedLength, expectedLength);
internalAbort();
}
void XMLHttpRequest::handleDidCancel()
{
WTF_LOG(Network, "XMLHttpRequest %p handleDidCancel()", this);
long long expectedLength = m_response.expectedContentLength();
long long receivedLength = m_receivedLength;
handleDidFailGeneric();
handleRequestError(AbortError, EventTypeNames::abort, receivedLength, expectedLength);
}
void XMLHttpRequest::handleRequestError(ExceptionCode exceptionCode, const AtomicString& type, long long receivedLength, long long expectedLength)
{
WTF_LOG(Network, "XMLHttpRequest %p handleRequestError()", this);
if (!m_async && exceptionCode) {
m_state = DONE;
m_exceptionCode = exceptionCode;
return;
}
ASSERT(m_error);
changeState(DONE);
if (!m_uploadComplete) {
m_uploadComplete = true;
if (m_upload && m_uploadEventsAllowed)
m_upload->handleRequestError(type);
}
dispatchThrottledProgressEvent(EventTypeNames::progress, receivedLength, expectedLength);
dispatchEventAndLoadEnd(type, receivedLength, expectedLength);
}
void XMLHttpRequest::dropProtectionSoon()
{
m_dropProtectionRunner.runAsync();
}
void XMLHttpRequest::dropProtection()
{
unsetPendingActivity(this);
}
void XMLHttpRequest::overrideMimeType(const AtomicString& override)
{
m_mimeTypeOverride = override;
}
void XMLHttpRequest::setRequestHeader(const AtomicString& name, const AtomicString& value, ExceptionState& exceptionState)
{
if (m_state != OPENED || m_loader) {
exceptionState.throwDOMException(InvalidStateError, "The object's state must be OPENED.");
return;
}
if (!isValidHTTPToken(name)) {
exceptionState.throwDOMException(SyntaxError, "'" + name + "' is not a valid HTTP header field name.");
return;
}
if (!isValidHTTPHeaderValue(value)) {
exceptionState.throwDOMException(SyntaxError, "'" + value + "' is not a valid HTTP header field value.");
return;
}
if (!isAllowedHTTPHeader(name)) {
logConsoleError(executionContext(), "Refused to set unsafe header \"" + name + "\"");
return;
}
setRequestHeaderInternal(name, value);
}
void XMLHttpRequest::setRequestHeaderInternal(const AtomicString& name, const AtomicString& value)
{
HTTPHeaderMap::AddResult result = m_requestHeaders.add(name, value);
if (!result.isNewEntry)
result.storedValue->value = result.storedValue->value + ", " + value;
}
const AtomicString& XMLHttpRequest::getRequestHeader(const AtomicString& name) const
{
return m_requestHeaders.get(name);
}
String XMLHttpRequest::getAllResponseHeaders() const
{
if (m_state < HEADERS_RECEIVED || m_error)
return "";
StringBuilder stringBuilder;
HTTPHeaderSet accessControlExposeHeaderSet;
parseAccessControlExposeHeadersAllowList(m_response.httpHeaderField("Access-Control-Expose-Headers"), accessControlExposeHeaderSet);
HTTPHeaderMap::const_iterator end = m_response.httpHeaderFields().end();
for (HTTPHeaderMap::const_iterator it = m_response.httpHeaderFields().begin(); it!= end; ++it) {
if (isSetCookieHeader(it->key) && !securityOrigin()->canLoadLocalResources())
continue;
if (!m_sameOriginRequest && !isOnAccessControlResponseHeaderWhitelist(it->key) && !accessControlExposeHeaderSet.contains(it->key))
continue;
stringBuilder.append(it->key);
stringBuilder.append(':');
stringBuilder.append(' ');
stringBuilder.append(it->value);
stringBuilder.append('\r');
stringBuilder.append('\n');
}
return stringBuilder.toString();
}
const AtomicString& XMLHttpRequest::getResponseHeader(const AtomicString& name) const
{
if (m_state < HEADERS_RECEIVED || m_error)
return nullAtom;
if (isSetCookieHeader(name) && !securityOrigin()->canLoadLocalResources()) {
logConsoleError(executionContext(), "Refused to get unsafe header \"" + name + "\"");
return nullAtom;
}
HTTPHeaderSet accessControlExposeHeaderSet;
parseAccessControlExposeHeadersAllowList(m_response.httpHeaderField("Access-Control-Expose-Headers"), accessControlExposeHeaderSet);
if (!m_sameOriginRequest && !isOnAccessControlResponseHeaderWhitelist(name) && !accessControlExposeHeaderSet.contains(name)) {
logConsoleError(executionContext(), "Refused to get unsafe header \"" + name + "\"");
return nullAtom;
}
return m_response.httpHeaderField(name);
}
AtomicString XMLHttpRequest::responseMIMEType() const
{
AtomicString mimeType = extractMIMETypeFromMediaType(m_mimeTypeOverride);
if (mimeType.isEmpty()) {
if (m_response.isHTTP())
mimeType = extractMIMETypeFromMediaType(m_response.httpHeaderField("Content-Type"));
else
mimeType = m_response.mimeType();
}
if (mimeType.isEmpty())
mimeType = AtomicString("text/xml", AtomicString::ConstructFromLiteral);
return mimeType;
}
bool XMLHttpRequest::responseIsXML() const
{
return DOMImplementation::isXMLMIMEType(responseMIMEType());
}
int XMLHttpRequest::status() const
{
if (m_state == UNSENT || m_state == OPENED || m_error)
return 0;
if (m_response.httpStatusCode())
return m_response.httpStatusCode();
return 0;
}
String XMLHttpRequest::statusText() const
{
if (m_state == UNSENT || m_state == OPENED || m_error)
return String();
if (!m_response.httpStatusText().isNull())
return m_response.httpStatusText();
return String();
}
void XMLHttpRequest::didFail(const ResourceError& error)
{
WTF_LOG(Network, "XMLHttpRequest %p didFail()", this);
if (m_error)
return;
if (error.isCancellation()) {
handleDidCancel();
return;
}
if (error.isTimeout()) {
handleDidTimeout();
return;
}
if (error.domain() == errorDomainBlinkInternal)
logConsoleError(executionContext(), "XMLHttpRequest cannot load " + error.failingURL() + ". " + error.localizedDescription());
handleNetworkError();
}
void XMLHttpRequest::didFailRedirectCheck()
{
WTF_LOG(Network, "XMLHttpRequest %p didFailRedirectCheck()", this);
handleNetworkError();
}
void XMLHttpRequest::didFinishLoading(unsigned long identifier, double)
{
WTF_LOG(Network, "XMLHttpRequest %p didFinishLoading(%lu)", this, identifier);
if (m_error)
return;
if (m_state < HEADERS_RECEIVED)
changeState(HEADERS_RECEIVED);
if (m_decoder)
m_responseText = m_responseText.concatenateWith(m_decoder->flush());
if (m_responseStream)
m_responseStream->finalize();
clearVariablesForLoading();
InspectorInstrumentation::didFinishXHRLoading(executionContext(), this, this, identifier, m_responseText, m_method, m_url, m_lastSendURL, m_lastSendLineNumber);
RefPtrWillBeRawPtr<XMLHttpRequest> protect(this);
if (m_loader) {
m_loader = nullptr;
dropProtection();
}
changeState(DONE);
}
void XMLHttpRequest::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
{
WTF_LOG(Network, "XMLHttpRequest %p didSendData(%llu, %llu)", this, bytesSent, totalBytesToBeSent);
if (!m_upload)
return;
if (m_uploadEventsAllowed)
m_upload->dispatchProgressEvent(bytesSent, totalBytesToBeSent);
if (bytesSent == totalBytesToBeSent && !m_uploadComplete) {
m_uploadComplete = true;
if (m_uploadEventsAllowed)
m_upload->dispatchEventAndLoadEnd(EventTypeNames::load, true, bytesSent, totalBytesToBeSent);
}
}
void XMLHttpRequest::didReceiveResponse(unsigned long identifier, const ResourceResponse& response)
{
WTF_LOG(Network, "XMLHttpRequest %p didReceiveResponse(%lu)", this, identifier);
m_response = response;
if (!m_mimeTypeOverride.isEmpty()) {
m_response.setHTTPHeaderField("Content-Type", m_mimeTypeOverride);
m_responseEncoding = extractCharsetFromMediaType(m_mimeTypeOverride);
}
if (m_responseEncoding.isEmpty())
m_responseEncoding = response.textEncodingName();
}
void XMLHttpRequest::didReceiveData(const char* data, int len)
{
if (m_error)
return;
if (m_state < HEADERS_RECEIVED)
changeState(HEADERS_RECEIVED);
bool useDecoder = m_responseTypeCode == ResponseTypeDefault || m_responseTypeCode == ResponseTypeText || m_responseTypeCode == ResponseTypeJSON || m_responseTypeCode == ResponseTypeDocument;
if (useDecoder && !m_decoder) {
if (m_responseTypeCode == ResponseTypeJSON)
m_decoder = TextResourceDecoder::create("application/json", "UTF-8");
else if (!m_responseEncoding.isEmpty())
m_decoder = TextResourceDecoder::create("text/plain", m_responseEncoding);
else if (responseIsXML()) {
m_decoder = TextResourceDecoder::create("application/xml");
m_decoder->useLenientXMLDecoding();
} else if (equalIgnoringCase(responseMIMEType(), "text/html"))
m_decoder = TextResourceDecoder::create("text/html", "UTF-8");
else
m_decoder = TextResourceDecoder::create("text/plain", "UTF-8");
}
if (!len)
return;
if (len == -1)
len = strlen(data);
if (useDecoder) {
m_responseText = m_responseText.concatenateWith(m_decoder->decode(data, len));
} else if (m_responseTypeCode == ResponseTypeArrayBuffer || m_responseTypeCode == ResponseTypeBlob) {
if (!m_binaryResponseBuilder)
m_binaryResponseBuilder = SharedBuffer::create();
m_binaryResponseBuilder->append(data, len);
} else if (m_responseTypeCode == ResponseTypeStream) {
if (!m_responseStream)
m_responseStream = Stream::create(executionContext(), responseMIMEType());
m_responseStream->addData(data, len);
}
if (m_error)
return;
trackProgress(len);
}
void XMLHttpRequest::handleDidTimeout()
{
WTF_LOG(Network, "XMLHttpRequest %p handleDidTimeout()", this);
RefPtrWillBeRawPtr<XMLHttpRequest> protect(this);
long long expectedLength = m_response.expectedContentLength();
long long receivedLength = m_receivedLength;
if (!internalAbort())
return;
handleDidFailGeneric();
handleRequestError(TimeoutError, EventTypeNames::timeout, receivedLength, expectedLength);
}
void XMLHttpRequest::suspend()
{
m_progressEventThrottle.suspend();
}
void XMLHttpRequest::resume()
{
m_progressEventThrottle.resume();
}
void XMLHttpRequest::stop()
{
internalAbort(DropProtectionAsync);
}
void XMLHttpRequest::contextDestroyed()
{
ASSERT(!m_loader);
ActiveDOMObject::contextDestroyed();
}
const AtomicString& XMLHttpRequest::interfaceName() const
{
return EventTargetNames::XMLHttpRequest;
}
ExecutionContext* XMLHttpRequest::executionContext() const
{
return ActiveDOMObject::executionContext();
}
void XMLHttpRequest::trace(Visitor* visitor)
{
visitor->trace(m_responseBlob);
visitor->trace(m_responseStream);
visitor->trace(m_progressEventThrottle);
}
}