1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/services/gcm/push_messaging_service_impl.h"
6
7#include <vector>
8
9#include "base/bind.h"
10#include "base/command_line.h"
11#include "base/prefs/pref_service.h"
12#include "base/strings/string_util.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/services/gcm/gcm_profile_service.h"
15#include "chrome/browser/services/gcm/gcm_profile_service_factory.h"
16#include "chrome/browser/services/gcm/push_messaging_application_id.h"
17#include "chrome/browser/services/gcm/push_messaging_permission_context.h"
18#include "chrome/browser/services/gcm/push_messaging_permission_context_factory.h"
19#include "chrome/common/chrome_switches.h"
20#include "chrome/common/pref_names.h"
21#include "components/content_settings/core/common/permission_request_id.h"
22#include "components/gcm_driver/gcm_driver.h"
23#include "components/pref_registry/pref_registry_syncable.h"
24#include "content/public/browser/browser_context.h"
25#include "content/public/browser/render_frame_host.h"
26#include "content/public/browser/web_contents.h"
27
28namespace gcm {
29
30namespace {
31const int kMaxRegistrations = 1000000;
32}  // namespace
33
34// static
35void PushMessagingServiceImpl::RegisterProfilePrefs(
36    user_prefs::PrefRegistrySyncable* registry) {
37  registry->RegisterIntegerPref(
38      prefs::kPushMessagingRegistrationCount,
39      0,
40      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
41}
42
43// static
44void PushMessagingServiceImpl::InitializeForProfile(Profile* profile) {
45  // TODO(mvanouwerkerk): Make sure to remove this check at the same time as
46  // push graduates from experimental in Blink.
47  if (!CommandLine::ForCurrentProcess()->HasSwitch(
48          switches::kEnableExperimentalWebPlatformFeatures)) {
49    return;
50  }
51  // TODO(johnme): Consider whether push should be enabled in incognito. If it
52  // does get enabled, then be careful that you're reading the pref from the
53  // right profile, as prefs defined in a regular profile are visible in the
54  // corresponding incognito profile unless overrriden.
55  if (!profile || profile->IsOffTheRecord() ||
56      profile->GetPrefs()->GetInteger(prefs::kPushMessagingRegistrationCount) <=
57          0) {
58    return;
59  }
60  // Create the GCMProfileService, and hence instantiate this class.
61  GCMProfileService* gcm_service =
62      gcm::GCMProfileServiceFactory::GetForProfile(profile);
63  PushMessagingServiceImpl* push_service =
64      static_cast<PushMessagingServiceImpl*>(
65          gcm_service->push_messaging_service());
66  // Register ourselves as an app handler.
67  gcm_service->driver()->AddAppHandler(kPushMessagingApplicationIdPrefix,
68                                       push_service);
69}
70
71PushMessagingServiceImpl::PushMessagingServiceImpl(
72    GCMProfileService* gcm_profile_service,
73    Profile* profile)
74    : gcm_profile_service_(gcm_profile_service),
75      profile_(profile),
76      weak_factory_(this) {
77}
78
79PushMessagingServiceImpl::~PushMessagingServiceImpl() {
80  // TODO(johnme): If it's possible for this to be destroyed before GCMDriver,
81  // then we should call RemoveAppHandler.
82}
83
84bool PushMessagingServiceImpl::CanHandle(const std::string& app_id) const {
85  return PushMessagingApplicationId::Parse(app_id).IsValid();
86}
87
88void PushMessagingServiceImpl::ShutdownHandler() {
89  // TODO(johnme): Do any necessary cleanup.
90}
91
92void PushMessagingServiceImpl::OnMessage(
93    const std::string& app_id,
94    const GCMClient::IncomingMessage& message) {
95  // The Push API only exposes a single string of data in the push event fired
96  // on the Service Worker. When developers send messages using GCM to the Push
97  // API, they must pass a single key-value pair, where the key is "data" and
98  // the value is the string they want to be passed to their Service Worker.
99  // For example, they could send the following JSON using the HTTPS GCM API:
100  // {
101  //     "registration_ids": ["FOO", "BAR"],
102  //     "data": {
103  //         "data": "BAZ",
104  //     },
105  //     "delay_while_idle": true,
106  // }
107  // TODO(johnme): Make sure this is clearly documented for developers.
108  PushMessagingApplicationId application_id =
109      PushMessagingApplicationId::Parse(app_id);
110  DCHECK(application_id.IsValid());
111  GCMClient::MessageData::const_iterator it = message.data.find("data");
112  if (application_id.IsValid() && it != message.data.end()) {
113    const std::string& data = it->second;
114    content::BrowserContext::DeliverPushMessage(
115        profile_,
116        application_id.origin,
117        application_id.service_worker_registration_id,
118        data,
119        base::Bind(&PushMessagingServiceImpl::DeliverMessageCallback,
120                   weak_factory_.GetWeakPtr(),
121                   application_id,
122                   message));
123  } else {
124    // Drop the message, as it is invalid.
125    // TODO(mvanouwerkerk): Show a warning in the developer console of the
126    // Service Worker corresponding to app_id.
127    // TODO(johnme): Add diagnostic observers (e.g. UMA and an internals page)
128    // to know when bad things happen.
129  }
130}
131
132void PushMessagingServiceImpl::DeliverMessageCallback(
133    const PushMessagingApplicationId& application_id,
134    const GCMClient::IncomingMessage& message,
135    content::PushMessagingStatus status) {
136  // TODO(mvanouwerkerk): UMA logging.
137  // TODO(mvanouwerkerk): Is there a way to recover from failure?
138}
139
140void PushMessagingServiceImpl::OnMessagesDeleted(const std::string& app_id) {
141  // TODO(mvanouwerkerk): Fire push error event on the Service Worker
142  // corresponding to app_id.
143}
144
145void PushMessagingServiceImpl::OnSendError(
146    const std::string& app_id,
147    const GCMClient::SendErrorDetails& send_error_details) {
148  NOTREACHED() << "The Push API shouldn't have sent messages upstream";
149}
150
151void PushMessagingServiceImpl::OnSendAcknowledged(
152    const std::string& app_id,
153    const std::string& message_id) {
154  NOTREACHED() << "The Push API shouldn't have sent messages upstream";
155}
156
157void PushMessagingServiceImpl::Register(
158    const GURL& origin,
159    int64 service_worker_registration_id,
160    const std::string& sender_id,
161    int renderer_id,
162    int render_frame_id,
163    bool user_gesture,
164    const content::PushMessagingService::RegisterCallback& callback) {
165  if (!gcm_profile_service_->driver()) {
166    NOTREACHED() << "There is no GCMDriver. Has GCMProfileService shut down?";
167  }
168
169  PushMessagingApplicationId application_id =
170      PushMessagingApplicationId(origin, service_worker_registration_id);
171  DCHECK(application_id.IsValid());
172
173  if (profile_->GetPrefs()->GetInteger(
174          prefs::kPushMessagingRegistrationCount) >= kMaxRegistrations) {
175    RegisterEnd(
176        callback,
177        std::string(),
178        content::PUSH_MESSAGING_STATUS_REGISTRATION_FAILED_LIMIT_REACHED);
179    return;
180  }
181
182  // If this is registering for the first time then the driver does not have
183  // this as an app handler and registration would fail.
184  if (gcm_profile_service_->driver()->GetAppHandler(
185          kPushMessagingApplicationIdPrefix) != this)
186    gcm_profile_service_->driver()->AddAppHandler(
187        kPushMessagingApplicationIdPrefix, this);
188
189  content::RenderFrameHost* render_frame_host =
190      content::RenderFrameHost::FromID(renderer_id, render_frame_id);
191
192  // The frame doesn't exist any more, or we received a bad frame id.
193  if (!render_frame_host)
194    return;
195
196  content::WebContents* web_contents =
197      content::WebContents::FromRenderFrameHost(render_frame_host);
198
199  // The page doesn't exist any more or we got a bad render frame host.
200  if (!web_contents)
201    return;
202
203  // TODO(miguelg) need to send this over IPC when bubble support is
204  // implemented.
205  int bridge_id = -1;
206
207  const PermissionRequestID id(
208      renderer_id, web_contents->GetRoutingID(), bridge_id, GURL());
209
210  GURL embedder = web_contents->GetLastCommittedURL();
211  gcm::PushMessagingPermissionContext* permission_context =
212      gcm::PushMessagingPermissionContextFactory::GetForProfile(profile_);
213
214  if (permission_context == NULL) {
215    RegisterEnd(
216        callback,
217        std::string(),
218        content::PUSH_MESSAGING_STATUS_REGISTRATION_FAILED_PERMISSION_DENIED);
219    return;
220  }
221
222  permission_context->RequestPermission(
223      web_contents,
224      id,
225      embedder,
226      user_gesture,
227      base::Bind(&PushMessagingServiceImpl::DidRequestPermission,
228                 weak_factory_.GetWeakPtr(),
229                 application_id,
230                 sender_id,
231                 callback));
232}
233
234void PushMessagingServiceImpl::RegisterEnd(
235    const content::PushMessagingService::RegisterCallback& callback,
236    const std::string& registration_id,
237    content::PushMessagingStatus status) {
238  GURL endpoint = GURL("https://android.googleapis.com/gcm/send");
239  callback.Run(endpoint, registration_id, status);
240  if (status == content::PUSH_MESSAGING_STATUS_OK) {
241    // TODO(johnme): Make sure the pref doesn't get out of sync after crashes.
242    int registration_count = profile_->GetPrefs()->GetInteger(
243        prefs::kPushMessagingRegistrationCount);
244    profile_->GetPrefs()->SetInteger(prefs::kPushMessagingRegistrationCount,
245                                     registration_count + 1);
246  }
247}
248
249void PushMessagingServiceImpl::DidRegister(
250    const content::PushMessagingService::RegisterCallback& callback,
251    const std::string& registration_id,
252    GCMClient::Result result) {
253  content::PushMessagingStatus status =
254      result == GCMClient::SUCCESS
255          ? content::PUSH_MESSAGING_STATUS_OK
256          : content::PUSH_MESSAGING_STATUS_REGISTRATION_FAILED_SERVICE_ERROR;
257  RegisterEnd(callback, registration_id, status);
258}
259
260void PushMessagingServiceImpl::DidRequestPermission(
261    const PushMessagingApplicationId& application_id,
262    const std::string& sender_id,
263    const content::PushMessagingService::RegisterCallback& register_callback,
264    bool allow) {
265  if (!allow) {
266    RegisterEnd(
267        register_callback,
268        std::string(),
269        content::PUSH_MESSAGING_STATUS_REGISTRATION_FAILED_PERMISSION_DENIED);
270    return;
271  }
272
273  // The GCMDriver could be NULL if GCMProfileService has been shut down.
274  if (!gcm_profile_service_->driver())
275    return;
276
277  std::vector<std::string> sender_ids(1, sender_id);
278
279  gcm_profile_service_->driver()->Register(
280      application_id.ToString(),
281      sender_ids,
282      base::Bind(&PushMessagingServiceImpl::DidRegister,
283                 weak_factory_.GetWeakPtr(),
284                 register_callback));
285}
286
287// TODO(johnme): Unregister should decrement the pref, and call
288// RemoveAppHandler if the count drops to zero.
289
290}  // namespace gcm
291