~ubuntu-branches/ubuntu/raring/qtwebkit-source/raring-proposed

« back to all changes in this revision

Viewing changes to Source/WebCore/page/EventSource.cpp

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2013-02-18 14:24:18 UTC
  • Revision ID: package-import@ubuntu.com-20130218142418-eon0jmjg3nj438uy
Tags: upstream-2.3
ImportĀ upstreamĀ versionĀ 2.3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2009 Ericsson AB
 
3
 * All rights reserved.
 
4
 * Copyright (C) 2010 Apple Inc. All rights reserved.
 
5
 * Copyright (C) 2011, Code Aurora Forum. All rights reserved.
 
6
 *
 
7
 * Redistribution and use in source and binary forms, with or without
 
8
 * modification, are permitted provided that the following conditions
 
9
 * are met:
 
10
 *
 
11
 * 1. Redistributions of source code must retain the above copyright
 
12
 *    notice, this list of conditions and the following disclaimer.
 
13
 * 2. Redistributions in binary form must reproduce the above copyright
 
14
 *    notice, this list of conditions and the following disclaimer
 
15
 *    in the documentation and/or other materials provided with the
 
16
 *    distribution.
 
17
 * 3. Neither the name of Ericsson nor the names of its contributors
 
18
 *    may be used to endorse or promote products derived from this
 
19
 *    software without specific prior written permission.
 
20
 *
 
21
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 
22
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 
23
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 
24
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 
25
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 
26
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 
27
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 
28
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 
29
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 
30
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 
31
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
32
 */
 
33
 
 
34
#include "config.h"
 
35
#include "EventSource.h"
 
36
 
 
37
#include "ContentSecurityPolicy.h"
 
38
#include "DOMWindow.h"
 
39
#include "Event.h"
 
40
#include "EventException.h"
 
41
#include "ExceptionCode.h"
 
42
#include "MemoryCache.h"
 
43
#include "MessageEvent.h"
 
44
#include "ResourceError.h"
 
45
#include "ResourceRequest.h"
 
46
#include "ResourceResponse.h"
 
47
#include "ScriptCallStack.h"
 
48
#include "ScriptExecutionContext.h"
 
49
#include "SecurityOrigin.h"
 
50
#include "SerializedScriptValue.h"
 
51
#include "TextResourceDecoder.h"
 
52
#include "ThreadableLoader.h"
 
53
#include <wtf/text/StringBuilder.h>
 
