1 /*
2  * Copyright (C) 2009, 2012 Ericsson AB. All rights reserved.
3  * Copyright (C) 2010 Apple Inc. All rights reserved.
4  * Copyright (C) 2011, Code Aurora Forum. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer
14  *    in the documentation and/or other materials provided with the
15  *    distribution.
16  * 3. Neither the name of Ericsson nor the names of its contributors
17  *    may be used to endorse or promote products derived from this
18  *    software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include "third_party/blink/renderer/modules/eventsource/event_source.h"
34 
35 #include <memory>
36 
37 #include "third_party/blink/public/platform/task_type.h"
38 #include "third_party/blink/public/platform/web_url_request.h"
39 #include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
40 #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h"
41 #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value_factory.h"
42 #include "third_party/blink/renderer/bindings/modules/v8/v8_event_source_init.h"
43 #include "third_party/blink/renderer/core/dom/events/event.h"
44 #include "third_party/blink/renderer/core/events/message_event.h"
45 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
46 #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
47 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
48 #include "third_party/blink/renderer/core/frame/local_frame.h"
49 #include "third_party/blink/renderer/core/inspector/console_message.h"
50 #include "third_party/blink/renderer/core/loader/threadable_loader.h"
51 #include "third_party/blink/renderer/core/probe/core_probes.h"
52 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
53 #include "third_party/blink/renderer/platform/heap/heap.h"
54 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
55 #include "third_party/blink/renderer/platform/loader/fetch/resource_error.h"
56 #include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
57 #include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
58 #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
59 #include "third_party/blink/renderer/platform/network/http_names.h"
60 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
61 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
62 
63 namespace blink {
64 
65 const uint64_t EventSource::kDefaultReconnectDelay = 3000;
66 
EventSource(ExecutionContext * context,const KURL & url,const EventSourceInit * event_source_init)67 inline EventSource::EventSource(ExecutionContext* context,
68                                 const KURL& url,
69                                 const EventSourceInit* event_source_init)
70     : ExecutionContextLifecycleObserver(context),
71       url_(url),
72       current_url_(url),
73       with_credentials_(event_source_init->withCredentials()),
74       state_(kConnecting),
75       connect_timer_(context->GetTaskRunner(TaskType::kRemoteEvent),
76                      this,
77                      &EventSource::ConnectTimerFired),
78       reconnect_delay_(kDefaultReconnectDelay) {}
79 
Create(ExecutionContext * context,const String & url,const EventSourceInit * event_source_init,ExceptionState & exception_state)80 EventSource* EventSource::Create(ExecutionContext* context,
81                                  const String& url,
82                                  const EventSourceInit* event_source_init,
83                                  ExceptionState& exception_state) {
84   UseCounter::Count(context, context->IsDocument()
85                                  ? WebFeature::kEventSourceDocument
86                                  : WebFeature::kEventSourceWorker);
87 
88   if (url.IsEmpty()) {
89     exception_state.ThrowDOMException(
90         DOMExceptionCode::kSyntaxError,
91         "Cannot open an EventSource to an empty URL.");
92     return nullptr;
93   }
94 
95   KURL full_url = context->CompleteURL(url);
96   if (!full_url.IsValid()) {
97     exception_state.ThrowDOMException(
98         DOMExceptionCode::kSyntaxError,
99         "Cannot open an EventSource to '" + url + "'. The URL is invalid.");
100     return nullptr;
101   }
102 
103   EventSource* source =
104       MakeGarbageCollected<EventSource>(context, full_url, event_source_init);
105 
106   source->ScheduleInitialConnect();
107   return source;
108 }
109 
~EventSource()110 EventSource::~EventSource() {
111   DCHECK_EQ(kClosed, state_);
112   DCHECK(!loader_);
113 }
114 
ScheduleInitialConnect()115 void EventSource::ScheduleInitialConnect() {
116   DCHECK_EQ(kConnecting, state_);
117   DCHECK(!loader_);
118 
119   connect_timer_.StartOneShot(base::TimeDelta(), FROM_HERE);
120 }
121 
Connect()122 void EventSource::Connect() {
123   DCHECK_EQ(kConnecting, state_);
124   DCHECK(!loader_);
125   DCHECK(GetExecutionContext());
126 
127   ExecutionContext& execution_context = *this->GetExecutionContext();
128   ResourceRequest request(current_url_);
129   request.SetHttpMethod(http_names::kGET);
130   request.SetHttpHeaderField(http_names::kAccept, "text/event-stream");
131   request.SetHttpHeaderField(http_names::kCacheControl, "no-cache");
132   request.SetRequestContext(mojom::RequestContextType::EVENT_SOURCE);
133   request.SetMode(network::mojom::RequestMode::kCors);
134   request.SetCredentialsMode(
135       with_credentials_ ? network::mojom::CredentialsMode::kInclude
136                         : network::mojom::CredentialsMode::kSameOrigin);
137   request.SetCacheMode(blink::mojom::FetchCacheMode::kNoStore);
138   request.SetExternalRequestStateFromRequestorAddressSpace(
139       execution_context.GetSecurityContext().AddressSpace());
140   request.SetCorsPreflightPolicy(
141       network::mojom::CorsPreflightPolicy::kPreventPreflight);
142   if (parser_ && !parser_->LastEventId().IsEmpty()) {
143     // HTTP headers are Latin-1 byte strings, but the Last-Event-ID header is
144     // encoded as UTF-8.
145     // TODO(davidben): This should be captured in the type of
146     // setHTTPHeaderField's arguments.
147     std::string last_event_id_utf8 = parser_->LastEventId().Utf8();
148     request.SetHttpHeaderField(
149         http_names::kLastEventID,
150         AtomicString(reinterpret_cast<const LChar*>(last_event_id_utf8.c_str()),
151                      last_event_id_utf8.length()));
152   }
153 
154   ResourceLoaderOptions resource_loader_options;
155   resource_loader_options.data_buffering_policy = kDoNotBufferData;
156 
157   probe::WillSendEventSourceRequest(&execution_context);
158   loader_ = MakeGarbageCollected<ThreadableLoader>(execution_context, this,
159                                                    resource_loader_options);
160   loader_->Start(request);
161 }
162 
NetworkRequestEnded()163 void EventSource::NetworkRequestEnded() {
164   loader_ = nullptr;
165 
166   if (state_ != kClosed)
167     ScheduleReconnect();
168 }
169 
ScheduleReconnect()170 void EventSource::ScheduleReconnect() {
171   state_ = kConnecting;
172   connect_timer_.StartOneShot(
173       base::TimeDelta::FromMilliseconds(reconnect_delay_), FROM_HERE);
174   DispatchEvent(*Event::Create(event_type_names::kError));
175 }
176 
ConnectTimerFired(TimerBase *)177 void EventSource::ConnectTimerFired(TimerBase*) {
178   Connect();
179 }
180 
url() const181 String EventSource::url() const {
182   return url_.GetString();
183 }
184 
withCredentials() const185 bool EventSource::withCredentials() const {
186   return with_credentials_;
187 }
188 
readyState() const189 EventSource::State EventSource::readyState() const {
190   return state_;
191 }
192 
close()193 void EventSource::close() {
194   if (state_ == kClosed) {
195     DCHECK(!loader_);
196     return;
197   }
198   if (parser_)
199     parser_->Stop();
200 
201   // Stop trying to reconnect if EventSource was explicitly closed or if
202   // contextDestroyed() was called.
203   if (connect_timer_.IsActive()) {
204     connect_timer_.Stop();
205   }
206 
207   state_ = kClosed;
208 
209   if (loader_) {
210     loader_->Cancel();
211     loader_ = nullptr;
212   }
213 
214 }
215 
InterfaceName() const216 const AtomicString& EventSource::InterfaceName() const {
217   return event_target_names::kEventSource;
218 }
219 
GetExecutionContext() const220 ExecutionContext* EventSource::GetExecutionContext() const {
221   return ExecutionContextLifecycleObserver::GetExecutionContext();
222 }
223 
DidReceiveResponse(uint64_t identifier,const ResourceResponse & response)224 void EventSource::DidReceiveResponse(uint64_t identifier,
225                                      const ResourceResponse& response) {
226   DCHECK_EQ(kConnecting, state_);
227   DCHECK(loader_);
228 
229   resource_identifier_ = identifier;
230   current_url_ = response.CurrentRequestUrl();
231   event_stream_origin_ =
232       SecurityOrigin::Create(response.CurrentRequestUrl())->ToString();
233   int status_code = response.HttpStatusCode();
234   bool mime_type_is_valid = response.MimeType() == "text/event-stream";
235   bool response_is_valid = status_code == 200 && mime_type_is_valid;
236   if (response_is_valid) {
237     const String& charset = response.TextEncodingName();
238     // If we have a charset, the only allowed value is UTF-8 (case-insensitive).
239     response_is_valid =
240         charset.IsEmpty() || EqualIgnoringASCIICase(charset, "UTF-8");
241     if (!response_is_valid) {
242       StringBuilder message;
243       message.Append("EventSource's response has a charset (\"");
244       message.Append(charset);
245       message.Append("\") that is not UTF-8. Aborting the connection.");
246       // FIXME: We are missing the source line.
247       GetExecutionContext()->AddConsoleMessage(
248           MakeGarbageCollected<ConsoleMessage>(
249               mojom::ConsoleMessageSource::kJavaScript,
250               mojom::ConsoleMessageLevel::kError, message.ToString()));
251     }
252   } else {
253     // To keep the signal-to-noise ratio low, we only log 200-response with an
254     // invalid MIME type.
255     if (status_code == 200 && !mime_type_is_valid) {
256       StringBuilder message;
257       message.Append("EventSource's response has a MIME type (\"");
258       message.Append(response.MimeType());
259       message.Append(
260           "\") that is not \"text/event-stream\". Aborting the connection.");
261       // FIXME: We are missing the source line.
262       GetExecutionContext()->AddConsoleMessage(
263           MakeGarbageCollected<ConsoleMessage>(
264               mojom::ConsoleMessageSource::kJavaScript,
265               mojom::ConsoleMessageLevel::kError, message.ToString()));
266     }
267   }
268 
269   if (response_is_valid) {
270     state_ = kOpen;
271     AtomicString last_event_id;
272     if (parser_) {
273       // The new parser takes over the event ID.
274       last_event_id = parser_->LastEventId();
275     }
276     parser_ = MakeGarbageCollected<EventSourceParser>(last_event_id, this);
277     DispatchEvent(*Event::Create(event_type_names::kOpen));
278   } else {
279     loader_->Cancel();
280   }
281 }
282 
DidReceiveData(const char * data,unsigned length)283 void EventSource::DidReceiveData(const char* data, unsigned length) {
284   DCHECK_EQ(kOpen, state_);
285   DCHECK(loader_);
286   DCHECK(parser_);
287 
288   parser_->AddBytes(data, length);
289 }
290 
DidFinishLoading(uint64_t)291 void EventSource::DidFinishLoading(uint64_t) {
292   DCHECK_EQ(kOpen, state_);
293   DCHECK(loader_);
294 
295   NetworkRequestEnded();
296 }
297 
DidFail(const ResourceError & error)298 void EventSource::DidFail(const ResourceError& error) {
299   DCHECK(loader_);
300   if (error.IsCancellation() && state_ == kClosed) {
301     NetworkRequestEnded();
302     return;
303   }
304 
305   DCHECK_NE(kClosed, state_);
306 
307   if (error.IsAccessCheck()) {
308     AbortConnectionAttempt();
309     return;
310   }
311 
312   if (error.IsCancellation()) {
313     // When the loading is cancelled for an external reason (e.g.,
314     // window.stop()), dispatch an error event and do not reconnect.
315     AbortConnectionAttempt();
316     return;
317   }
318   NetworkRequestEnded();
319 }
320 
DidFailRedirectCheck()321 void EventSource::DidFailRedirectCheck() {
322   DCHECK(loader_);
323 
324   AbortConnectionAttempt();
325 }
326 
OnMessageEvent(const AtomicString & event_type,const String & data,const AtomicString & last_event_id)327 void EventSource::OnMessageEvent(const AtomicString& event_type,
328                                  const String& data,
329                                  const AtomicString& last_event_id) {
330   MessageEvent* e = MessageEvent::Create();
331   e->initMessageEvent(event_type, false, false, data, event_stream_origin_,
332                       last_event_id, nullptr, nullptr);
333 
334   probe::WillDispatchEventSourceEvent(GetExecutionContext(),
335                                       resource_identifier_, event_type,
336                                       last_event_id, data);
337   DispatchEvent(*e);
338 }
339 
OnReconnectionTimeSet(uint64_t reconnection_time)340 void EventSource::OnReconnectionTimeSet(uint64_t reconnection_time) {
341   reconnect_delay_ = reconnection_time;
342 }
343 
AbortConnectionAttempt()344 void EventSource::AbortConnectionAttempt() {
345   DCHECK_NE(kClosed, state_);
346 
347   loader_ = nullptr;
348   state_ = kClosed;
349   NetworkRequestEnded();
350 
351   DispatchEvent(*Event::Create(event_type_names::kError));
352 }
353 
ContextDestroyed()354 void EventSource::ContextDestroyed() {
355   close();
356 }
357 
HasPendingActivity() const358 bool EventSource::HasPendingActivity() const {
359   return state_ != kClosed;
360 }
361 
Trace(Visitor * visitor)362 void EventSource::Trace(Visitor* visitor) {
363   visitor->Trace(parser_);
364   visitor->Trace(loader_);
365   EventTargetWithInlineData::Trace(visitor);
366   ThreadableLoaderClient::Trace(visitor);
367   ExecutionContextLifecycleObserver::Trace(visitor);
368   EventSourceParser::Client::Trace(visitor);
369 }
370 
371 }  // namespace blink
372