1// Copyright 2013 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/extensions/api/gcm/gcm_api.h"
6
7#include <algorithm>
8#include <map>
9#include <vector>
10
11#include "base/metrics/histogram.h"
12#include "base/strings/string_number_conversions.h"
13#include "base/strings/string_util.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/services/gcm/gcm_profile_service.h"
17#include "chrome/browser/services/gcm/gcm_profile_service_factory.h"
18#include "chrome/common/extensions/api/gcm.h"
19#include "components/gcm_driver/gcm_driver.h"
20#include "extensions/browser/event_router.h"
21#include "extensions/common/extension.h"
22
23namespace {
24
25const size_t kMaximumMessageSize = 4096;  // in bytes.
26const char kCollapseKey[] = "collapse_key";
27const char kGoogDotRestrictedPrefix[] = "goog.";
28const char kGoogleRestrictedPrefix[] = "google";
29
30// Error messages.
31const char kInvalidParameter[] =
32    "Function was called with invalid parameters.";
33const char kGCMDisabled[] = "GCM is currently disabled.";
34const char kNotSignedIn[] = "Profile was not signed in.";
35const char kAsyncOperationPending[] =
36    "Asynchronous operation is pending.";
37const char kNetworkError[] = "Network error occurred.";
38const char kServerError[] = "Server error occurred.";
39const char kTtlExceeded[] = "Time-to-live exceeded.";
40const char kUnknownError[] = "Unknown error occurred.";
41
42const char* GcmResultToError(gcm::GCMClient::Result result) {
43  switch (result) {
44    case gcm::GCMClient::SUCCESS:
45      return "";
46    case gcm::GCMClient::INVALID_PARAMETER:
47      return kInvalidParameter;
48    case gcm::GCMClient::GCM_DISABLED:
49      return kGCMDisabled;
50    case gcm::GCMClient::NOT_SIGNED_IN:
51      return kNotSignedIn;
52    case gcm::GCMClient::ASYNC_OPERATION_PENDING:
53      return kAsyncOperationPending;
54    case gcm::GCMClient::NETWORK_ERROR:
55      return kNetworkError;
56    case gcm::GCMClient::SERVER_ERROR:
57      return kServerError;
58    case gcm::GCMClient::TTL_EXCEEDED:
59      return kTtlExceeded;
60    case gcm::GCMClient::UNKNOWN_ERROR:
61      return kUnknownError;
62    default:
63      NOTREACHED() << "Unexpected value of result cannot be converted: "
64                   << result;
65  }
66
67  // Never reached, but prevents missing return statement warning.
68  return "";
69}
70
71bool IsMessageKeyValid(const std::string& key) {
72  std::string lower = base::StringToLowerASCII(key);
73  return !key.empty() &&
74         key.compare(0, arraysize(kCollapseKey) - 1, kCollapseKey) != 0 &&
75         lower.compare(0,
76                       arraysize(kGoogleRestrictedPrefix) - 1,
77                       kGoogleRestrictedPrefix) != 0 &&
78         lower.compare(0,
79                       arraysize(kGoogDotRestrictedPrefix),
80                       kGoogDotRestrictedPrefix) != 0;
81}
82
83}  // namespace
84
85namespace extensions {
86
87bool GcmApiFunction::RunAsync() {
88  if (!IsGcmApiEnabled())
89    return false;
90
91  return DoWork();
92}
93
94bool GcmApiFunction::IsGcmApiEnabled() const {
95  Profile* profile = Profile::FromBrowserContext(browser_context());
96
97  // GCM is not supported in incognito mode.
98  if (profile->IsOffTheRecord())
99    return false;
100
101  return gcm::GCMProfileService::IsGCMEnabled(profile);
102}
103
104gcm::GCMDriver* GcmApiFunction::GetGCMDriver() const {
105  return gcm::GCMProfileServiceFactory::GetForProfile(
106      Profile::FromBrowserContext(browser_context()))->driver();
107}
108
109GcmRegisterFunction::GcmRegisterFunction() {}
110
111GcmRegisterFunction::~GcmRegisterFunction() {}
112
113bool GcmRegisterFunction::DoWork() {
114  scoped_ptr<api::gcm::Register::Params> params(
115      api::gcm::Register::Params::Create(*args_));
116  EXTENSION_FUNCTION_VALIDATE(params.get());
117
118  GetGCMDriver()->Register(
119      extension()->id(),
120      params->sender_ids,
121      base::Bind(&GcmRegisterFunction::CompleteFunctionWithResult, this));
122
123  return true;
124}
125
126void GcmRegisterFunction::CompleteFunctionWithResult(
127    const std::string& registration_id,
128    gcm::GCMClient::Result result) {
129  SetResult(new base::StringValue(registration_id));
130  SetError(GcmResultToError(result));
131  SendResponse(gcm::GCMClient::SUCCESS == result);
132}
133
134GcmUnregisterFunction::GcmUnregisterFunction() {}
135
136GcmUnregisterFunction::~GcmUnregisterFunction() {}
137
138bool GcmUnregisterFunction::DoWork() {
139  UMA_HISTOGRAM_BOOLEAN("GCM.APICallUnregister", true);
140
141  GetGCMDriver()->Unregister(
142      extension()->id(),
143      base::Bind(&GcmUnregisterFunction::CompleteFunctionWithResult, this));
144
145  return true;
146}
147
148void GcmUnregisterFunction::CompleteFunctionWithResult(
149    gcm::GCMClient::Result result) {
150  SetError(GcmResultToError(result));
151  SendResponse(gcm::GCMClient::SUCCESS == result);
152}
153
154GcmSendFunction::GcmSendFunction() {}
155
156GcmSendFunction::~GcmSendFunction() {}
157
158bool GcmSendFunction::DoWork() {
159  scoped_ptr<api::gcm::Send::Params> params(
160      api::gcm::Send::Params::Create(*args_));
161  EXTENSION_FUNCTION_VALIDATE(params.get());
162  EXTENSION_FUNCTION_VALIDATE(
163      ValidateMessageData(params->message.data.additional_properties));
164
165  gcm::GCMClient::OutgoingMessage outgoing_message;
166  outgoing_message.id = params->message.message_id;
167  outgoing_message.data = params->message.data.additional_properties;
168  if (params->message.time_to_live.get())
169    outgoing_message.time_to_live = *params->message.time_to_live;
170
171  GetGCMDriver()->Send(
172      extension()->id(),
173      params->message.destination_id,
174      outgoing_message,
175      base::Bind(&GcmSendFunction::CompleteFunctionWithResult, this));
176
177  return true;
178}
179
180void GcmSendFunction::CompleteFunctionWithResult(
181    const std::string& message_id,
182    gcm::GCMClient::Result result) {
183  SetResult(new base::StringValue(message_id));
184  SetError(GcmResultToError(result));
185  SendResponse(gcm::GCMClient::SUCCESS == result);
186}
187
188bool GcmSendFunction::ValidateMessageData(
189    const gcm::GCMClient::MessageData& data) const {
190  size_t total_size = 0u;
191  for (std::map<std::string, std::string>::const_iterator iter = data.begin();
192       iter != data.end(); ++iter) {
193    total_size += iter->first.size() + iter->second.size();
194
195    if (!IsMessageKeyValid(iter->first) ||
196        kMaximumMessageSize < iter->first.size() ||
197        kMaximumMessageSize < iter->second.size() ||
198        kMaximumMessageSize < total_size)
199      return false;
200  }
201
202  return total_size != 0;
203}
204
205GcmJsEventRouter::GcmJsEventRouter(Profile* profile) : profile_(profile) {
206}
207
208GcmJsEventRouter::~GcmJsEventRouter() {
209}
210
211void GcmJsEventRouter::OnMessage(
212    const std::string& app_id,
213    const gcm::GCMClient::IncomingMessage& message) {
214  api::gcm::OnMessage::Message message_arg;
215  message_arg.data.additional_properties = message.data;
216  if (!message.collapse_key.empty())
217    message_arg.collapse_key.reset(new std::string(message.collapse_key));
218
219  scoped_ptr<Event> event(new Event(
220      api::gcm::OnMessage::kEventName,
221      api::gcm::OnMessage::Create(message_arg).Pass(),
222      profile_));
223  EventRouter::Get(profile_)->DispatchEventToExtension(app_id, event.Pass());
224}
225
226void GcmJsEventRouter::OnMessagesDeleted(const std::string& app_id) {
227  scoped_ptr<Event> event(new Event(
228      api::gcm::OnMessagesDeleted::kEventName,
229      api::gcm::OnMessagesDeleted::Create().Pass(),
230      profile_));
231  EventRouter::Get(profile_)->DispatchEventToExtension(app_id, event.Pass());
232}
233
234void GcmJsEventRouter::OnSendError(
235    const std::string& app_id,
236    const gcm::GCMClient::SendErrorDetails& send_error_details) {
237  api::gcm::OnSendError::Error error;
238  error.message_id.reset(new std::string(send_error_details.message_id));
239  error.error_message = GcmResultToError(send_error_details.result);
240  error.details.additional_properties = send_error_details.additional_data;
241
242  scoped_ptr<Event> event(new Event(
243      api::gcm::OnSendError::kEventName,
244      api::gcm::OnSendError::Create(error).Pass(),
245      profile_));
246  EventRouter::Get(profile_)->DispatchEventToExtension(app_id, event.Pass());
247}
248
249}  // namespace extensions
250