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