| // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "chrome/browser/extensions/extension_sync_data.h" | 
 |  | 
 | #include "base/logging.h" | 
 | #include "base/metrics/histogram_macros.h" | 
 | #include "base/strings/stringprintf.h" | 
 | #include "chrome/browser/extensions/convert_web_app.h" | 
 | #include "chrome/browser/extensions/extension_service.h" | 
 | #include "chrome/common/extensions/manifest_handlers/app_icon_color_info.h" | 
 | #include "chrome/common/extensions/manifest_handlers/app_launch_info.h" | 
 | #include "chrome/common/extensions/manifest_handlers/app_theme_color_info.h" | 
 | #include "chrome/common/extensions/manifest_handlers/linked_app_icons.h" | 
 | #include "components/crx_file/id_util.h" | 
 | #include "components/sync/model/sync_data.h" | 
 | #include "components/sync/protocol/app_specifics.pb.h" | 
 | #include "components/sync/protocol/extension_specifics.pb.h" | 
 | #include "components/sync/protocol/sync.pb.h" | 
 | #include "extensions/common/extension.h" | 
 | #include "extensions/common/manifest_url_handlers.h" | 
 |  | 
 | using syncer::StringOrdinal; | 
 |  | 
 | namespace extensions { | 
 |  | 
 | namespace { | 
 |  | 
 | std::string GetExtensionSpecificsLogMessage( | 
 |     const sync_pb::ExtensionSpecifics& specifics) { | 
 |   return base::StringPrintf( | 
 |       "id: %s\nversion: %s\nupdate_url: %s\nenabled: %i\ndisable_reasons: %i", | 
 |       specifics.id().c_str(), | 
 |       specifics.version().c_str(), | 
 |       specifics.update_url().c_str(), | 
 |       specifics.enabled(), | 
 |       specifics.disable_reasons()); | 
 | } | 
 |  | 
 | enum BadSyncDataReason { | 
 |   // Invalid extension ID. | 
 |   BAD_EXTENSION_ID, | 
 |  | 
 |   // Invalid version. | 
 |   BAD_VERSION, | 
 |  | 
 |   // Invalid update URL. | 
 |   BAD_UPDATE_URL, | 
 |  | 
 |   // No ExtensionSpecifics in the EntitySpecifics. | 
 |   NO_EXTENSION_SPECIFICS, | 
 |  | 
 |   // Not used anymore; still here because of UMA. | 
 |   DEPRECATED_BAD_DISABLE_REASONS, | 
 |  | 
 |   // Must be at the end. | 
 |   NUM_BAD_SYNC_DATA_REASONS | 
 | }; | 
 |  | 
 | void RecordBadSyncData(BadSyncDataReason reason) { | 
 |   UMA_HISTOGRAM_ENUMERATION("Extensions.BadSyncDataReason", reason, | 
 |                             NUM_BAD_SYNC_DATA_REASONS); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | ExtensionSyncData::LinkedAppIconInfo::LinkedAppIconInfo() { | 
 | } | 
 |  | 
 | ExtensionSyncData::LinkedAppIconInfo::~LinkedAppIconInfo() { | 
 | } | 
 |  | 
 | ExtensionSyncData::ExtensionSyncData() | 
 |     : is_app_(false), | 
 |       uninstalled_(false), | 
 |       enabled_(false), | 
 |       supports_disable_reasons_(false), | 
 |       disable_reasons_(disable_reason::DISABLE_NONE), | 
 |       incognito_enabled_(false), | 
 |       remote_install_(false), | 
 |       launch_type_(LAUNCH_TYPE_INVALID) {} | 
 |  | 
 | ExtensionSyncData::ExtensionSyncData(const Extension& extension, | 
 |                                      bool enabled, | 
 |                                      int disable_reasons, | 
 |                                      bool incognito_enabled, | 
 |                                      bool remote_install, | 
 |                                      const GURL& update_url) | 
 |     : ExtensionSyncData(extension, | 
 |                         enabled, | 
 |                         disable_reasons, | 
 |                         incognito_enabled, | 
 |                         remote_install, | 
 |                         update_url, | 
 |                         StringOrdinal(), | 
 |                         StringOrdinal(), | 
 |                         LAUNCH_TYPE_INVALID) {} | 
 |  | 
 | ExtensionSyncData::ExtensionSyncData(const Extension& extension, | 
 |                                      bool enabled, | 
 |                                      int disable_reasons, | 
 |                                      bool incognito_enabled, | 
 |                                      bool remote_install, | 
 |                                      const GURL& update_url, | 
 |                                      const StringOrdinal& app_launch_ordinal, | 
 |                                      const StringOrdinal& page_ordinal, | 
 |                                      extensions::LaunchType launch_type) | 
 |     : is_app_(extension.is_app()), | 
 |       id_(extension.id()), | 
 |       uninstalled_(false), | 
 |       enabled_(enabled), | 
 |       supports_disable_reasons_(true), | 
 |       disable_reasons_(disable_reasons), | 
 |       incognito_enabled_(incognito_enabled), | 
 |       remote_install_(remote_install), | 
 |       version_(extension.from_bookmark() ? base::Version("0") | 
 |                                          : extension.version()), | 
 |       update_url_(update_url), | 
 |       name_(extension.non_localized_name()), | 
 |       app_launch_ordinal_(app_launch_ordinal), | 
 |       page_ordinal_(page_ordinal), | 
 |       launch_type_(launch_type) { | 
 |   if (is_app_ && extension.from_bookmark()) { | 
 |     bookmark_app_description_ = extension.description(); | 
 |     bookmark_app_url_ = AppLaunchInfo::GetLaunchWebURL(&extension).spec(); | 
 |     bookmark_app_scope_ = GetScopeURLFromBookmarkApp(&extension).spec(); | 
 |     bookmark_app_icon_color_ = AppIconColorInfo::GetIconColorString(&extension); | 
 |     bookmark_app_theme_color_ = AppThemeColorInfo::GetThemeColor(&extension); | 
 |     extensions::LinkedAppIcons icons = | 
 |         LinkedAppIcons::GetLinkedAppIcons(&extension); | 
 |     for (const auto& icon : icons.icons) { | 
 |       LinkedAppIconInfo linked_icon; | 
 |       linked_icon.url = icon.url; | 
 |       linked_icon.size = icon.size; | 
 |       linked_icons_.push_back(linked_icon); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | ExtensionSyncData::ExtensionSyncData(const ExtensionSyncData& other) = default; | 
 |  | 
 | ExtensionSyncData::~ExtensionSyncData() {} | 
 |  | 
 | // static | 
 | std::unique_ptr<ExtensionSyncData> ExtensionSyncData::CreateFromSyncData( | 
 |     const syncer::SyncData& sync_data) { | 
 |   std::unique_ptr<ExtensionSyncData> data(new ExtensionSyncData); | 
 |   if (data->PopulateFromSyncData(sync_data)) | 
 |     return data; | 
 |   return nullptr; | 
 | } | 
 |  | 
 | // static | 
 | std::unique_ptr<ExtensionSyncData> ExtensionSyncData::CreateFromSyncChange( | 
 |     const syncer::SyncChange& sync_change) { | 
 |   std::unique_ptr<ExtensionSyncData> data( | 
 |       CreateFromSyncData(sync_change.sync_data())); | 
 |   if (!data.get()) | 
 |     return nullptr; | 
 |  | 
 |   if (sync_change.change_type() == syncer::SyncChange::ACTION_DELETE) | 
 |     data->uninstalled_ = true; | 
 |   return data; | 
 | } | 
 |  | 
 | syncer::SyncData ExtensionSyncData::GetSyncData() const { | 
 |   sync_pb::EntitySpecifics specifics; | 
 |   if (is_app_) | 
 |     ToAppSpecifics(specifics.mutable_app()); | 
 |   else | 
 |     ToExtensionSpecifics(specifics.mutable_extension()); | 
 |  | 
 |   return syncer::SyncData::CreateLocalData(id_, name_, specifics); | 
 | } | 
 |  | 
 | syncer::SyncChange ExtensionSyncData::GetSyncChange( | 
 |     syncer::SyncChange::SyncChangeType change_type) const { | 
 |   return syncer::SyncChange(FROM_HERE, change_type, GetSyncData()); | 
 | } | 
 |  | 
 | void ExtensionSyncData::ToExtensionSpecifics( | 
 |     sync_pb::ExtensionSpecifics* specifics) const { | 
 |   DCHECK(crx_file::id_util::IdIsValid(id_)); | 
 |   specifics->set_id(id_); | 
 |   specifics->set_update_url(update_url_.spec()); | 
 |   specifics->set_version(version_.GetString()); | 
 |   specifics->set_enabled(enabled_); | 
 |   if (supports_disable_reasons_) | 
 |     specifics->set_disable_reasons(disable_reasons_); | 
 |   specifics->set_incognito_enabled(incognito_enabled_); | 
 |   specifics->set_remote_install(remote_install_); | 
 |   specifics->set_name(name_); | 
 | } | 
 |  | 
 | void ExtensionSyncData::ToAppSpecifics(sync_pb::AppSpecifics* specifics) const { | 
 |   DCHECK(specifics); | 
 |   // Only sync the ordinal values and launch type if they are valid. | 
 |   if (app_launch_ordinal_.IsValid()) | 
 |     specifics->set_app_launch_ordinal(app_launch_ordinal_.ToInternalValue()); | 
 |   if (page_ordinal_.IsValid()) | 
 |     specifics->set_page_ordinal(page_ordinal_.ToInternalValue()); | 
 |  | 
 |   sync_pb::AppSpecifics::LaunchType sync_launch_type = | 
 |       static_cast<sync_pb::AppSpecifics::LaunchType>(launch_type_); | 
 |  | 
 |   // The corresponding validation of this value during processing of an | 
 |   // ExtensionSyncData is in ExtensionSyncService::ApplySyncData. | 
 |   if (launch_type_ >= LAUNCH_TYPE_FIRST && launch_type_ < NUM_LAUNCH_TYPES && | 
 |       sync_pb::AppSpecifics_LaunchType_IsValid(sync_launch_type)) { | 
 |     specifics->set_launch_type(sync_launch_type); | 
 |   } | 
 |  | 
 |   if (!bookmark_app_url_.empty()) | 
 |     specifics->set_bookmark_app_url(bookmark_app_url_); | 
 |  | 
 |   if (!bookmark_app_description_.empty()) | 
 |     specifics->set_bookmark_app_description(bookmark_app_description_); | 
 |  | 
 |   if (!bookmark_app_scope_.empty()) | 
 |     specifics->set_bookmark_app_scope(bookmark_app_scope_); | 
 |  | 
 |   if (!bookmark_app_icon_color_.empty()) | 
 |     specifics->set_bookmark_app_icon_color(bookmark_app_icon_color_); | 
 |  | 
 |   if (bookmark_app_theme_color_) | 
 |     specifics->set_bookmark_app_theme_color(bookmark_app_theme_color_.value()); | 
 |  | 
 |   for (const auto& linked_icon : linked_icons_) { | 
 |     sync_pb::LinkedAppIconInfo* linked_app_icon_info = | 
 |         specifics->add_linked_app_icons(); | 
 |     DCHECK(linked_icon.url.is_valid()); | 
 |     linked_app_icon_info->set_url(linked_icon.url.spec()); | 
 |     linked_app_icon_info->set_size(linked_icon.size); | 
 |   } | 
 |  | 
 |   ToExtensionSpecifics(specifics->mutable_extension()); | 
 | } | 
 |  | 
 | bool ExtensionSyncData::PopulateFromExtensionSpecifics( | 
 |     const sync_pb::ExtensionSpecifics& specifics) { | 
 |   if (!crx_file::id_util::IdIsValid(specifics.id())) { | 
 |     LOG(ERROR) << "Attempt to sync bad ExtensionSpecifics (bad ID):\n" | 
 |                << GetExtensionSpecificsLogMessage(specifics); | 
 |     RecordBadSyncData(BAD_EXTENSION_ID); | 
 |     return false; | 
 |   } | 
 |  | 
 |   base::Version specifics_version(specifics.version()); | 
 |   if (!specifics_version.IsValid()) { | 
 |     LOG(ERROR) << "Attempt to sync bad ExtensionSpecifics (bad version):\n" | 
 |                << GetExtensionSpecificsLogMessage(specifics); | 
 |     RecordBadSyncData(BAD_VERSION); | 
 |     return false; | 
 |   } | 
 |  | 
 |   // The update URL must be either empty or valid. | 
 |   GURL specifics_update_url(specifics.update_url()); | 
 |   if (!specifics_update_url.is_empty() && !specifics_update_url.is_valid()) { | 
 |     LOG(ERROR) << "Attempt to sync bad ExtensionSpecifics (bad update URL):\n" | 
 |                << GetExtensionSpecificsLogMessage(specifics); | 
 |     RecordBadSyncData(BAD_UPDATE_URL); | 
 |     return false; | 
 |   } | 
 |  | 
 |   id_ = specifics.id(); | 
 |   update_url_ = specifics_update_url; | 
 |   version_ = specifics_version; | 
 |   enabled_ = specifics.enabled(); | 
 |   supports_disable_reasons_ = specifics.has_disable_reasons(); | 
 |   disable_reasons_ = specifics.disable_reasons(); | 
 |   incognito_enabled_ = specifics.incognito_enabled(); | 
 |   remote_install_ = specifics.remote_install(); | 
 |   name_ = specifics.name(); | 
 |   return true; | 
 | } | 
 |  | 
 | bool ExtensionSyncData::PopulateFromAppSpecifics( | 
 |     const sync_pb::AppSpecifics& specifics) { | 
 |   if (!PopulateFromExtensionSpecifics(specifics.extension())) | 
 |     return false; | 
 |  | 
 |   is_app_ = true; | 
 |  | 
 |   app_launch_ordinal_ = syncer::StringOrdinal(specifics.app_launch_ordinal()); | 
 |   page_ordinal_ = syncer::StringOrdinal(specifics.page_ordinal()); | 
 |  | 
 |   launch_type_ = specifics.has_launch_type() | 
 |       ? static_cast<extensions::LaunchType>(specifics.launch_type()) | 
 |       : LAUNCH_TYPE_INVALID; | 
 |  | 
 |   bookmark_app_url_ = specifics.bookmark_app_url(); | 
 |   bookmark_app_description_ = specifics.bookmark_app_description(); | 
 |   bookmark_app_scope_ = specifics.bookmark_app_scope(); | 
 |   bookmark_app_icon_color_ = specifics.bookmark_app_icon_color(); | 
 |   if (specifics.has_bookmark_app_theme_color()) | 
 |     bookmark_app_theme_color_ = specifics.bookmark_app_theme_color(); | 
 |  | 
 |   for (int i = 0; i < specifics.linked_app_icons_size(); ++i) { | 
 |     const sync_pb::LinkedAppIconInfo& linked_app_icon_info = | 
 |         specifics.linked_app_icons(i); | 
 |     if (linked_app_icon_info.has_url() && linked_app_icon_info.has_size()) { | 
 |       LinkedAppIconInfo linked_icon; | 
 |       linked_icon.url = GURL(linked_app_icon_info.url()); | 
 |       linked_icon.size = linked_app_icon_info.size(); | 
 |       linked_icons_.push_back(linked_icon); | 
 |     } | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | bool ExtensionSyncData::PopulateFromSyncData( | 
 |     const syncer::SyncData& sync_data) { | 
 |   const sync_pb::EntitySpecifics& entity_specifics = sync_data.GetSpecifics(); | 
 |  | 
 |   if (entity_specifics.has_app()) | 
 |     return PopulateFromAppSpecifics(entity_specifics.app()); | 
 |  | 
 |   if (entity_specifics.has_extension()) | 
 |     return PopulateFromExtensionSpecifics(entity_specifics.extension()); | 
 |  | 
 |   LOG(ERROR) << "Attempt to sync bad EntitySpecifics: no extension data."; | 
 |   RecordBadSyncData(NO_EXTENSION_SPECIFICS); | 
 |   return false; | 
 | } | 
 |  | 
 | }  // namespace extensions |