54
 
 
55
namespace WebCore {
 
56
 
 
57
const unsigned long long EventSource::defaultReconnectDelay = 3000;
 
58
 
 
59
inline EventSource::EventSource(const KURL& url, ScriptExecutionContext* context)
 
60
    : ActiveDOMObject(context, this)
 
61
    , m_url(url)
 
62
    , m_state(CONNECTING)
 
63
    , m_decoder(TextResourceDecoder::create("text/plain", "UTF-8"))
 
64
    , m_reconnectTimer(this, &EventSource::reconnectTimerFired)
 
65
    , m_discardTrailingNewline(false)
 
66
    , m_requestInFlight(false)
 
67
    , m_reconnectDelay(defaultReconnectDelay)
 
68
    , m_origin(context->securityOrigin()->toString())
 
69
{
 
70
}
 
71
 
 
72
PassRefPtr<EventSource> EventSource::create(ScriptExecutionContext* context, const String& url, ExceptionCode& ec)
 
73
{
 
74
    if (url.isEmpty()) {
 
75
        ec = SYNTAX_ERR;
 
76
        return 0;
 
77
    }
 
78
 
 
79
    KURL fullURL = context->completeURL(url);
 
80
    if (!fullURL.isValid()) {
 
81
        ec = SYNTAX_ERR;
 
82
        return 0;
 
83
    }
 
84
 
 
85
    // FIXME: Should support at least some cross-origin requests.
 
86
    if (!context->securityOrigin()->canRequest(fullURL)) {
 
87
        ec = SECURITY_ERR;
 
88
        return 0;
 
89
    }
 
90
 
 
91
    if (!context->contentSecurityPolicy()->allowConnectToSource(fullURL)) {
 
92
        // FIXME: Should this be throwing an exception?
 
93
        ec = SECURITY_ERR;
 
94
        return 0;
 
95
    }
 
96
 
 
97
    RefPtr<EventSource> source = adoptRef(new EventSource(fullURL, context));
 
98
 
 
99
    source->setPendingActivity(source.get());
 
100
    source->connect();
 
101
    source->suspendIfNeeded();
 
102
 
 
103
    return source.release();
 
104
}
 
105
 
 
106
EventSource::~EventSource()
 
107
{
 
108
    ASSERT(m_state == CLOSED);
 
109
    ASSERT(!m_requestInFlight);
 
110
}
 
111
 
 
112
void EventSource::connect()
 
113
{
 
114
    ASSERT(m_state == CONNECTING);
 
115
    ASSERT(!m_requestInFlight);
 
116
 
 
117
    ResourceRequest request(m_url);
 
118
    request.setHTTPMethod("GET");
 
119
    request.setHTTPHeaderField("Accept", "text/event-stream");
 
120
    request.setHTTPHeaderField("Cache-Control", "no-cache");
 
121
    if (!m_lastEventId.isEmpty())
 
122
        request.setHTTPHeaderField("Last-Event-ID", m_lastEventId);
 
123
 
 
124
    ThreadableLoaderOptions options;
 
125
    options.sendLoadCallbacks = SendCallbacks;
 
126
    options.sniffContent = DoNotSniffContent;
 
127
    options.allowCredentials = AllowStoredCredentials;
 
128
    options.shouldBufferData = DoNotBufferData;
 
129
 
 
130
    m_loader = ThreadableLoader::create(scriptExecutionContext(), this, request, options);
 
131
 
 
132
    if (m_loader)
 
133
        m_requestInFlight = true;
 
134
}
 
135
 
 
136
void EventSource::networkRequestEnded()
 
137
{
 
138
    if (!m_requestInFlight)
 
139
        return;
 
140
 
 
141
    m_requestInFlight = false;
 
142
 
 
143
    if (m_state != CLOSED)
 
144
        scheduleReconnect();
 
145
    else
 
146
        unsetPendingActivity(this);
 
147
}
 
148
 
 
149
void EventSource::scheduleReconnect()
 
150
{
 
151
    m_state = CONNECTING;
 
152
    m_reconnectTimer.startOneShot(m_reconnectDelay / 1000);
 
153
    dispatchEvent(Event::create(eventNames().errorEvent, false, false));
 
154
}
 
155
 
 
156
void EventSource::reconnectTimerFired(Timer<EventSource>*)
 
157
{
 
158
    connect();
 
159
}
 
160
 
 
161
String EventSource::url() const
 
162
{
 
163
    return m_url.string();
 
164
}
 
165
 
 
166
EventSource::State EventSource::readyState() const
 
167
{
 
168
    return m_state;
 
169
}
 
170
 
 
171
void EventSource::close()
 
172
{
 
173
    if (m_state == CLOSED) {
 
174
        ASSERT(!m_requestInFlight);
 
175
        return;
 
176
    }
 
177
 
 
178
    // Stop trying to reconnect if EventSource was explicitly closed or if ActiveDOMObject::stop() was called.
 
179
    if (m_reconnectTimer.isActive()) {
 
180
        m_reconnectTimer.stop();
 
181
        unsetPendingActivity(this);
 
182
    }
 
183
 
 
184
    if (m_requestInFlight)
 
185
        m_loader->cancel();
 
186
 
 
187
    m_state = CLOSED;
 
188
}
 
189
 
 
190
const AtomicString& EventSource::interfaceName() const
 
191
{
 
192
    return eventNames().interfaceForEventSource;
 
193
}
 
194
 
 
195
ScriptExecutionContext* EventSource::scriptExecutionContext() const
 
196
{
 
197
    return ActiveDOMObject::scriptExecutionContext();
 
198
}
 
199
 
 
200
void EventSource::didReceiveResponse(unsigned long, const ResourceResponse& response)
 
201
{
 
202
    ASSERT(m_state == CONNECTING);
 
203
    ASSERT(m_requestInFlight);
 
204
 
 
205
    int statusCode = response.httpStatusCode();
 
206
    bool mimeTypeIsValid = response.mimeType() == "text/event-stream";
 
207
    bool responseIsValid = statusCode == 200 && mimeTypeIsValid;
 
208
    if (responseIsValid) {
 
209
        const String& charset = response.textEncodingName();
 
210
        // If we have a charset, the only allowed value is UTF-8 (case-insensitive).
 
211
        responseIsValid = charset.isEmpty() || equalIgnoringCase(charset, "UTF-8");
 
212
        if (!responseIsValid) {
 
213
            StringBuilder message;
 
214
            message.appendLiteral("EventSource's response has a charset (\"");
 
215
            message.append(charset);
 
216
            message.appendLiteral("\") that is not UTF-8. Aborting the connection.");
 
217
            // FIXME: We are missing the source line.
 
218
            scriptExecutionContext()->addConsoleMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, message.toString());
 
219
        }
 
220
    } else {
 
221
        // To keep the signal-to-noise ratio low, we only log 200-response with an invalid MIME type.
 
222
        if (statusCode == 200 && !mimeTypeIsValid) {
 
223
            StringBuilder message;
 
224
            message.appendLiteral("EventSource's response has a MIME type (\"");
 
225
            message.append(response.mimeType());
 
226
            message.appendLiteral("\") that is not \"text/event-stream\". Aborting the connection.");
 
227
            // FIXME: We are missing the source line.
 
228
            scriptExecutionContext()->addConsoleMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, message.toString());
 
229
        }
 
230
    }
 
