blob: 52c6d2c81863d09ff0fe79dd47745c2a316b4320 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/gcm/gcm_api.h"
#include <stddef.h>
#include <algorithm>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "chrome/browser/extensions/extension_gcm_app_handler.h"
#include "chrome/browser/gcm/gcm_profile_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/api/gcm.h"
#include "components/gcm_driver/common/gcm_message.h"
#include "components/gcm_driver/gcm_client.h"
#include "components/gcm_driver/gcm_driver.h"
#include "components/gcm_driver/gcm_profile_service.h"
#include "extensions/browser/event_router.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension.h"
static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));
namespace {
const size_t kMaximumGcmMessageSize = 4096; // in bytes.
const char kCollapseKey[] = "collapse_key";
const char kGoogDotRestrictedPrefix[] = "goog.";
const char kGoogleRestrictedPrefix[] = "google";
// Error messages.
const char kInvalidParameter[] =
"Function was called with invalid parameters.";
const char kGCMDisabled[] = "GCM is currently disabled.";
const char kAsyncOperationPending[] =
"Asynchronous operation is pending.";
const char kNetworkError[] = "Network error occurred.";
const char kServerError[] = "Server error occurred.";
const char kTtlExceeded[] = "Time-to-live exceeded.";
const char kUnknownError[] = "Unknown error occurred.";
const char* GcmResultToError(gcm::GCMClient::Result result) {
switch (result) {
case gcm::GCMClient::SUCCESS:
return "";
case gcm::GCMClient::INVALID_PARAMETER:
return kInvalidParameter;
case gcm::GCMClient::GCM_DISABLED:
return kGCMDisabled;
case gcm::GCMClient::ASYNC_OPERATION_PENDING:
return kAsyncOperationPending;
case gcm::GCMClient::NETWORK_ERROR:
return kNetworkError;
case gcm::GCMClient::SERVER_ERROR:
return kServerError;
case gcm::GCMClient::TTL_EXCEEDED:
return kTtlExceeded;
case gcm::GCMClient::UNKNOWN_ERROR:
return kUnknownError;
default:
NOTREACHED() << "Unexpected value of result cannot be converted: "
<< result;
}
}
bool IsMessageKeyValid(const std::string& key) {
std::string lower = base::ToLowerASCII(key);
return !key.empty() &&
key.compare(0, std::size(kCollapseKey) - 1, kCollapseKey) != 0 &&
lower.compare(0, std::size(kGoogleRestrictedPrefix) - 1,
kGoogleRestrictedPrefix) != 0 &&
lower.compare(0, std::size(kGoogDotRestrictedPrefix),
kGoogDotRestrictedPrefix) != 0;
}
} // namespace
namespace extensions {
bool GcmApiFunction::PreRunValidation(std::string* error) {
return IsGcmApiEnabled(error);
}
bool GcmApiFunction::IsGcmApiEnabled(std::string* error) const {
Profile* profile = Profile::FromBrowserContext(browser_context());
// GCM is not supported in incognito mode.
if (profile->IsOffTheRecord()) {
*error = "GCM is not supported in incognito mode.";
return false;
}
return true;
}
gcm::GCMDriver* GcmApiFunction::GetGCMDriver() const {
return gcm::GCMProfileServiceFactory::GetForProfile(
Profile::FromBrowserContext(browser_context()))->driver();
}
GcmRegisterFunction::GcmRegisterFunction() = default;
GcmRegisterFunction::~GcmRegisterFunction() = default;
ExtensionFunction::ResponseAction GcmRegisterFunction::Run() {
std::optional<api::gcm::Register::Params> params =
api::gcm::Register::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
#if BUILDFLAG(IS_ANDROID)
// This server API was deprecated by Firebase in 2019. Don't bother trying to
// implement register() on Android - it will just return an error.
// TODO(crbug.com/421235963): Consider deprecating on other platforms.
return RespondNow(Error(GcmResultToError(gcm::GCMClient::UNKNOWN_ERROR)));
#else
GetGCMDriver()->Register(
extension()->id(), params->sender_ids,
base::BindOnce(&GcmRegisterFunction::CompleteFunctionWithResult, this));
// Register() might have returned synchronously.
return did_respond() ? AlreadyResponded() : RespondLater();
#endif // BUILDFLAG(IS_ANDROID)
}
void GcmRegisterFunction::CompleteFunctionWithResult(
const std::string& registration_id,
gcm::GCMClient::Result gcm_result) {
base::Value::List result;
result.Append(registration_id);
const bool succeeded = gcm::GCMClient::SUCCESS == gcm_result;
Respond(succeeded
? ArgumentList(std::move(result))
// TODO(lazyboy): We shouldn't be using |result| in case of error.
: ErrorWithArgumentsDoNotUse(std::move(result),
GcmResultToError(gcm_result)));
}
GcmUnregisterFunction::GcmUnregisterFunction() = default;
GcmUnregisterFunction::~GcmUnregisterFunction() = default;
ExtensionFunction::ResponseAction GcmUnregisterFunction::Run() {
#if BUILDFLAG(IS_ANDROID)
// This server API was deprecated by Firebase in 2019. Don't bother trying to
// implement register() on Android - it will just return an error.
// TODO(crbug.com/421235963): Consider deprecating on other platforms.
return RespondNow(Error(GcmResultToError(gcm::GCMClient::UNKNOWN_ERROR)));
#else
GetGCMDriver()->Unregister(
extension()->id(),
base::BindOnce(&GcmUnregisterFunction::CompleteFunctionWithResult, this));
// Unregister might have responded already (synchronously).
return did_respond() ? AlreadyResponded() : RespondLater();
#endif // BUILDFLAG(IS_ANDROID)
}
void GcmUnregisterFunction::CompleteFunctionWithResult(
gcm::GCMClient::Result result) {
const bool succeeded = gcm::GCMClient::SUCCESS == result;
Respond(succeeded ? NoArguments() : Error(GcmResultToError(result)));
}
GcmSendFunction::GcmSendFunction() = default;
GcmSendFunction::~GcmSendFunction() = default;
ExtensionFunction::ResponseAction GcmSendFunction::Run() {
std::optional<api::gcm::Send::Params> params =
api::gcm::Send::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
EXTENSION_FUNCTION_VALIDATE(
ValidateMessageData(params->message.data.additional_properties));
gcm::OutgoingMessage outgoing_message;
outgoing_message.id = params->message.message_id;
outgoing_message.data = params->message.data.additional_properties;
if (params->message.time_to_live)
outgoing_message.time_to_live = *params->message.time_to_live;
GetGCMDriver()->Send(
extension()->id(), params->message.destination_id, outgoing_message,
base::BindOnce(&GcmSendFunction::CompleteFunctionWithResult, this));
// Send might have already responded synchronously.
return did_respond() ? AlreadyResponded() : RespondLater();
}
void GcmSendFunction::CompleteFunctionWithResult(
const std::string& message_id,
gcm::GCMClient::Result gcm_result) {
base::Value::List result;
result.Append(message_id);
const bool succeeded = gcm::GCMClient::SUCCESS == gcm_result;
Respond(succeeded
? ArgumentList(std::move(result))
// TODO(lazyboy): We shouldn't be using |result| in case of error.
: ErrorWithArgumentsDoNotUse(std::move(result),
GcmResultToError(gcm_result)));
}
bool GcmSendFunction::ValidateMessageData(const gcm::MessageData& data) const {
size_t total_size = 0u;
for (const auto& [key, value] : data) {
total_size += key.size() + value.size();
if (!IsMessageKeyValid(key) || kMaximumGcmMessageSize < key.size() ||
kMaximumGcmMessageSize < value.size() ||
kMaximumGcmMessageSize < total_size)
return false;
}
return total_size != 0;
}
GcmJsEventRouter::GcmJsEventRouter(Profile* profile) : profile_(profile) {
}
GcmJsEventRouter::~GcmJsEventRouter() = default;
void GcmJsEventRouter::OnMessage(const std::string& app_id,
const gcm::IncomingMessage& message) {
api::gcm::OnMessage::Message message_arg;
message_arg.data.additional_properties = message.data;
if (!message.sender_id.empty())
message_arg.from = message.sender_id;
if (!message.collapse_key.empty()) {
message_arg.collapse_key = message.collapse_key;
}
std::unique_ptr<Event> event(
new Event(events::GCM_ON_MESSAGE, api::gcm::OnMessage::kEventName,
api::gcm::OnMessage::Create(message_arg), profile_));
EventRouter::Get(profile_)
->DispatchEventToExtension(app_id, std::move(event));
}
void GcmJsEventRouter::OnMessagesDeleted(const std::string& app_id) {
std::unique_ptr<Event> event(new Event(
events::GCM_ON_MESSAGES_DELETED, api::gcm::OnMessagesDeleted::kEventName,
api::gcm::OnMessagesDeleted::Create(), profile_));
EventRouter::Get(profile_)
->DispatchEventToExtension(app_id, std::move(event));
}
void GcmJsEventRouter::OnSendError(
const std::string& app_id,
const gcm::GCMClient::SendErrorDetails& send_error_details) {
api::gcm::OnSendError::Error error;
error.message_id = send_error_details.message_id;
error.error_message = GcmResultToError(send_error_details.result);
error.details.additional_properties = send_error_details.additional_data;
std::unique_ptr<Event> event(
new Event(events::GCM_ON_SEND_ERROR, api::gcm::OnSendError::kEventName,
api::gcm::OnSendError::Create(error), profile_));
EventRouter::Get(profile_)
->DispatchEventToExtension(app_id, std::move(event));
}
} // namespace extensions