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 
36 #if ENABLE(EVENTSOURCE)
37 
38 #include "EventSource.h"
39 
40 #include "MemoryCache.h"
41 #include "DOMWindow.h"
42 #include "Event.h"
43 #include "EventException.h"
44 #include "PlatformString.h"
45 #include "MessageEvent.h"
46 #include "ResourceError.h"
47 #include "ResourceRequest.h"
48 #include "ResourceResponse.h"
49 #include "ScriptCallStack.h"
50 #include "ScriptExecutionContext.h"
51 #include "SecurityOrigin.h"
52 #include "SerializedScriptValue.h"
53 #include "TextResourceDecoder.h"
54 #include "ThreadableLoader.h"
55 
56 namespace WebCore {
57 
58 const unsigned long long EventSource::defaultReconnectDelay = 3000;
59 
EventSource(const KURL & url,ScriptExecutionContext * context)60 inline EventSource::EventSource(const KURL& url, ScriptExecutionContext* context)
61     : ActiveDOMObject(context, this)
62     , m_url(url)
63     , m_state(CONNECTING)
64     , m_decoder(TextResourceDecoder::create("text/plain", "UTF-8"))
65     , m_reconnectTimer(this, &EventSource::reconnectTimerFired)
66     , m_discardTrailingNewline(false)
67     , m_failSilently(false)
68     , m_requestInFlight(false)
69     , m_reconnectDelay(defaultReconnectDelay)
70     , m_origin(context->securityOrigin()->toString())
71 {
72 }
73 
create(const String & url,ScriptExecutionContext * context,ExceptionCode & ec)74 PassRefPtr<EventSource> EventSource::create(const String& url, ScriptExecutionContext* context, ExceptionCode& ec)
75 {
76     if (url.isEmpty()) {
77         ec = SYNTAX_ERR;
78         return 0;
79     }
80 
81     KURL fullURL = context->completeURL(url);
82     if (!fullURL.isValid()) {
83         ec = SYNTAX_ERR;
84         return 0;
85     }
86 
87     // FIXME: Should support at least some cross-origin requests.
88     if (!context->securityOrigin()->canRequest(fullURL)) {
89         ec = SECURITY_ERR;
90         return 0;
91     }
92 
93     RefPtr<EventSource> source = adoptRef(new EventSource(fullURL, context));
94 
95     source->setPendingActivity(source.get());
96     source->connect();
97 
98     return source.release();
99 }
100 
~EventSource()101 EventSource::~EventSource()
102 {
103 }
104 
connect()105 void EventSource::connect()
106 {
107     ResourceRequest request(m_url);
108     request.setHTTPMethod("GET");
109     request.setHTTPHeaderField("Accept", "text/event-stream");
110     request.setHTTPHeaderField("Cache-Control", "no-cache");
111     if (!m_lastEventId.isEmpty())
112         request.setHTTPHeaderField("Last-Event-ID", m_lastEventId);
113 
114     ThreadableLoaderOptions options;
115     options.sendLoadCallbacks = true;
116     options.sniffContent = false;
117     options.allowCredentials = true;
118 
119     m_loader = ThreadableLoader::create(scriptExecutionContext(), this, request, options);
120 
121     m_requestInFlight = true;
122 }
123 
endRequest()124 void EventSource::endRequest()
125 {
126     if (!m_requestInFlight)
127         return;
128 
129     m_requestInFlight = false;
130 
131     if (!m_failSilently)
132         dispatchEvent(Event::create(eventNames().errorEvent, false, false));
133 
134     if (m_state != CLOSED)
135         scheduleReconnect();
136     else
137         unsetPendingActivity(this);
138 }
139 
scheduleReconnect()140 void EventSource::scheduleReconnect()
141 {
142     m_state = CONNECTING;
143     m_reconnectTimer.startOneShot(m_reconnectDelay / 1000);
144 }
145 
reconnectTimerFired(Timer<EventSource> *)146 void EventSource::reconnectTimerFired(Timer<EventSource>*)
147 {
148     connect();
149 }
150 
url() const151 String EventSource::url() const
152 {
153     return m_url.string();
154 }
155 
readyState() const156 EventSource::State EventSource::readyState() const
157 {
158     return m_state;
159 }
160 
close()161 void EventSource::close()
162 {
163     if (m_state == CLOSED)
164         return;
165 
166     if (m_reconnectTimer.isActive()) {
167         m_reconnectTimer.stop();
168         unsetPendingActivity(this);
169     }
170 
171     m_state = CLOSED;
172     m_failSilently = true;
173 
174     if (m_requestInFlight)
175         m_loader->cancel();
176 }
177 
scriptExecutionContext() const178 ScriptExecutionContext* EventSource::scriptExecutionContext() const
179 {
180     return ActiveDOMObject::scriptExecutionContext();
181 }
182 
didReceiveResponse(const ResourceResponse & response)183 void EventSource::didReceiveResponse(const ResourceResponse& response)
184 {
185     int statusCode = response.httpStatusCode();
186     bool mimeTypeIsValid = response.mimeType() == "text/event-stream";
187     bool responseIsValid = statusCode == 200 && mimeTypeIsValid;
188     if (responseIsValid) {
189         const String& charset = response.textEncodingName();
190         // If we have a charset, the only allowed value is UTF-8 (case-insensitive). This should match
191         // the updated EventSource standard.
192         responseIsValid = charset.isEmpty() || equalIgnoringCase(charset, "UTF-8");
193         if (!responseIsValid) {
194             String message = "EventSource's response has a charset (\"";
195             message += charset;
196             message += "\") that is not UTF-8. Aborting the connection.";
197             // FIXME: We are missing the source line.
198             scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, message, 1, String(), 0);
199         }
200     } else {
201         // To keep the signal-to-noise ratio low, we only log 200-response with an invalid MIME type.
202         if (statusCode == 200 && !mimeTypeIsValid) {
203             String message = "EventSource's response has a MIME type (\"";
204             message += response.mimeType();
205             message += "\") that is not \"text/event-stream\". Aborting the connection.";
206             // FIXME: We are missing the source line.
207             scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, message, 1, String(), 0);
208         }
209     }
210 
211     if (responseIsValid) {
212         m_state = OPEN;
213         dispatchEvent(Event::create(eventNames().openEvent, false, false));
214     } else {
215         if (statusCode <= 200 || statusCode > 299)
216             m_state = CLOSED;
217         m_loader->cancel();
218     }
219 }
220 
didReceiveData(const char * data,int length)221 void EventSource::didReceiveData(const char* data, int length)
222 {
223     append(m_receiveBuf, m_decoder->decode(data, length));
224     parseEventStream();
225 }
226 
didFinishLoading(unsigned long,double)227 void EventSource::didFinishLoading(unsigned long, double)
228 {
229     if (m_receiveBuf.size() > 0 || m_data.size() > 0) {
230         append(m_receiveBuf, "\n\n");
231         parseEventStream();
232     }
233     m_state = CONNECTING;
234     endRequest();
235 }
236 
didFail(const ResourceError & error)237 void EventSource::didFail(const ResourceError& error)
238 {
239     int canceled = error.isCancellation();
240     if (((m_state == CONNECTING) && !canceled) || ((m_state == OPEN) && canceled))
241         m_state = CLOSED;
242     endRequest();
243 }
244 
didFailRedirectCheck()245 void EventSource::didFailRedirectCheck()
246 {
247     m_state = CLOSED;
248     m_loader->cancel();
249 }
250 
parseEventStream()251 void EventSource::parseEventStream()
252 {
253     unsigned int bufPos = 0;
254     unsigned int bufSize = m_receiveBuf.size();
255     while (bufPos < bufSize) {
256         if (m_discardTrailingNewline) {
257             if (m_receiveBuf[bufPos] == '\n')
258                 bufPos++;
259             m_discardTrailingNewline = false;
260         }
261 
262         int lineLength = -1;
263         int fieldLength = -1;
264         for (unsigned int i = bufPos; lineLength < 0 && i < bufSize; i++) {
265             switch (m_receiveBuf[i]) {
266             case ':':
267                 if (fieldLength < 0)
268                     fieldLength = i - bufPos;
269                 break;
270             case '\r':
271                 m_discardTrailingNewline = true;
272             case '\n':
273                 lineLength = i - bufPos;
274                 break;
275             }
276         }
277 
278         if (lineLength < 0)
279             break;
280 
281         parseEventStreamLine(bufPos, fieldLength, lineLength);
282         bufPos += lineLength + 1;
283     }
284 
285     if (bufPos == bufSize)
286         m_receiveBuf.clear();
287     else if (bufPos)
288         m_receiveBuf.remove(0, bufPos);
289 }
290 
parseEventStreamLine(unsigned int bufPos,int fieldLength,int lineLength)291 void EventSource::parseEventStreamLine(unsigned int bufPos, int fieldLength, int lineLength)
292 {
293     if (!lineLength) {
294         if (!m_data.isEmpty()) {
295             m_data.removeLast();
296             dispatchEvent(createMessageEvent());
297         }
298         if (!m_eventName.isEmpty())
299             m_eventName = "";
300     } else if (fieldLength) {
301         bool noValue = fieldLength < 0;
302 
303         String field(&m_receiveBuf[bufPos], noValue ? lineLength : fieldLength);
304         int step;
305         if (noValue)
306             step = lineLength;
307         else if (m_receiveBuf[bufPos + fieldLength + 1] != ' ')
308             step = fieldLength + 1;
309         else
310             step = fieldLength + 2;
311         bufPos += step;
312         int valueLength = lineLength - step;
313 
314         if (field == "data") {
315             if (valueLength)
316                 m_data.append(&m_receiveBuf[bufPos], valueLength);
317             m_data.append('\n');
318         } else if (field == "event")
319             m_eventName = valueLength ? String(&m_receiveBuf[bufPos], valueLength) : "";
320         else if (field == "id")
321             m_lastEventId = valueLength ? String(&m_receiveBuf[bufPos], valueLength) : "";
322         else if (field == "retry") {
323             if (!valueLength)
324                 m_reconnectDelay = defaultReconnectDelay;
325             else {
326                 String value(&m_receiveBuf[bufPos], valueLength);
327                 bool ok;
328                 unsigned long long retry = value.toUInt64(&ok);
329                 if (ok)
330                     m_reconnectDelay = retry;
331             }
332         }
333     }
334 }
335 
stop()336 void EventSource::stop()
337 {
338     close();
339 }
340 
createMessageEvent()341 PassRefPtr<MessageEvent> EventSource::createMessageEvent()
342 {
343     RefPtr<MessageEvent> event = MessageEvent::create();
344     event->initMessageEvent(m_eventName.isEmpty() ? eventNames().messageEvent : AtomicString(m_eventName), false, false, SerializedScriptValue::create(String::adopt(m_data)), m_origin, m_lastEventId, 0, 0);
345     return event.release();
346 }
347 
eventTargetData()348 EventTargetData* EventSource::eventTargetData()
349 {
350     return &m_eventTargetData;
351 }
352 
ensureEventTargetData()353 EventTargetData* EventSource::ensureEventTargetData()
354 {
355     return &m_eventTargetData;
356 }
357 
358 } // namespace WebCore
359 
360 #endif // ENABLE(EVENTSOURCE)
361