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.
5 #include "chrome/browser/extensions/api/gcm/gcm_api.h"
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 "extensions/common/extension.h"
23 const size_t kMaximumMessageSize = 4096; // in bytes.
24 const char kCollapseKey[] = "collapse_key";
25 const char kGoogDotRestrictedPrefix[] = "goog.";
26 const char kGoogleRestrictedPrefix[] = "google";
29 const char kInvalidParameter[] =
30 "Function was called with invalid parameters.";
31 const char kNotSignedIn[] = "Profile was not signed in.";
32 const char kAsyncOperationPending[] =
33 "Asynchronous operation is pending.";
34 const char kNetworkError[] = "Network error occurred.";
35 const char kServerError[] = "Server error occurred.";
36 const char kTtlExceeded[] = "Time-to-live exceeded.";
37 const char kUnknownError[] = "Unknown error occurred.";
39 const char* GcmResultToError(gcm::GCMClient::Result result) {
41 case gcm::GCMClient::SUCCESS:
43 case gcm::GCMClient::INVALID_PARAMETER:
44 return kInvalidParameter;
45 case gcm::GCMClient::NOT_SIGNED_IN:
47 case gcm::GCMClient::ASYNC_OPERATION_PENDING:
48 return kAsyncOperationPending;
49 case gcm::GCMClient::NETWORK_ERROR:
51 case gcm::GCMClient::SERVER_ERROR:
53 case gcm::GCMClient::TTL_EXCEEDED:
55 case gcm::GCMClient::UNKNOWN_ERROR:
58 NOTREACHED() << "Unexpected value of result cannot be converted: "
62 // Never reached, but prevents missing return statement warning.
66 bool IsMessageKeyValid(const std::string& key) {
67 std::string lower = StringToLowerASCII(key);
68 return !key.empty() &&
69 key.compare(0, arraysize(kCollapseKey) - 1, kCollapseKey) != 0 &&
71 arraysize(kGoogleRestrictedPrefix) - 1,
72 kGoogleRestrictedPrefix) != 0 &&
74 arraysize(kGoogDotRestrictedPrefix),
75 kGoogDotRestrictedPrefix) != 0;
80 namespace extensions {
82 bool GcmApiFunction::RunAsync() {
83 if (!IsGcmApiEnabled())
89 bool GcmApiFunction::IsGcmApiEnabled() const {
90 Profile* profile = Profile::FromBrowserContext(browser_context());
92 // GCM is not supported in incognito mode.
93 if (profile->IsOffTheRecord())
96 return gcm::GCMProfileService::GetGCMEnabledState(profile) !=
97 gcm::GCMProfileService::ALWAYS_DISABLED;
100 gcm::GCMProfileService* GcmApiFunction::GCMProfileService() const {
101 return gcm::GCMProfileServiceFactory::GetForProfile(
102 Profile::FromBrowserContext(browser_context()));
105 GcmRegisterFunction::GcmRegisterFunction() {}
107 GcmRegisterFunction::~GcmRegisterFunction() {}
109 bool GcmRegisterFunction::DoWork() {
110 scoped_ptr<api::gcm::Register::Params> params(
111 api::gcm::Register::Params::Create(*args_));
112 EXTENSION_FUNCTION_VALIDATE(params.get());
114 GCMProfileService()->Register(
115 GetExtension()->id(),
117 base::Bind(&GcmRegisterFunction::CompleteFunctionWithResult, this));
122 void GcmRegisterFunction::CompleteFunctionWithResult(
123 const std::string& registration_id,
124 gcm::GCMClient::Result result) {
125 SetResult(new base::StringValue(registration_id));
126 SetError(GcmResultToError(result));
127 SendResponse(gcm::GCMClient::SUCCESS == result);
130 GcmUnregisterFunction::GcmUnregisterFunction() {}
132 GcmUnregisterFunction::~GcmUnregisterFunction() {}
134 bool GcmUnregisterFunction::DoWork() {
135 UMA_HISTOGRAM_BOOLEAN("GCM.APICallUnregister", true);
137 GCMProfileService()->Unregister(
138 GetExtension()->id(),
139 base::Bind(&GcmUnregisterFunction::CompleteFunctionWithResult, this));
144 void GcmUnregisterFunction::CompleteFunctionWithResult(
145 gcm::GCMClient::Result result) {
146 SetError(GcmResultToError(result));
147 SendResponse(gcm::GCMClient::SUCCESS == result);
150 GcmSendFunction::GcmSendFunction() {}
152 GcmSendFunction::~GcmSendFunction() {}
154 bool GcmSendFunction::DoWork() {
155 scoped_ptr<api::gcm::Send::Params> params(
156 api::gcm::Send::Params::Create(*args_));
157 EXTENSION_FUNCTION_VALIDATE(params.get());
158 EXTENSION_FUNCTION_VALIDATE(
159 ValidateMessageData(params->message.data.additional_properties));
161 gcm::GCMClient::OutgoingMessage outgoing_message;
162 outgoing_message.id = params->message.message_id;
163 outgoing_message.data = params->message.data.additional_properties;
164 if (params->message.time_to_live.get())
165 outgoing_message.time_to_live = *params->message.time_to_live;
167 GCMProfileService()->Send(
168 GetExtension()->id(),
169 params->message.destination_id,
171 base::Bind(&GcmSendFunction::CompleteFunctionWithResult, this));
176 void GcmSendFunction::CompleteFunctionWithResult(
177 const std::string& message_id,
178 gcm::GCMClient::Result result) {
179 SetResult(new base::StringValue(message_id));
180 SetError(GcmResultToError(result));
181 SendResponse(gcm::GCMClient::SUCCESS == result);
184 bool GcmSendFunction::ValidateMessageData(
185 const gcm::GCMClient::MessageData& data) const {
186 size_t total_size = 0u;
187 for (std::map<std::string, std::string>::const_iterator iter = data.begin();
188 iter != data.end(); ++iter) {
189 total_size += iter->first.size() + iter->second.size();
191 if (!IsMessageKeyValid(iter->first) ||
192 kMaximumMessageSize < iter->first.size() ||
193 kMaximumMessageSize < iter->second.size() ||
194 kMaximumMessageSize < total_size)
198 return total_size != 0;
201 GcmJsEventRouter::GcmJsEventRouter(Profile* profile) : profile_(profile) {
202 EventRouter* event_router = EventRouter::Get(profile_);
206 event_router->RegisterObserver(this, api::gcm::OnMessage::kEventName);
207 event_router->RegisterObserver(this, api::gcm::OnMessagesDeleted::kEventName);
208 event_router->RegisterObserver(this, api::gcm::OnSendError::kEventName);
211 GcmJsEventRouter::~GcmJsEventRouter() {
212 EventRouter* event_router = EventRouter::Get(profile_);
214 event_router->UnregisterObserver(this);
217 void GcmJsEventRouter::OnMessage(
218 const std::string& app_id,
219 const gcm::GCMClient::IncomingMessage& message) {
220 api::gcm::OnMessage::Message message_arg;
221 message_arg.data.additional_properties = message.data;
222 if (!message.collapse_key.empty())
223 message_arg.collapse_key.reset(new std::string(message.collapse_key));
225 scoped_ptr<Event> event(new Event(
226 api::gcm::OnMessage::kEventName,
227 api::gcm::OnMessage::Create(message_arg).Pass(),
229 EventRouter::Get(profile_)->DispatchEventToExtension(app_id, event.Pass());
232 void GcmJsEventRouter::OnMessagesDeleted(const std::string& app_id) {
233 scoped_ptr<Event> event(new Event(
234 api::gcm::OnMessagesDeleted::kEventName,
235 api::gcm::OnMessagesDeleted::Create().Pass(),
237 EventRouter::Get(profile_)->DispatchEventToExtension(app_id, event.Pass());
240 void GcmJsEventRouter::OnSendError(
241 const std::string& app_id,
242 const gcm::GCMClient::SendErrorDetails& send_error_details) {
243 api::gcm::OnSendError::Error error;
244 error.message_id.reset(new std::string(send_error_details.message_id));
245 error.error_message = GcmResultToError(send_error_details.result);
246 error.details.additional_properties = send_error_details.additional_data;
248 scoped_ptr<Event> event(new Event(
249 api::gcm::OnSendError::kEventName,
250 api::gcm::OnSendError::Create(error).Pass(),
252 EventRouter::Get(profile_)->DispatchEventToExtension(app_id, event.Pass());
255 void GcmJsEventRouter::OnListenerAdded(const EventListenerInfo& details) {
256 if (gcm::GCMProfileService::GetGCMEnabledState(profile_) ==
257 gcm::GCMProfileService::ALWAYS_DISABLED) {
260 gcm::GCMProfileServiceFactory::GetForProfile(profile_)->Start();
263 } // namespace extensions