summaryrefslogtreecommitdiffstats
path: root/chrome/browser/sync/glue/extension_util.cc
blob: dfd7b048c2545be1cd1d51a2c6e5d12dddc65a96 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
// Copyright (c) 2010 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/sync/glue/extension_util.h"

#include <sstream>

#include "base/logging.h"
#include "base/scoped_ptr.h"
#include "base/stl_util-inl.h"
#include "base/version.h"
#include "chrome/browser/extensions/extensions_service.h"
#include "chrome/browser/sync/protocol/extension_specifics.pb.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_constants.h"
#include "googleurl/src/gurl.h"

namespace browser_sync {

ExtensionType GetExtensionType(const Extension& extension) {
  if (extension.is_theme()) {
    return THEME;
  }

  // TODO(akalin): Add Extensions::is_app().
  if (!extension.GetFullLaunchURL().is_empty()) {
    return APP;
  }

  if (extension.converted_from_user_script()) {
    if (extension.update_url().is_empty()) {
      return LOCAL_USER_SCRIPT;
    }
    return UPDATEABLE_USER_SCRIPT;
  }

  // Otherwise, we just have a regular extension.
  return EXTENSION;
}

bool IsExtensionValid(const Extension& extension) {
  // TODO(akalin): Figure out if we need to allow some other types.
  if (extension.location() != Extension::INTERNAL) {
    // We have a non-standard location.
    return false;
  }

  // Disallow extensions with non-gallery auto-update URLs for now.
  //
  // TODO(akalin): Relax this restriction once we've put in UI to
  // approve synced extensions.
  if (!extension.update_url().is_empty() &&
      (extension.update_url() !=
       GURL(extension_urls::kGalleryUpdateHttpUrl)) &&
      (extension.update_url() !=
       GURL(extension_urls::kGalleryUpdateHttpsUrl))) {
    return false;
  }

  // Disallow extensions with native code plugins.
  //
  // TODO(akalin): Relax this restriction once we've put in UI to
  // approve synced extensions.
  if (!extension.plugins().empty()) {
    return false;
  }

  return true;
}

bool IsExtensionValidAndSyncable(const Extension& extension,
                                 const ExtensionTypeSet& allowed_types) {
  return
      IsExtensionValid(extension) &&
      ContainsKey(allowed_types, GetExtensionType(extension));
}

std::string ExtensionSpecificsToString(
    const sync_pb::ExtensionSpecifics& specifics) {
  std::stringstream ss;
  ss << "{ ";
  ss << "id: "                   << specifics.id()                << ", ";
  ss << "version: "              << specifics.version()           << ", ";
  ss << "update_url: "           << specifics.update_url()        << ", ";
  ss << "enabled: "              << specifics.enabled()           << ", ";
  ss << "incognito_enabled: "    << specifics.incognito_enabled() << ", ";
  ss << "name: "                 << specifics.name();
  ss << " }";
  return ss.str();
}

bool IsExtensionSpecificsValid(
    const sync_pb::ExtensionSpecifics& specifics) {
  if (!Extension::IdIsValid(specifics.id())) {
    return false;
  }

  scoped_ptr<Version> version(
      Version::GetVersionFromString(specifics.version()));
  if (!version.get()) {
    return false;
  }

  // The update URL must be either empty or valid.
  GURL update_url(specifics.update_url());
  if (!update_url.is_empty() && !update_url.is_valid()) {
    return false;
  }

  return true;
}

void DcheckIsExtensionSpecificsValid(
    const sync_pb::ExtensionSpecifics& specifics) {
  DCHECK(IsExtensionSpecificsValid(specifics))
      << ExtensionSpecificsToString(specifics);
}

bool AreExtensionSpecificsEqual(const sync_pb::ExtensionSpecifics& a,
                                const sync_pb::ExtensionSpecifics& b) {
  // TODO(akalin): Figure out if we have to worry about version/URL
  // strings that are not identical but map to the same object.
  return ((a.id() == b.id()) &&
          (a.version() == b.version()) &&
          (a.update_url() == b.update_url()) &&
          (a.enabled() == b.enabled()) &&
          (a.incognito_enabled() == b.incognito_enabled()) &&
          (a.name() == b.name()));
}

bool IsExtensionSpecificsUnset(
    const sync_pb::ExtensionSpecifics& specifics) {
  return AreExtensionSpecificsEqual(specifics,
                                    sync_pb::ExtensionSpecifics());
}

void CopyUserProperties(
    const sync_pb::ExtensionSpecifics& specifics,
    sync_pb::ExtensionSpecifics* dest_specifics) {
  DCHECK(dest_specifics);
  dest_specifics->set_enabled(specifics.enabled());
  dest_specifics->set_incognito_enabled(specifics.incognito_enabled());
}

void CopyNonUserProperties(
    const sync_pb::ExtensionSpecifics& specifics,
    sync_pb::ExtensionSpecifics* dest_specifics) {
  DCHECK(dest_specifics);
  sync_pb::ExtensionSpecifics old_dest_specifics(*dest_specifics);
  *dest_specifics = specifics;
  CopyUserProperties(old_dest_specifics, dest_specifics);
}

bool AreExtensionSpecificsUserPropertiesEqual(
    const sync_pb::ExtensionSpecifics& a,
    const sync_pb::ExtensionSpecifics& b) {
  sync_pb::ExtensionSpecifics a_user_properties, b_user_properties;
  CopyUserProperties(a, &a_user_properties);
  CopyUserProperties(b, &b_user_properties);
  return AreExtensionSpecificsEqual(a_user_properties, b_user_properties);
}

bool AreExtensionSpecificsNonUserPropertiesEqual(
    const sync_pb::ExtensionSpecifics& a,
    const sync_pb::ExtensionSpecifics& b) {
  sync_pb::ExtensionSpecifics a_non_user_properties, b_non_user_properties;
  CopyNonUserProperties(a, &a_non_user_properties);
  CopyNonUserProperties(b, &b_non_user_properties);
  return AreExtensionSpecificsEqual(
      a_non_user_properties, b_non_user_properties);
}

void GetExtensionSpecifics(const Extension& extension,
                           ExtensionsService* extensions_service,
                           sync_pb::ExtensionSpecifics* specifics) {
  const std::string& id = extension.id();
  bool enabled =
      extensions_service->GetExtensionById(id, false) != NULL;
  bool incognito_enabled =
      extensions_service->IsIncognitoEnabled(&extension);
  GetExtensionSpecificsHelper(extension, enabled, incognito_enabled,
                              specifics);
}

void GetExtensionSpecificsHelper(const Extension& extension,
                                 bool enabled, bool incognito_enabled,
                                 sync_pb::ExtensionSpecifics* specifics) {
  DCHECK(IsExtensionValid(extension));
  const std::string& id = extension.id();
  specifics->set_id(id);
  specifics->set_version(extension.VersionString());
  specifics->set_update_url(extension.update_url().spec());
  specifics->set_enabled(enabled);
  specifics->set_incognito_enabled(incognito_enabled);
  specifics->set_name(extension.name());
  DcheckIsExtensionSpecificsValid(*specifics);
}

bool IsExtensionOutdated(const Extension& extension,
                         const sync_pb::ExtensionSpecifics& specifics) {
  DCHECK(IsExtensionValid(extension));
  DcheckIsExtensionSpecificsValid(specifics);
  scoped_ptr<Version> specifics_version(
      Version::GetVersionFromString(specifics.version()));
  if (!specifics_version.get()) {
    // If version is invalid, assume we're up-to-date.
    return false;
  }
  return extension.version()->CompareTo(*specifics_version) < 0;
}

void SetExtensionProperties(
    const sync_pb::ExtensionSpecifics& specifics,
    ExtensionsService* extensions_service, Extension* extension) {
  DcheckIsExtensionSpecificsValid(specifics);
  CHECK(extensions_service);
  CHECK(extension);
  DCHECK(IsExtensionValid(*extension));
  const std::string& id = extension->id();
  GURL update_url(specifics.update_url());
  if (update_url != extension->update_url()) {
    LOG(WARNING) << "specifics for extension " << id
                 << "has a different update URL than the extension: "
                 << update_url.spec() << " vs. " << extension->update_url();
  }
  bool enabled = extensions_service->GetExtensionById(id, false) != NULL;
  if (enabled && !specifics.enabled()) {
    extensions_service->DisableExtension(id);
  } else if (!enabled && specifics.enabled()) {
    extensions_service->EnableExtension(id);
  }
  bool incognito_enabled =
      extensions_service->IsIncognitoEnabled(extension);
  if (incognito_enabled != specifics.incognito_enabled()) {
    extensions_service->SetIsIncognitoEnabled(
        extension, specifics.incognito_enabled());
  }
  if (specifics.name() != extension->name()) {
    LOG(WARNING) << "specifics for extension " << id
                 << "has a different name than the extension: "
                 << specifics.name() << " vs. " << extension->name();
  }
}

void MergeExtensionSpecifics(
    const sync_pb::ExtensionSpecifics& specifics,
    bool merge_user_properties,
    sync_pb::ExtensionSpecifics* merged_specifics) {
  DcheckIsExtensionSpecificsValid(*merged_specifics);
  DcheckIsExtensionSpecificsValid(specifics);
  DCHECK_EQ(specifics.id(), merged_specifics->id());
  // TODO(akalin): Merge enabled permissions when we sync those.
  scoped_ptr<Version> version(
      Version::GetVersionFromString(specifics.version()));
  CHECK(version.get());
  scoped_ptr<Version> merged_version(
      Version::GetVersionFromString(merged_specifics->version()));
  CHECK(merged_version.get());
  if (version->CompareTo(*merged_version) >= 0) {
    // |specifics| has a more recent or the same version, so merge it
    // in.
    CopyNonUserProperties(specifics, merged_specifics);
    if (merge_user_properties) {
      CopyUserProperties(specifics, merged_specifics);
    }
  }
}

}  // namespace browser_sync