Home | History | Annotate | Download | only in gcm
      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 = 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       GetExtension()->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       GetExtension()->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       GetExtension()->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