10e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org/*
20e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * Copyright (C) 2009, 2012 Ericsson AB. All rights reserved.
30e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * Copyright (C) 2010 Apple Inc. All rights reserved.
40e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * Copyright (C) 2011, Code Aurora Forum. All rights reserved.
50e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org *
60e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * Redistribution and use in source and binary forms, with or without
70e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * modification, are permitted provided that the following conditions
80e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * are met:
90e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org *
100e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * 1. Redistributions of source code must retain the above copyright
110e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org *    notice, this list of conditions and the following disclaimer.
120e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * 2. Redistributions in binary form must reproduce the above copyright
130e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org *    notice, this list of conditions and the following disclaimer
140e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org *    in the documentation and/or other materials provided with the
150e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org *    distribution.
160e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * 3. Neither the name of Ericsson nor the names of its contributors
170e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org *    may be used to endorse or promote products derived from this
180e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org *    software without specific prior written permission.
190e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org *
200e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
210e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
220e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
230e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
240e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
250e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
260e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
270e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
280e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
290e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
300e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
310e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org */
320e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org
330e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org#include "config.h"
340e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org#include "core/page/EventSource.h"
350e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org
360e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org#include "bindings/core/v8/ExceptionState.h"
370e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org#include "bindings/core/v8/ScriptController.h"
380e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org#include "bindings/core/v8/SerializedScriptValue.h"
390e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org#include "core/dom/Document.h"
400e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org#include "core/dom/ExceptionCode.h"
410e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org#include "core/dom/ExecutionContext.h"
420e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org#include "core/events/Event.h"
430e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org#include "core/events/MessageEvent.h"
440e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org#include "core/frame/LocalDOMWindow.h"
450e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org#include "core/frame/LocalFrame.h"
46ce87dc3ad8b1990c02fe9b75343056a937ab49defischman@webrtc.org#include "core/frame/csp/ContentSecurityPolicy.h"
47ce87dc3ad8b1990c02fe9b75343056a937ab49defischman@webrtc.org#include "core/html/parser/TextResourceDecoder.h"
48ce87dc3ad8b1990c02fe9b75343056a937ab49defischman@webrtc.org#include "core/inspector/ConsoleMessage.h"
49a846c2043a8abdbc8fd1f7511d824e6a64a8baf5glaznev@webrtc.org#include "core/loader/ThreadableLoader.h"
50a846c2043a8abdbc8fd1f7511d824e6a64a8baf5glaznev@webrtc.org#include "core/page/EventSourceInit.h"
51a846c2043a8abdbc8fd1f7511d824e6a64a8baf5glaznev@webrtc.org#include "platform/network/ResourceError.h"
52ce87dc3ad8b1990c02fe9b75343056a937ab49defischman@webrtc.org#include "platform/network/ResourceRequest.h"
53a846c2043a8abdbc8fd1f7511d824e6a64a8baf5glaznev@webrtc.org#include "platform/network/ResourceResponse.h"
54a846c2043a8abdbc8fd1f7511d824e6a64a8baf5glaznev@webrtc.org#include "platform/weborigin/SecurityOrigin.h"
550e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org#include "public/platform/WebURLRequest.h"
560e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org#include "wtf/text/StringBuilder.h"
570e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org
580e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.orgnamespace blink {
590e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org
600e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.orgconst unsigned long long EventSource::defaultReconnectDelay = 3000;
610e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org
620e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.orginline EventSource::EventSource(ExecutionContext* context, const KURL& url, const EventSourceInit& eventSourceInit)
630e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    : ActiveDOMObject(context)
640e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    , m_url(url)
650e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    , m_withCredentials(eventSourceInit.withCredentials())
660e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    , m_state(CONNECTING)
670e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    , m_decoder(TextResourceDecoder::create("text/plain", "UTF-8"))
680e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    , m_connectTimer(this, &EventSource::connectTimerFired)
690e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    , m_discardTrailingNewline(false)
700e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    , m_requestInFlight(false)
710e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    , m_reconnectDelay(defaultReconnectDelay)
720e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org{
730e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org}
740e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org
750e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.orgPassRefPtrWillBeRawPtr<EventSource> EventSource::create(ExecutionContext* context, const String& url, const EventSourceInit& eventSourceInit, ExceptionState& exceptionState)
760e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org{
770e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    if (url.isEmpty()) {
780e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org        exceptionState.throwDOMException(SyntaxError, "Cannot open an EventSource to an empty URL.");
790e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org        return nullptr;
800e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    }
810e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org
820e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    KURL fullURL = context->completeURL(url);
830e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    if (!fullURL.isValid()) {
840e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org        exceptionState.throwDOMException(SyntaxError, "Cannot open an EventSource to '" + url + "'. The URL is invalid.");
850e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org        return nullptr;
860e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    }
870e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org
884047deae290d09b36a569c91b9c2e2c26e319d36fischman@webrtc.org    // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved.
890e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    bool shouldBypassMainWorldCSP = false;
900e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    if (context->isDocument()) {
910e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org        Document* document = toDocument(context);
920e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org        shouldBypassMainWorldCSP = document->frame()->script().shouldBypassMainWorldCSP();
930e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    }
940e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    if (!shouldBypassMainWorldCSP && !context->contentSecurityPolicy()->allowConnectToSource(fullURL)) {
950e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org        // We can safely expose the URL to JavaScript, as this exception is generate synchronously before any redirects take place.
9620477f1f73a6bbb4e9101751c99f705a07843147fischman@webrtc.org        exceptionState.throwSecurityError("Refused to connect to '" + fullURL.elidedString() + "' because it violates the document's Content Security Policy.");
9720477f1f73a6bbb4e9101751c99f705a07843147fischman@webrtc.org        return nullptr;
9820477f1f73a6bbb4e9101751c99f705a07843147fischman@webrtc.org    }
9920477f1f73a6bbb4e9101751c99f705a07843147fischman@webrtc.org
10020477f1f73a6bbb4e9101751c99f705a07843147fischman@webrtc.org    RefPtrWillBeRawPtr<EventSource> source = adoptRefWillBeNoop(new EventSource(context, fullURL, eventSourceInit));
10120477f1f73a6bbb4e9101751c99f705a07843147fischman@webrtc.org
10220477f1f73a6bbb4e9101751c99f705a07843147fischman@webrtc.org    source->scheduleInitialConnect();
1030e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    source->suspendIfNeeded();
1040e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org
1050e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    return source.release();
1060e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org}
1070e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org
1080e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.orgEventSource::~EventSource()
1090e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org{
1100e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    ASSERT(m_state == CLOSED);
1110e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    ASSERT(!m_requestInFlight);
1120e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org}
1130e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org
1140e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.orgvoid EventSource::scheduleInitialConnect()
1150e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org{
1160e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    ASSERT(m_state == CONNECTING);
1170e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    ASSERT(!m_requestInFlight);
1180e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org
1190e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    m_connectTimer.startOneShot(0, FROM_HERE);
1200e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org}
1210e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org
1220e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.orgvoid EventSource::connect()
1230e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org{
1240e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    ASSERT(m_state == CONNECTING);
1250e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    ASSERT(!m_requestInFlight);
1260e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    ASSERT(executionContext());
1270e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org
12820477f1f73a6bbb4e9101751c99f705a07843147fischman@webrtc.org    ExecutionContext& executionContext = *this->executionContext();
12920477f1f73a6bbb4e9101751c99f705a07843147fischman@webrtc.org    ResourceRequest request(m_url);
13020477f1f73a6bbb4e9101751c99f705a07843147fischman@webrtc.org    request.setHTTPMethod("GET");
1310e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    request.setHTTPHeaderField("Accept", "text/event-stream");
13220477f1f73a6bbb4e9101751c99f705a07843147fischman@webrtc.org    request.setHTTPHeaderField("Cache-Control", "no-cache");
1330e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    request.setRequestContext(blink::WebURLRequest::RequestContextEventSource);
1340e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org    if (!m_lastEventId.isEmpty())
1350e118e7129884fbea117e78d6f2068139a414dbhenrike@webrtc.org        request.setHTTPHeaderField("Last-Event-ID", m_lastEventId);
136
137    SecurityOrigin* origin = executionContext.securityOrigin();
138
139    ThreadableLoaderOptions options;
140    options.preflightPolicy = PreventPreflight;
141    options.crossOriginRequestPolicy = UseAccessControl;
142    options.contentSecurityPolicyEnforcement = ContentSecurityPolicy::shouldBypassMainWorld(&executionContext) ? DoNotEnforceContentSecurityPolicy : EnforceConnectSrcDirective;
143
144    ResourceLoaderOptions resourceLoaderOptions;
145    resourceLoaderOptions.allowCredentials = (origin->canRequest(m_url) || m_withCredentials) ? AllowStoredCredentials : DoNotAllowStoredCredentials;
146    resourceLoaderOptions.credentialsRequested = m_withCredentials ? ClientRequestedCredentials : ClientDidNotRequestCredentials;
147    resourceLoaderOptions.dataBufferingPolicy = DoNotBufferData;
148    resourceLoaderOptions.securityOrigin = origin;
149    resourceLoaderOptions.mixedContentBlockingTreatment = TreatAsActiveContent;
150
151    m_loader = ThreadableLoader::create(executionContext, this, request, options, resourceLoaderOptions);
152
153    if (m_loader)
154        m_requestInFlight = true;
155}
156
157void EventSource::networkRequestEnded()
158{
159    if (!m_requestInFlight)
160        return;
161
162    m_requestInFlight = false;
163
164    if (m_state != CLOSED)
165        scheduleReconnect();
166}
167
168void EventSource::scheduleReconnect()
169{
170    m_state = CONNECTING;
171    m_connectTimer.startOneShot(m_reconnectDelay / 1000.0, FROM_HERE);
172    dispatchEvent(Event::create(EventTypeNames::error));
173}
174
175void EventSource::connectTimerFired(Timer<EventSource>*)
176{
177    connect();
178}
179
180String EventSource::url() const
181{
182    return m_url.string();
183}
184
185bool EventSource::withCredentials() const
186{
187    return m_withCredentials;
188}
189
190EventSource::State EventSource::readyState() const
191{
192    return m_state;
193}
194
195void EventSource::close()
196{
197    if (m_state == CLOSED) {
198        ASSERT(!m_requestInFlight);
199        return;
200    }
201
202    // Stop trying to reconnect if EventSource was explicitly closed or if ActiveDOMObject::stop() was called.
203    if (m_connectTimer.isActive()) {
204        m_connectTimer.stop();
205    }
206
207    if (m_requestInFlight)
208        m_loader->cancel();
209
210    m_state = CLOSED;
211}
212
213const AtomicString& EventSource::interfaceName() const
214{
215    return EventTargetNames::EventSource;
216}
217
218ExecutionContext* EventSource::executionContext() const
219{
220    return ActiveDOMObject::executionContext();
221}
222
223void EventSource::didReceiveResponse(unsigned long, const ResourceResponse& response)
224{
225    ASSERT(m_state == CONNECTING);
226    ASSERT(m_requestInFlight);
227
228    m_eventStreamOrigin = SecurityOrigin::create(response.url())->toString();
229    int statusCode = response.httpStatusCode();
230    bool mimeTypeIsValid = response.mimeType() == "text/event-stream";
231    bool responseIsValid = statusCode == 200 && mimeTypeIsValid;
232    if (responseIsValid) {
233        const String& charset = response.textEncodingName();
234        // If we have a charset, the only allowed value is UTF-8 (case-insensitive).
235        responseIsValid = charset.isEmpty() || equalIgnoringCase(charset, "UTF-8");
236        if (!responseIsValid) {
237            StringBuilder message;
238            message.appendLiteral("EventSource's response has a charset (\"");
239            message.append(charset);
240            message.appendLiteral("\") that is not UTF-8. Aborting the connection.");
241            // FIXME: We are missing the source line.
242            executionContext()->addConsoleMessage(ConsoleMessage::create(JSMessageSource, ErrorMessageLevel, message.toString()));
243        }
244    } else {
245        // To keep the signal-to-noise ratio low, we only log 200-response with an invalid MIME type.
246        if (statusCode == 200 && !mimeTypeIsValid) {
247            StringBuilder message;
248            message.appendLiteral("EventSource's response has a MIME type (\"");
249            message.append(response.mimeType());
250            message.appendLiteral("\") that is not \"text/event-stream\". Aborting the connection.");
251            // FIXME: We are missing the source line.
252            executionContext()->addConsoleMessage(ConsoleMessage::create(JSMessageSource, ErrorMessageLevel, message.toString()));
253        }
254    }
255
256    if (responseIsValid) {
257        m_state = OPEN;
258        dispatchEvent(Event::create(EventTypeNames::open));
259    } else {
260        m_loader->cancel();
261        dispatchEvent(Event::create(EventTypeNames::error));
262    }
263}
264
265void EventSource::didReceiveData(const char* data, int length)
266{
267    ASSERT(m_state == OPEN);
268    ASSERT(m_requestInFlight);
269
270    append(m_receiveBuf, m_decoder->decode(data, length));
271    parseEventStream();
272}
273
274void EventSource::didFinishLoading(unsigned long, double)
275{
276    ASSERT(m_state == OPEN);
277    ASSERT(m_requestInFlight);
278
279    if (m_receiveBuf.size() > 0 || m_data.size() > 0) {
280        parseEventStream();
281
282        // Discard everything that has not been dispatched by now.
283        m_receiveBuf.clear();
284        m_data.clear();
285        m_eventName = emptyAtom;
286        m_currentlyParsedEventId = nullAtom;
287    }
288    networkRequestEnded();
289}
290
291void EventSource::didFail(const ResourceError& error)
292{
293    ASSERT(m_state != CLOSED);
294    ASSERT(m_requestInFlight);
295
296    if (error.isCancellation())
297        m_state = CLOSED;
298    networkRequestEnded();
299}
300
301void EventSource::didFailAccessControlCheck(const ResourceError& error)
302{
303    String message = "EventSource cannot load " + error.failingURL() + ". " + error.localizedDescription();
304    executionContext()->addConsoleMessage(ConsoleMessage::create(JSMessageSource, ErrorMessageLevel, message));
305
306    abortConnectionAttempt();
307}
308
309void EventSource::didFailRedirectCheck()
310{
311    abortConnectionAttempt();
312}
313
314void EventSource::abortConnectionAttempt()
315{
316    ASSERT(m_state == CONNECTING);
317
318    if (m_requestInFlight) {
319        m_loader->cancel();
320    } else {
321        m_state = CLOSED;
322    }
323
324    ASSERT(m_state == CLOSED);
325    dispatchEvent(Event::create(EventTypeNames::error));
326}
327
328void EventSource::parseEventStream()
329{
330    unsigned bufPos = 0;
331    unsigned bufSize = m_receiveBuf.size();
332    while (bufPos < bufSize) {
333        if (m_discardTrailingNewline) {
334            if (m_receiveBuf[bufPos] == '\n')
335                bufPos++;
336            m_discardTrailingNewline = false;
337        }
338
339        int lineLength = -1;
340        int fieldLength = -1;
341        for (unsigned i = bufPos; lineLength < 0 && i < bufSize; i++) {
342            switch (m_receiveBuf[i]) {
343            case ':':
344                if (fieldLength < 0)
345                    fieldLength = i - bufPos;
346                break;
347            case '\r':
348                m_discardTrailingNewline = true;
349            case '\n':
350                lineLength = i - bufPos;
351                break;
352            }
353        }
354
355        if (lineLength < 0)
356            break;
357
358        parseEventStreamLine(bufPos, fieldLength, lineLength);
359        bufPos += lineLength + 1;
360
361        // EventSource.close() might've been called by one of the message event handlers.
362        // Per spec, no further messages should be fired after that.
363        if (m_state == CLOSED)
364            break;
365    }
366
367    if (bufPos == bufSize)
368        m_receiveBuf.clear();
369    else if (bufPos)
370        m_receiveBuf.remove(0, bufPos);
371}
372
373void EventSource::parseEventStreamLine(unsigned bufPos, int fieldLength, int lineLength)
374{
375    if (!lineLength) {
376        if (!m_data.isEmpty()) {
377            m_data.removeLast();
378            if (!m_currentlyParsedEventId.isNull()) {
379                m_lastEventId = m_currentlyParsedEventId;
380                m_currentlyParsedEventId = nullAtom;
381            }
382            dispatchEvent(createMessageEvent());
383        }
384        if (!m_eventName.isEmpty())
385            m_eventName = emptyAtom;
386    } else if (fieldLength) {
387        bool noValue = fieldLength < 0;
388
389        String field(&m_receiveBuf[bufPos], noValue ? lineLength : fieldLength);
390        int step;
391        if (noValue)
392            step = lineLength;
393        else if (m_receiveBuf[bufPos + fieldLength + 1] != ' ')
394            step = fieldLength + 1;
395        else
396            step = fieldLength + 2;
397        bufPos += step;
398        int valueLength = lineLength - step;
399
400        if (field == "data") {
401            if (valueLength)
402                m_data.append(&m_receiveBuf[bufPos], valueLength);
403            m_data.append('\n');
404        } else if (field == "event") {
405            m_eventName = valueLength ? AtomicString(&m_receiveBuf[bufPos], valueLength) : "";
406        } else if (field == "id") {
407            m_currentlyParsedEventId = valueLength ? AtomicString(&m_receiveBuf[bufPos], valueLength) : "";
408        } else if (field == "retry") {
409            if (!valueLength)
410                m_reconnectDelay = defaultReconnectDelay;
411            else {
412                String value(&m_receiveBuf[bufPos], valueLength);
413                bool ok;
414                unsigned long long retry = value.toUInt64(&ok);
415                if (ok)
416                    m_reconnectDelay = retry;
417            }
418        }
419    }
420}
421
422void EventSource::stop()
423{
424    close();
425}
426
427bool EventSource::hasPendingActivity() const
428{
429    return m_state != CLOSED;
430}
431
432PassRefPtrWillBeRawPtr<MessageEvent> EventSource::createMessageEvent()
433{
434    RefPtrWillBeRawPtr<MessageEvent> event = MessageEvent::create();
435    event->initMessageEvent(m_eventName.isEmpty() ? EventTypeNames::message : m_eventName, false, false, SerializedScriptValue::create(String(m_data)), m_eventStreamOrigin, m_lastEventId, 0, nullptr);
436    m_data.clear();
437    return event.release();
438}
439
440} // namespace blink
441