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 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 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 87 bool GcmApiFunction::RunAsync() { 88 if (!IsGcmApiEnabled()) 89 return false; 90 91 return DoWork(); 92 } 93 94 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 104 gcm::GCMDriver* GcmApiFunction::GetGCMDriver() const { 105 return gcm::GCMProfileServiceFactory::GetForProfile( 106 Profile::FromBrowserContext(browser_context()))->driver(); 107 } 108 109 GcmRegisterFunction::GcmRegisterFunction() {} 110 111 GcmRegisterFunction::~GcmRegisterFunction() {} 112 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 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 134 GcmUnregisterFunction::GcmUnregisterFunction() {} 135 136 GcmUnregisterFunction::~GcmUnregisterFunction() {} 137 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 148 void GcmUnregisterFunction::CompleteFunctionWithResult( 149 gcm::GCMClient::Result result) { 150 SetError(GcmResultToError(result)); 151 SendResponse(gcm::GCMClient::SUCCESS == result); 152 } 153 154 GcmSendFunction::GcmSendFunction() {} 155 156 GcmSendFunction::~GcmSendFunction() {} 157 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 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 188 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 205 GcmJsEventRouter::GcmJsEventRouter(Profile* profile) : profile_(profile) { 206 } 207 208 GcmJsEventRouter::~GcmJsEventRouter() { 209 } 210 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 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 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