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