231
 
 
232
    if (responseIsValid) {
 
233
        m_state = OPEN;
 
234
        dispatchEvent(Event::create(eventNames().openEvent, false, false));
 
235
    } else {
 
236
        m_loader->cancel();
 
237
        dispatchEvent(Event::create(eventNames().errorEvent, false, false));
 
238
    }
 
239
}
 
240
 
 
241
void EventSource::didReceiveData(const char* data, int length)
 
242
{
 
243
    ASSERT(m_state == OPEN);
 
244
    ASSERT(m_requestInFlight);
 
245
 
 
246
    append(m_receiveBuf, m_decoder->decode(data, length));
 
247
    parseEventStream();
 
248
}
 
249
 
 
250
void EventSource::didFinishLoading(unsigned long, double)
 
251
{
 
252
    ASSERT(m_state == OPEN);
 
253
    ASSERT(m_requestInFlight);
 
254
 
 
255
    if (m_receiveBuf.size() > 0 || m_data.size() > 0) {
 
256
        parseEventStream();
 
257
 
 
258
        // Discard everything that has not been dispatched by now.
 
259
        m_receiveBuf.clear();
 
260
        m_data.clear();
 
261
        m_eventName = "";
 
262
        m_currentlyParsedEventId = String();
 
263
    }
 
264
    networkRequestEnded();
 
265
}
 
266
 
 
267
void EventSource::didFail(const ResourceError& error)
 
268
{
 
269
    ASSERT(m_state != CLOSED);
 
270
    ASSERT(m_requestInFlight);
 
271
 
 
272
    if (error.isCancellation())
 
273
        m_state = CLOSED;
 
274
    networkRequestEnded();
 
275
}
 
276
 
 
277
void EventSource::didFailRedirectCheck()
 
278
{
 
279
    ASSERT(m_state == CONNECTING);
 
280
    ASSERT(m_requestInFlight);
 
281
 
 
282
    m_loader->cancel();
 
283
 
 
284
    ASSERT(m_state == CLOSED);
 
285
    dispatchEvent(Event::create(eventNames().errorEvent, false, false));
 
286
}
 
287
 
 
288
void EventSource::parseEventStream()
 
