diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/page/EventSource.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/page/EventSource.cpp')
-rw-r--r-- | Source/WebCore/page/EventSource.cpp | 366 |
1 files changed, 169 insertions, 197 deletions
diff --git a/Source/WebCore/page/EventSource.cpp b/Source/WebCore/page/EventSource.cpp index 4ccfbe380..d5555ed2c 100644 --- a/Source/WebCore/page/EventSource.cpp +++ b/Source/WebCore/page/EventSource.cpp @@ -1,6 +1,6 @@ /* * Copyright (C) 2009, 2012 Ericsson AB. All rights reserved. - * Copyright (C) 2010 Apple Inc. All rights reserved. + * Copyright (C) 2010, 2016 Apple Inc. All rights reserved. * Copyright (C) 2011, Code Aurora Forum. All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,77 +34,50 @@ #include "EventSource.h" #include "ContentSecurityPolicy.h" -#include "DOMWindow.h" -#include "Dictionary.h" -#include "Document.h" -#include "Event.h" -#include "EventException.h" +#include "EventNames.h" #include "ExceptionCode.h" -#include "Frame.h" -#include "MemoryCache.h" #include "MessageEvent.h" #include "ResourceError.h" #include "ResourceRequest.h" #include "ResourceResponse.h" -#include "ScriptCallStack.h" -#include "ScriptController.h" #include "ScriptExecutionContext.h" #include "SecurityOrigin.h" -#include "SerializedScriptValue.h" #include "TextResourceDecoder.h" #include "ThreadableLoader.h" -#include <wtf/text/StringBuilder.h> namespace WebCore { -const unsigned long long EventSource::defaultReconnectDelay = 3000; +const uint64_t EventSource::defaultReconnectDelay = 3000; -inline EventSource::EventSource(ScriptExecutionContext& context, const URL& url, const Dictionary& eventSourceInit) +inline EventSource::EventSource(ScriptExecutionContext& context, const URL& url, const Init& eventSourceInit) : ActiveDOMObject(&context) , m_url(url) - , m_withCredentials(false) - , m_state(CONNECTING) - , m_decoder(TextResourceDecoder::create("text/plain", "UTF-8")) - , m_connectTimer(this, &EventSource::connectTimerFired) - , m_discardTrailingNewline(false) - , m_requestInFlight(false) - , m_reconnectDelay(defaultReconnectDelay) + , m_withCredentials(eventSourceInit.withCredentials) + , m_decoder(TextResourceDecoder::create(ASCIILiteral("text/plain"), "UTF-8")) + , m_connectTimer(*this, &EventSource::connect) { - eventSourceInit.get("withCredentials", m_withCredentials); } -PassRefPtr<EventSource> EventSource::create(ScriptExecutionContext& context, const String& url, const Dictionary& eventSourceInit, ExceptionCode& ec) +ExceptionOr<Ref<EventSource>> EventSource::create(ScriptExecutionContext& context, const String& url, const Init& eventSourceInit) { - if (url.isEmpty()) { - ec = SYNTAX_ERR; - return 0; - } + if (url.isEmpty()) + return Exception { SYNTAX_ERR }; URL fullURL = context.completeURL(url); - if (!fullURL.isValid()) { - ec = SYNTAX_ERR; - return 0; - } + if (!fullURL.isValid()) + return Exception { SYNTAX_ERR }; - // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved. - bool shouldBypassMainWorldContentSecurityPolicy = false; - if (context.isDocument()) { - Document& document = toDocument(context); - shouldBypassMainWorldContentSecurityPolicy = document.frame()->script().shouldBypassMainWorldContentSecurityPolicy(); - } - if (!shouldBypassMainWorldContentSecurityPolicy && !context.contentSecurityPolicy()->allowConnectToSource(fullURL)) { + // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is resolved. + if (!context.shouldBypassMainWorldContentSecurityPolicy() && !context.contentSecurityPolicy()->allowConnectToSource(fullURL)) { // FIXME: Should this be throwing an exception? - ec = SECURITY_ERR; - return 0; + return Exception { SECURITY_ERR }; } - RefPtr<EventSource> source = adoptRef(new EventSource(context, fullURL, eventSourceInit)); - - source->setPendingActivity(source.get()); + auto source = adoptRef(*new EventSource(context, fullURL, eventSourceInit)); + source->setPendingActivity(source.ptr()); source->scheduleInitialConnect(); source->suspendIfNeeded(); - - return source.release(); + return WTFMove(source); } EventSource::~EventSource() @@ -118,34 +91,33 @@ void EventSource::connect() ASSERT(m_state == CONNECTING); ASSERT(!m_requestInFlight); - ResourceRequest request(m_url); + ResourceRequest request { m_url }; request.setHTTPMethod("GET"); - request.setHTTPHeaderField("Accept", "text/event-stream"); - request.setHTTPHeaderField("Cache-Control", "no-cache"); + request.setHTTPHeaderField(HTTPHeaderName::Accept, "text/event-stream"); + request.setHTTPHeaderField(HTTPHeaderName::CacheControl, "no-cache"); if (!m_lastEventId.isEmpty()) - request.setHTTPHeaderField("Last-Event-ID", m_lastEventId); - - SecurityOrigin* origin = scriptExecutionContext()->securityOrigin(); + request.setHTTPHeaderField(HTTPHeaderName::LastEventID, m_lastEventId); ThreadableLoaderOptions options; options.sendLoadCallbacks = SendCallbacks; - options.sniffContent = DoNotSniffContent; - options.allowCredentials = (origin->canRequest(m_url) || m_withCredentials) ? AllowStoredCredentials : DoNotAllowStoredCredentials; + options.credentials = m_withCredentials ? FetchOptions::Credentials::Include : FetchOptions::Credentials::SameOrigin; options.preflightPolicy = PreventPreflight; - options.crossOriginRequestPolicy = UseAccessControl; + options.mode = FetchOptions::Mode::Cors; + options.cache = FetchOptions::Cache::NoStore; options.dataBufferingPolicy = DoNotBufferData; - options.securityOrigin = origin; + options.contentSecurityPolicyEnforcement = scriptExecutionContext()->shouldBypassMainWorldContentSecurityPolicy() ? ContentSecurityPolicyEnforcement::DoNotEnforce : ContentSecurityPolicyEnforcement::EnforceConnectSrcDirective; - m_loader = ThreadableLoader::create(scriptExecutionContext(), this, request, options); + ASSERT(scriptExecutionContext()); + m_loader = ThreadableLoader::create(*scriptExecutionContext(), *this, WTFMove(request), options); + // FIXME: Can we just use m_loader for this, null it out when it's no longer in flight, and eliminate the m_requestInFlight member? if (m_loader) m_requestInFlight = true; } void EventSource::networkRequestEnded() { - if (!m_requestInFlight) - return; + ASSERT(m_requestInFlight); m_requestInFlight = false; @@ -170,26 +142,6 @@ void EventSource::scheduleReconnect() dispatchEvent(Event::create(eventNames().errorEvent, false, false)); } -void EventSource::connectTimerFired(Timer<EventSource>&) -{ - connect(); -} - -String EventSource::url() const -{ - return m_url.string(); -} - -bool EventSource::withCredentials() const -{ - return m_withCredentials; -} - -EventSource::State EventSource::readyState() const -{ - return m_state; -} - void EventSource::close() { if (m_state == CLOSED) { @@ -209,46 +161,47 @@ void EventSource::close() } } +bool EventSource::responseIsValid(const ResourceResponse& response) const +{ + // Logs to the console as a side effect. + + // To keep the signal-to-noise ratio low, we don't log anything if the status code is not 200. + if (response.httpStatusCode() != 200) + return false; + + if (!equalLettersIgnoringASCIICase(response.mimeType(), "text/event-stream")) { + auto message = makeString("EventSource's response has a MIME type (\"", response.mimeType(), "\") that is not \"text/event-stream\". Aborting the connection."); + // FIXME: Console message would be better with a source code location; where would we get that? + scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, WTFMove(message)); + return false; + } + + // If we have a charset, the only allowed value is UTF-8 (case-insensitive). + auto& charset = response.textEncodingName(); + if (!charset.isEmpty() && !equalLettersIgnoringASCIICase(charset, "utf-8")) { + auto message = makeString("EventSource's response has a charset (\"", charset, "\") that is not UTF-8. Aborting the connection."); + // FIXME: Console message would be better with a source code location; where would we get that? + scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, WTFMove(message)); + return false; + } + + return true; +} + void EventSource::didReceiveResponse(unsigned long, const ResourceResponse& response) { ASSERT(m_state == CONNECTING); ASSERT(m_requestInFlight); - m_eventStreamOrigin = SecurityOrigin::create(response.url())->toString(); - int statusCode = response.httpStatusCode(); - bool mimeTypeIsValid = response.mimeType() == "text/event-stream"; - bool responseIsValid = statusCode == 200 && mimeTypeIsValid; - if (responseIsValid) { - const String& charset = response.textEncodingName(); - // If we have a charset, the only allowed value is UTF-8 (case-insensitive). - responseIsValid = charset.isEmpty() || equalIgnoringCase(charset, "UTF-8"); - if (!responseIsValid) { - StringBuilder message; - message.appendLiteral("EventSource's response has a charset (\""); - message.append(charset); - message.appendLiteral("\") that is not UTF-8. Aborting the connection."); - // FIXME: We are missing the source line. - scriptExecutionContext()->addConsoleMessage(JSMessageSource, ErrorMessageLevel, message.toString()); - } - } else { - // To keep the signal-to-noise ratio low, we only log 200-response with an invalid MIME type. - if (statusCode == 200 && !mimeTypeIsValid) { - StringBuilder message; - message.appendLiteral("EventSource's response has a MIME type (\""); - message.append(response.mimeType()); - message.appendLiteral("\") that is not \"text/event-stream\". Aborting the connection."); - // FIXME: We are missing the source line. - scriptExecutionContext()->addConsoleMessage(JSMessageSource, ErrorMessageLevel, message.toString()); - } - } - - if (responseIsValid) { - m_state = OPEN; - dispatchEvent(Event::create(eventNames().openEvent, false, false)); - } else { + if (!responseIsValid(response)) { m_loader->cancel(); dispatchEvent(Event::create(eventNames().errorEvent, false, false)); + return; } + + m_eventStreamOrigin = SecurityOrigin::create(response.url())->toString(); + m_state = OPEN; + dispatchEvent(Event::create(eventNames().openEvent, false, false)); } void EventSource::didReceiveData(const char* data, int length) @@ -256,7 +209,7 @@ void EventSource::didReceiveData(const char* data, int length) ASSERT(m_state == OPEN); ASSERT(m_requestInFlight); - append(m_receiveBuf, m_decoder->decode(data, length)); + append(m_receiveBuffer, m_decoder->decode(data, length)); parseEventStream(); } @@ -265,39 +218,40 @@ void EventSource::didFinishLoading(unsigned long, double) ASSERT(m_state == OPEN); ASSERT(m_requestInFlight); - if (m_receiveBuf.size() > 0 || m_data.size() > 0) { - parseEventStream(); + append(m_receiveBuffer, m_decoder->flush()); + parseEventStream(); + + // Discard everything that has not been dispatched by now. + // FIXME: Why does this need to be done? + // If this is important, why isn't it important to clear other data members: m_decoder, m_lastEventId, m_loader? + m_receiveBuffer.clear(); + m_data.clear(); + m_eventName = { }; + m_currentlyParsedEventId = { }; - // Discard everything that has not been dispatched by now. - m_receiveBuf.clear(); - m_data.clear(); - m_eventName = ""; - m_currentlyParsedEventId = String(); - } networkRequestEnded(); } void EventSource::didFail(const ResourceError& error) { ASSERT(m_state != CLOSED); + + if (error.isAccessControl()) { + String message = makeString("EventSource cannot load ", error.failingURL().string(), ". ", error.localizedDescription()); + scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, message); + + abortConnectionAttempt(); + return; + } + ASSERT(m_requestInFlight); if (error.isCancellation()) m_state = CLOSED; - networkRequestEnded(); -} -void EventSource::didFailAccessControlCheck(const ResourceError& error) -{ - String message = makeString("EventSource cannot load ", error.failingURL(), ". ", error.localizedDescription()); - scriptExecutionContext()->addConsoleMessage(JSMessageSource, ErrorMessageLevel, message); - - abortConnectionAttempt(); -} + // FIXME: Why don't we need to clear data members here as in didFinishLoading? -void EventSource::didFailRedirectCheck() -{ - abortConnectionAttempt(); + networkRequestEnded(); } void EventSource::abortConnectionAttempt() @@ -317,37 +271,37 @@ void EventSource::abortConnectionAttempt() void EventSource::parseEventStream() { - unsigned int bufPos = 0; - unsigned int bufSize = m_receiveBuf.size(); - while (bufPos < bufSize) { + unsigned position = 0; + unsigned size = m_receiveBuffer.size(); + while (position < size) { if (m_discardTrailingNewline) { - if (m_receiveBuf[bufPos] == '\n') - bufPos++; + if (m_receiveBuffer[position] == '\n') + ++position; m_discardTrailingNewline = false; } - int lineLength = -1; - int fieldLength = -1; - for (unsigned int i = bufPos; lineLength < 0 && i < bufSize; i++) { - switch (m_receiveBuf[i]) { + std::optional<unsigned> lineLength; + std::optional<unsigned> fieldLength; + for (unsigned i = position; !lineLength && i < size; ++i) { + switch (m_receiveBuffer[i]) { case ':': - if (fieldLength < 0) - fieldLength = i - bufPos; + if (!fieldLength) + fieldLength = i - position; break; case '\r': m_discardTrailingNewline = true; FALLTHROUGH; case '\n': - lineLength = i - bufPos; + lineLength = i - position; break; } } - if (lineLength < 0) + if (!lineLength) break; - parseEventStreamLine(bufPos, fieldLength, lineLength); - bufPos += lineLength + 1; + parseEventStreamLine(position, fieldLength, lineLength.value()); + position += lineLength.value() + 1; // EventSource.close() might've been called by one of the message event handlers. // Per spec, no further messages should be fired after that. @@ -355,57 +309,55 @@ void EventSource::parseEventStream() break; } - if (bufPos == bufSize) - m_receiveBuf.clear(); - else if (bufPos) - m_receiveBuf.remove(0, bufPos); + // FIXME: The following operation makes it clear that m_receiveBuffer should be some other type, + // perhaps a Deque or a circular buffer of some sort. + if (position == size) + m_receiveBuffer.clear(); + else if (position) + m_receiveBuffer.remove(0, position); } -void EventSource::parseEventStreamLine(unsigned bufPos, int fieldLength, int lineLength) +void EventSource::parseEventStreamLine(unsigned position, std::optional<unsigned> fieldLength, unsigned lineLength) { if (!lineLength) { - if (!m_data.isEmpty()) { - m_data.removeLast(); - if (!m_currentlyParsedEventId.isNull()) { - m_lastEventId.swap(m_currentlyParsedEventId); - m_currentlyParsedEventId = String(); - } - dispatchEvent(createMessageEvent()); - } - if (!m_eventName.isEmpty()) - m_eventName = ""; - } else if (fieldLength) { - bool noValue = fieldLength < 0; - - String field(&m_receiveBuf[bufPos], noValue ? lineLength : fieldLength); - int step; - if (noValue) - step = lineLength; - else if (m_receiveBuf[bufPos + fieldLength + 1] != ' ') - step = fieldLength + 1; - else - step = fieldLength + 2; - bufPos += step; - int valueLength = lineLength - step; - - if (field == "data") { - if (valueLength) - m_data.append(&m_receiveBuf[bufPos], valueLength); - m_data.append('\n'); - } else if (field == "event") - m_eventName = valueLength ? String(&m_receiveBuf[bufPos], valueLength) : ""; - else if (field == "id") - m_currentlyParsedEventId = valueLength ? String(&m_receiveBuf[bufPos], valueLength) : ""; - else if (field == "retry") { - if (!valueLength) - m_reconnectDelay = defaultReconnectDelay; - else { - String value(&m_receiveBuf[bufPos], valueLength); - bool ok; - unsigned long long retry = value.toUInt64(&ok); - if (ok) - m_reconnectDelay = retry; - } + if (!m_data.isEmpty()) + dispatchMessageEvent(); + m_eventName = { }; + return; + } + + if (fieldLength && !fieldLength.value()) + return; + + StringView field { &m_receiveBuffer[position], fieldLength ? fieldLength.value() : lineLength }; + + unsigned step; + if (!fieldLength) + step = lineLength; + else if (m_receiveBuffer[position + fieldLength.value() + 1] != ' ') + step = fieldLength.value() + 1; + else + step = fieldLength.value() + 2; + position += step; + unsigned valueLength = lineLength - step; + + if (field == "data") { + m_data.append(&m_receiveBuffer[position], valueLength); + m_data.append('\n'); + } else if (field == "event") + m_eventName = { &m_receiveBuffer[position], valueLength }; + else if (field == "id") + m_currentlyParsedEventId = { &m_receiveBuffer[position], valueLength }; + else if (field == "retry") { + if (!valueLength) + m_reconnectDelay = defaultReconnectDelay; + else { + // FIXME: Do we really want to ignore trailing garbage here? Should we be using the strict version instead? + // FIXME: If we can't parse the value, should we leave m_reconnectDelay alone or set it to defaultReconnectDelay? + bool ok; + auto reconnectDelay = charactersToUInt64(&m_receiveBuffer[position], valueLength, &ok); + if (ok) + m_reconnectDelay = reconnectDelay; } } } @@ -415,11 +367,31 @@ void EventSource::stop() close(); } -PassRefPtr<MessageEvent> EventSource::createMessageEvent() +const char* EventSource::activeDOMObjectName() const { - RefPtr<MessageEvent> event = MessageEvent::create(); - event->initMessageEvent(m_eventName.isEmpty() ? eventNames().messageEvent : AtomicString(m_eventName), false, false, SerializedScriptValue::create(String::adopt(m_data)), m_eventStreamOrigin, m_lastEventId, 0, 0); - return event.release(); + return "EventSource"; +} + +bool EventSource::canSuspendForDocumentSuspension() const +{ + // FIXME: We should return true here when we can because this object is not actually currently active. + return false; +} + +void EventSource::dispatchMessageEvent() +{ + if (!m_currentlyParsedEventId.isNull()) + m_lastEventId = WTFMove(m_currentlyParsedEventId); + + auto& name = m_eventName.isEmpty() ? eventNames().messageEvent : m_eventName; + + // Omit the trailing "\n" character. + ASSERT(!m_data.isEmpty()); + unsigned size = m_data.size() - 1; + auto data = SerializedScriptValue::create({ m_data.data(), size }); + m_data = { }; + + dispatchEvent(MessageEvent::create(name, WTFMove(data), m_eventStreamOrigin, m_lastEventId)); } } // namespace WebCore |