extension_util.cc revision 21d179b334e59e9a3bfcaed4c4430bef1bc5759d
1// Copyright (c) 2010 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/sync/glue/extension_util.h"
6
7#include <sstream>
8
9#include "base/logging.h"
10#include "base/scoped_ptr.h"
11#include "base/stl_util-inl.h"
12#include "base/version.h"
13#include "chrome/browser/extensions/extension_prefs.h"
14#include "chrome/browser/extensions/extension_service.h"
15#include "chrome/browser/sync/protocol/extension_specifics.pb.h"
16#include "chrome/common/extensions/extension.h"
17#include "chrome/common/extensions/extension_constants.h"
18#include "googleurl/src/gurl.h"
19
20namespace browser_sync {
21
22bool IsExtensionValid(const Extension& extension) {
23  // TODO(akalin): Figure out if we need to allow some other types.
24  if (extension.location() != Extension::INTERNAL) {
25    // We have a non-standard location.
26    return false;
27  }
28
29  // Disallow extensions with non-gallery auto-update URLs for now.
30  //
31  // TODO(akalin): Relax this restriction once we've put in UI to
32  // approve synced extensions.
33  if (!extension.update_url().is_empty() &&
34      (extension.update_url() != Extension::GalleryUpdateUrl(false)) &&
35      (extension.update_url() != Extension::GalleryUpdateUrl(true))) {
36    return false;
37  }
38
39  // Disallow extensions with native code plugins.
40  //
41  // TODO(akalin): Relax this restriction once we've put in UI to
42  // approve synced extensions.
43  if (!extension.plugins().empty()) {
44    return false;
45  }
46
47  return true;
48}
49
50std::string ExtensionSpecificsToString(
51    const sync_pb::ExtensionSpecifics& specifics) {
52  std::stringstream ss;
53  ss << "{ ";
54  ss << "id: "                   << specifics.id()                << ", ";
55  ss << "version: "              << specifics.version()           << ", ";
56  ss << "update_url: "           << specifics.update_url()        << ", ";
57  ss << "enabled: "              << specifics.enabled()           << ", ";
58  ss << "incognito_enabled: "    << specifics.incognito_enabled() << ", ";
59  ss << "name: "                 << specifics.name();
60  ss << " }";
61  return ss.str();
62}
63
64bool IsExtensionSpecificsValid(
65    const sync_pb::ExtensionSpecifics& specifics) {
66  if (!Extension::IdIsValid(specifics.id())) {
67    return false;
68  }
69
70  scoped_ptr<Version> version(
71      Version::GetVersionFromString(specifics.version()));
72  if (!version.get()) {
73    return false;
74  }
75
76  // The update URL must be either empty or valid.
77  GURL update_url(specifics.update_url());
78  if (!update_url.is_empty() && !update_url.is_valid()) {
79    return false;
80  }
81
82  return true;
83}
84
85void DcheckIsExtensionSpecificsValid(
86    const sync_pb::ExtensionSpecifics& specifics) {
87  DCHECK(IsExtensionSpecificsValid(specifics))
88      << ExtensionSpecificsToString(specifics);
89}
90
91bool AreExtensionSpecificsEqual(const sync_pb::ExtensionSpecifics& a,
92                                const sync_pb::ExtensionSpecifics& b) {
93  // TODO(akalin): Figure out if we have to worry about version/URL
94  // strings that are not identical but map to the same object.
95  return ((a.id() == b.id()) &&
96          (a.version() == b.version()) &&
97          (a.update_url() == b.update_url()) &&
98          (a.enabled() == b.enabled()) &&
99          (a.incognito_enabled() == b.incognito_enabled()) &&
100          (a.name() == b.name()));
101}
102
103bool IsExtensionSpecificsUnset(
104    const sync_pb::ExtensionSpecifics& specifics) {
105  return AreExtensionSpecificsEqual(specifics,
106                                    sync_pb::ExtensionSpecifics());
107}
108
109void CopyUserProperties(
110    const sync_pb::ExtensionSpecifics& specifics,
111    sync_pb::ExtensionSpecifics* dest_specifics) {
112  DCHECK(dest_specifics);
113  dest_specifics->set_enabled(specifics.enabled());
114  dest_specifics->set_incognito_enabled(specifics.incognito_enabled());
115}
116
117void CopyNonUserProperties(
118    const sync_pb::ExtensionSpecifics& specifics,
119    sync_pb::ExtensionSpecifics* dest_specifics) {
120  DCHECK(dest_specifics);
121  sync_pb::ExtensionSpecifics old_dest_specifics(*dest_specifics);
122  *dest_specifics = specifics;
123  CopyUserProperties(old_dest_specifics, dest_specifics);
124}
125
126bool AreExtensionSpecificsUserPropertiesEqual(
127    const sync_pb::ExtensionSpecifics& a,
128    const sync_pb::ExtensionSpecifics& b) {
129  sync_pb::ExtensionSpecifics a_user_properties, b_user_properties;
130  CopyUserProperties(a, &a_user_properties);
131  CopyUserProperties(b, &b_user_properties);
132  return AreExtensionSpecificsEqual(a_user_properties, b_user_properties);
133}
134
135bool AreExtensionSpecificsNonUserPropertiesEqual(
136    const sync_pb::ExtensionSpecifics& a,
137    const sync_pb::ExtensionSpecifics& b) {
138  sync_pb::ExtensionSpecifics a_non_user_properties, b_non_user_properties;
139  CopyNonUserProperties(a, &a_non_user_properties);
140  CopyNonUserProperties(b, &b_non_user_properties);
141  return AreExtensionSpecificsEqual(
142      a_non_user_properties, b_non_user_properties);
143}
144
145void GetExtensionSpecifics(const Extension& extension,
146                           ExtensionPrefs* extension_prefs,
147                           sync_pb::ExtensionSpecifics* specifics) {
148  const std::string& id = extension.id();
149  bool enabled =
150      extension_prefs->GetExtensionState(id) == Extension::ENABLED;
151  bool incognito_enabled = extension_prefs->IsIncognitoEnabled(id);
152  GetExtensionSpecificsHelper(extension, enabled, incognito_enabled,
153                              specifics);
154}
155
156void GetExtensionSpecificsHelper(const Extension& extension,
157                                 bool enabled, bool incognito_enabled,
158                                 sync_pb::ExtensionSpecifics* specifics) {
159  DCHECK(IsExtensionValid(extension));
160  const std::string& id = extension.id();
161  specifics->set_id(id);
162  specifics->set_version(extension.VersionString());
163  specifics->set_update_url(extension.update_url().spec());
164  specifics->set_enabled(enabled);
165  specifics->set_incognito_enabled(incognito_enabled);
166  specifics->set_name(extension.name());
167  DcheckIsExtensionSpecificsValid(*specifics);
168}
169
170bool IsExtensionOutdated(const Extension& extension,
171                         const sync_pb::ExtensionSpecifics& specifics) {
172  DCHECK(IsExtensionValid(extension));
173  DcheckIsExtensionSpecificsValid(specifics);
174  scoped_ptr<Version> specifics_version(
175      Version::GetVersionFromString(specifics.version()));
176  if (!specifics_version.get()) {
177    // If version is invalid, assume we're up-to-date.
178    return false;
179  }
180  return extension.version()->CompareTo(*specifics_version) < 0;
181}
182
183void SetExtensionProperties(
184    const sync_pb::ExtensionSpecifics& specifics,
185    ExtensionService* extensions_service, const Extension* extension) {
186  DcheckIsExtensionSpecificsValid(specifics);
187  CHECK(extensions_service);
188  CHECK(extension);
189  DCHECK(IsExtensionValid(*extension));
190  const std::string& id = extension->id();
191  GURL update_url(specifics.update_url());
192  if (update_url != extension->update_url()) {
193    LOG(WARNING) << "specifics for extension " << id
194                 << "has a different update URL than the extension: "
195                 << update_url.spec() << " vs. " << extension->update_url();
196  }
197  ExtensionPrefs* extension_prefs = extensions_service->extension_prefs();
198  bool enabled = extension_prefs->GetExtensionState(id) == Extension::ENABLED;
199  if (enabled && !specifics.enabled()) {
200    extensions_service->DisableExtension(id);
201  } else if (!enabled && specifics.enabled()) {
202    extensions_service->EnableExtension(id);
203  }
204  bool incognito_enabled = extension_prefs->IsIncognitoEnabled(id);
205  if (incognito_enabled != specifics.incognito_enabled()) {
206    extensions_service->SetIsIncognitoEnabled(
207        extension, specifics.incognito_enabled());
208  }
209  if (specifics.name() != extension->name()) {
210    LOG(WARNING) << "specifics for extension " << id
211                 << "has a different name than the extension: "
212                 << specifics.name() << " vs. " << extension->name();
213  }
214}
215
216void MergeExtensionSpecifics(
217    const sync_pb::ExtensionSpecifics& specifics,
218    bool merge_user_properties,
219    sync_pb::ExtensionSpecifics* merged_specifics) {
220  DcheckIsExtensionSpecificsValid(*merged_specifics);
221  DcheckIsExtensionSpecificsValid(specifics);
222  DCHECK_EQ(specifics.id(), merged_specifics->id());
223  // TODO(akalin): Merge enabled permissions when we sync those.
224  scoped_ptr<Version> version(
225      Version::GetVersionFromString(specifics.version()));
226  CHECK(version.get());
227  scoped_ptr<Version> merged_version(
228      Version::GetVersionFromString(merged_specifics->version()));
229  CHECK(merged_version.get());
230  if (version->CompareTo(*merged_version) >= 0) {
231    // |specifics| has a more recent or the same version, so merge it
232    // in.
233    CopyNonUserProperties(specifics, merged_specifics);
234    if (merge_user_properties) {
235      CopyUserProperties(specifics, merged_specifics);
236    }
237  }
238}
239
240}  // namespace browser_sync
241