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
23 namespace {
24
25 const size_t kMaximumMessageSize = 4096; // in bytes.
26 const char kCollapseKey[] = "collapse_key";
27 const char kGoogDotRestrictedPrefix[] = "goog.";
28 const char kGoogleRestrictedPrefix[] = "google";
29
30 // Error messages.
31 const char kInvalidParameter[] =
32 "Function was called with invalid parameters.";
33 const char kGCMDisabled[] = "GCM is currently disabled.";
34 const char kNotSignedIn[] = "Profile was not signed in.";
35 const char kAsyncOperationPending[] =
36 "Asynchronous operation is pending.";
37 const char kNetworkError[] = "Network error occurred.";
38 const char kServerError[] = "Server error occurred.";
39 const char kTtlExceeded[] = "Time-to-live exceeded.";
40 const char kUnknownError[] = "Unknown error occurred.";
41
GcmResultToError(gcm::GCMClient::Result result)42 const 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
IsMessageKeyValid(const std::string & key)71 bool 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
85 namespace extensions {
86
RunAsync()87 bool GcmApiFunction::RunAsync() {
88 if (!IsGcmApiEnabled())
89 return false;
90
91 return DoWork();
92 }
93
IsGcmApiEnabled() const94 bool 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
GetGCMDriver() const104 gcm::GCMDriver* GcmApiFunction::GetGCMDriver() const {
105 return gcm::GCMProfileServiceFactory::GetForProfile(
106 Profile::FromBrowserContext(browser_context()))->driver();
107 }
108
GcmRegisterFunction()109 GcmRegisterFunction::GcmRegisterFunction() {}
110
~GcmRegisterFunction()111 GcmRegisterFunction::~GcmRegisterFunction() {}
112
DoWork()113 bool 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
CompleteFunctionWithResult(const std::string & registration_id,gcm::GCMClient::Result result)126 void 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
GcmUnregisterFunction()134 GcmUnregisterFunction::GcmUnregisterFunction() {}
135
~GcmUnregisterFunction()136 GcmUnregisterFunction::~GcmUnregisterFunction() {}
137
DoWork()138 bool 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
CompleteFunctionWithResult(gcm::GCMClient::Result result)148 void GcmUnregisterFunction::CompleteFunctionWithResult(
149 gcm::GCMClient::Result result) {
150 SetError(GcmResultToError(result));
151 SendResponse(gcm::GCMClient::SUCCESS == result);
152 }
153
GcmSendFunction()154 GcmSendFunction::GcmSendFunction() {}
155
~GcmSendFunction()156 GcmSendFunction::~GcmSendFunction() {}
157
DoWork()158 bool 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
CompleteFunctionWithResult(const std::string & message_id,gcm::GCMClient::Result result)180 void 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
ValidateMessageData(const gcm::GCMClient::MessageData & data) const188 bool 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
GcmJsEventRouter(Profile * profile)205 GcmJsEventRouter::GcmJsEventRouter(Profile* profile) : profile_(profile) {
206 }
207
~GcmJsEventRouter()208 GcmJsEventRouter::~GcmJsEventRouter() {
209 }
210
OnMessage(const std::string & app_id,const gcm::GCMClient::IncomingMessage & message)211 void 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
OnMessagesDeleted(const std::string & app_id)226 void 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
OnSendError(const std::string & app_id,const gcm::GCMClient::SendErrorDetails & send_error_details)234 void 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