289
{
 
290
    unsigned int bufPos = 0;
 
291
    unsigned int bufSize = m_receiveBuf.size();
 
292
    while (bufPos < bufSize) {
 
293
        if (m_discardTrailingNewline) {
 
294
            if (m_receiveBuf[bufPos] == '\n')
 
295
                bufPos++;
 
296
            m_discardTrailingNewline = false;
 
297
        }
 
298
 
 
299
        int lineLength = -1;
 
300
        int fieldLength = -1;
 
301
        for (unsigned int i = bufPos; lineLength < 0 && i < bufSize; i++) {
 
302
            switch (m_receiveBuf[i]) {
 
303
            case ':':
 
304
                if (fieldLength < 0)
 
305
                    fieldLength = i - bufPos;
 
306
                break;
 
307
            case '\r':
 
308
                m_discardTrailingNewline = true;
 
309
            case '\n':
 
310
                lineLength = i - bufPos;
 
311
                break;
 
312
            }
 
313
        }
 
314
 
 
315
        if (lineLength < 0)
 
316
            break;
 
317
 
 
318
        parseEventStreamLine(bufPos, fieldLength, lineLength);
 
319
        bufPos += lineLength + 1;
 
320
 
 
321
        // EventSource.close() might've been called by one of the message event handlers.
 
322
        // Per spec, no further messages should be fired after that.
 
323
        if (m_state == CLOSED)
 
324
            break;
 
325
    }
 
326
 
 
327
    if (bufPos == bufSize)
 
328
        m_receiveBuf.clear();
 
329
    else if (bufPos)
 
330
        m_receiveBuf.remove(0, bufPos);
 
331
}
 
332
 
 
333
void EventSource::parseEventStreamLine(unsigned int bufPos, int fieldLength, int lineLength)
 
334
{
 
335
    if (!lineLength) {
 
336
        if (!m_data.isEmpty()) {
 
337
            m_data.removeLast();
 
338
            if (!m_currentlyParsedEventId.isNull()) {
 
339
                m_lastEventId.swap(m_currentlyParsedEventId);
 
340
                m_currentlyParsedEventId = String();
 
341
            }
 
342
            dispatchEvent(createMessageEvent());
 
343
        }
 
344
        if (!m_eventName.isEmpty())
 
345
            m_eventName = "";
 
346
    } else if (fieldLength) {
 
347
        bool noValue = fieldLength < 0;
 
348
 
 
349
        String field(&m_receiveBuf[bufPos], noValue ? lineLength : fieldLength);
 
350
        int step;
 
351
        if (noValue)
 
352
            step = lineLength;
 
353
        else if (m_receiveBuf[bufPos + fieldLength + 1] != ' ')
 
354
            step = fieldLength + 1;
 
355
        else
 
356
            step = fieldLength + 2;
 
357
        bufPos += step;
 
358
        int valueLength = lineLength - step;
 
359
 
 
360
        if (field == "data") {
 
361
            if (valueLength)
 
362
                m_data.append(&m_receiveBuf[bufPos], valueLength);
 
363
            m_data.append('\n');
 
364
        } else if (field == "event")
 
365
            m_eventName = valueLength ? String(&m_receiveBuf[bufPos], valueLength) : "";
 
366
        else if (field == "id")
 
367
            m_currentlyParsedEventId = valueLength ? String(&m_receiveBuf[bufPos], valueLength) : "";
 
368
        else if (field == "retry") {
 
369
            if (!valueLength)
 
370
                m_reconnectDelay = defaultReconnectDelay;
 
371
            else {
 
372
                String value(&m_receiveBuf[bufPos], valueLength);
 
373
                bool ok;
 
374
                unsigned long long retry = value.toUInt64(&ok);
 
375
                if (ok)
 
376
                    m_reconnectDelay = retry;
 
377
            }
 
378
        }
 
379
    }
 
380
}
 
381
 
 
382
void EventSource::stop()
 
383
{
 
384
    close();
 
385
}
 
386
 
 
387
PassRefPtr<MessageEvent> EventSource::createMessageEvent()
 
388
{
 
389
    RefPtr<MessageEvent> event = MessageEvent::create();
 
390
    event->initMessageEvent(m_eventName.isEmpty() ? eventNames().messageEvent : AtomicString(m_eventName), false, false, SerializedScriptValue::create(String::adopt(m_data)), m_origin, m_lastEventId, 0, 0);
 
391
    return event.release();
 
392
}
 
393
 
 
394
EventTargetData* EventSource::eventTargetData()
 
395
{
 
396
    return &m_eventTargetData;
 
397
}
 
398
 
 
399
EventTargetData* EventSource::ensureEventTargetData()
 
400
{
 
401
    return &m_eventTargetData;
 
402
}
 
403
 
 
404
} // namespace WebCore