Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(161)

Side by Side Diff: chrome/common/extensions/extension_permission_set.cc

Issue 9317013: Add a centralized mechanism for whitelisting access to extension permissions. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: fix a couple tests Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/common/extensions/extension_permission_set.h" 5 #include "chrome/common/extensions/extension_permission_set.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <string> 8 #include <string>
9 9
10 #include "base/command_line.h"
10 #include "base/memory/singleton.h" 11 #include "base/memory/singleton.h"
11 #include "base/values.h" 12 #include "base/values.h"
12 #include "base/string_number_conversions.h" 13 #include "base/string_number_conversions.h"
13 #include "base/utf_string_conversions.h" 14 #include "base/utf_string_conversions.h"
15 #include "chrome/common/chrome_switches.h"
14 #include "chrome/common/extensions/extension.h" 16 #include "chrome/common/extensions/extension.h"
15 #include "chrome/common/extensions/extension_constants.h" 17 #include "chrome/common/extensions/extension_constants.h"
16 #include "chrome/common/extensions/extension_l10n_util.h" 18 #include "chrome/common/extensions/extension_l10n_util.h"
17 #include "chrome/common/extensions/url_pattern.h" 19 #include "chrome/common/extensions/url_pattern.h"
18 #include "chrome/common/extensions/url_pattern_set.h" 20 #include "chrome/common/extensions/url_pattern_set.h"
19 #include "content/public/common/url_constants.h" 21 #include "content/public/common/url_constants.h"
20 #include "grit/generated_resources.h" 22 #include "grit/generated_resources.h"
21 #include "net/base/registry_controlled_domain.h" 23 #include "net/base/registry_controlled_domain.h"
22 #include "ui/base/l10n/l10n_util.h" 24 #include "ui/base/l10n/l10n_util.h"
23 25
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
59 const char* kNonPermissionFunctionNames[] = { 61 const char* kNonPermissionFunctionNames[] = {
60 "management.getPermissionWarningsByManifest", 62 "management.getPermissionWarningsByManifest",
61 "tabs.create", 63 "tabs.create",
62 "tabs.onRemoved", 64 "tabs.onRemoved",
63 "tabs.remove", 65 "tabs.remove",
64 "tabs.update", 66 "tabs.update",
65 }; 67 };
66 const size_t kNumNonPermissionFunctionNames = 68 const size_t kNumNonPermissionFunctionNames =
67 arraysize(kNonPermissionFunctionNames); 69 arraysize(kNonPermissionFunctionNames);
68 70
71 // ExtensionAPIPermission::kTerminalPrivate whitelist.
72 const char* kTerminalPrivateWhitelist[] = {
73 "pnhechapfaindjhompbnflcldabbghjo" // HTerm app.
74 };
75
76 // ExtensionAPIPermission::kWebSocketProxyPrivate whitelist.
77 // ExtensionAPIPermission::kInputMethodPrivate whitelist.
78 const char* kChromeOSWhitelist[] = {
79 "haiffjcadagjlijoggckpgfnoeiflnem",
80 "gnedhmakppccajfpfiihfcdlnpgomkcf",
81 "fjcibdnjlbfnbfdjneajpipnlcppleek",
82 "okddffdblfhhnmhodogpojmfkjmhinfp",
83 "pnhechapfaindjhompbnflcldabbghjo" // HTerm app.
84 };
85
86 // ExtensionAPIPermission::kChromePrivate whitelist.
87 const char* kChromePrivateWhitelist[] = {
88 "haiffjcadagjlijoggckpgfnoeiflnem",
89 "gnedhmakppccajfpfiihfcdlnpgomkcf",
90 "fjcibdnjlbfnbfdjneajpipnlcppleek"
91 };
92
93 // ExtensionAPIPermission::kChromeAuthPrivate whitelist.
94 const char* kChromeAuthPrivateWhitelist[] = {
95 extension_misc::kCloudPrintAppId
96 };
97
98 // ExtensionAPIPermission::kWebstorePrivate whitelist.
99 const char* kWebstorePrivateWhitelist[] = {
100 extension_misc::kEnterpriseWebStoreAppId,
101 extension_misc::kWebStoreAppId
102 };
103
69 const char kOldUnlimitedStoragePermission[] = "unlimited_storage"; 104 const char kOldUnlimitedStoragePermission[] = "unlimited_storage";
70 const char kWindowsPermission[] = "windows"; 105 const char kWindowsPermission[] = "windows";
71 106
72 void AddPatternsAndRemovePaths(const URLPatternSet& set, URLPatternSet* out) { 107 void AddPatternsAndRemovePaths(const URLPatternSet& set, URLPatternSet* out) {
73 DCHECK(out); 108 DCHECK(out);
74 for (URLPatternSet::const_iterator i = set.begin(); i != set.end(); ++i) { 109 for (URLPatternSet::const_iterator i = set.begin(); i != set.end(); ++i) {
75 URLPattern p = *i; 110 URLPattern p = *i;
76 p.SetPath("/*"); 111 p.SetPath("/*");
77 out->AddPattern(p); 112 out->AddPattern(p);
78 } 113 }
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
191 kFlagCannotBeOptional, kTypeAll); 226 kFlagCannotBeOptional, kTypeAll);
192 info->RegisterPermission( 227 info->RegisterPermission(
193 kNotification, "notifications", 0, 228 kNotification, "notifications", 0,
194 ExtensionPermissionMessage::kNone, kFlagNone, kTypeAll); 229 ExtensionPermissionMessage::kNone, kFlagNone, kTypeAll);
195 info->RegisterPermission( 230 info->RegisterPermission(
196 kUnlimitedStorage, "unlimitedStorage", 0, 231 kUnlimitedStorage, "unlimitedStorage", 0,
197 ExtensionPermissionMessage::kNone, kFlagCannotBeOptional, kTypeAll); 232 ExtensionPermissionMessage::kNone, kFlagCannotBeOptional, kTypeAll);
198 233
199 // Register hosted app permissions that are also private. 234 // Register hosted app permissions that are also private.
200 info->RegisterPermission( 235 info->RegisterPermission(
201 kChromePrivate, "chromePrivate", 0, 236 kChromePrivate, "chromePrivate", 0, ExtensionPermissionMessage::kNone,
202 ExtensionPermissionMessage::kNone, kFlagCannotBeOptional, 237 kFlagCannotBeOptional | kFlagEnforceWhitelist,
203 kTypeAll - kTypePlatformApp); 238 kTypeAll - kTypePlatformApp);
204 info->RegisterPermission( 239 info->RegisterPermission(
205 kChromeAuthPrivate, "chromeAuthPrivate", 0, 240 kChromeAuthPrivate, "chromeAuthPrivate", 0,
206 ExtensionPermissionMessage::kNone, 241 ExtensionPermissionMessage::kNone,
207 kFlagComponentOnly | kFlagCannotBeOptional, kTypeAll - kTypePlatformApp); 242 kFlagComponentOnly | kFlagCannotBeOptional | kFlagEnforceWhitelist,
243 kTypeAll - kTypePlatformApp);
208 info->RegisterPermission( 244 info->RegisterPermission(
209 kWebstorePrivate, "webstorePrivate", 0, 245 kWebstorePrivate, "webstorePrivate", 0,
210 ExtensionPermissionMessage::kNone, 246 ExtensionPermissionMessage::kNone,
211 kFlagComponentOnly | kFlagCannotBeOptional, kTypeAll - kTypePlatformApp); 247 kFlagComponentOnly | kFlagCannotBeOptional | kFlagEnforceWhitelist,
248 kTypeAll - kTypePlatformApp);
212 249
213 // Register hosted and packaged app permissions. 250 // Register hosted and packaged app permissions.
214 info->RegisterPermission( 251 info->RegisterPermission(
215 kAppNotifications, "appNotifications", 0, 252 kAppNotifications, "appNotifications", 0,
216 ExtensionPermissionMessage::kNone, kFlagNone, 253 ExtensionPermissionMessage::kNone, kFlagNone,
217 kTypeHostedApp | kTypePackagedApp); 254 kTypeHostedApp | kTypePackagedApp);
218 255
219 // Register extension permissions. 256 // Register extension permissions.
220 info->RegisterPermission( 257 info->RegisterPermission(
221 kBookmark, "bookmarks", IDS_EXTENSION_PROMPT_WARNING_BOOKMARKS, 258 kBookmark, "bookmarks", IDS_EXTENSION_PROMPT_WARNING_BOOKMARKS,
(...skipping 17 matching lines...) Expand all
239 ExtensionPermissionMessage::kBrowsingHistory, 276 ExtensionPermissionMessage::kBrowsingHistory,
240 kFlagNone, kTypeDefault); 277 kFlagNone, kTypeDefault);
241 info->RegisterPermission( 278 info->RegisterPermission(
242 kIdle, "idle", 0, ExtensionPermissionMessage::kNone, 279 kIdle, "idle", 0, ExtensionPermissionMessage::kNone,
243 kFlagNone, kTypeDefault); 280 kFlagNone, kTypeDefault);
244 info->RegisterPermission( 281 info->RegisterPermission(
245 kInput, "input", 0, ExtensionPermissionMessage::kNone, 282 kInput, "input", 0, ExtensionPermissionMessage::kNone,
246 kFlagImpliesFullURLAccess, kTypeDefault); 283 kFlagImpliesFullURLAccess, kTypeDefault);
247 info->RegisterPermission( 284 info->RegisterPermission(
248 kInputMethodPrivate, "inputMethodPrivate", 0, 285 kInputMethodPrivate, "inputMethodPrivate", 0,
249 ExtensionPermissionMessage::kNone, kFlagCannotBeOptional, kTypeDefault); 286 ExtensionPermissionMessage::kNone,
287 kFlagCannotBeOptional | kFlagEnforceWhitelist, kTypeDefault);
250 info->RegisterPermission( 288 info->RegisterPermission(
251 kManagement, "management", IDS_EXTENSION_PROMPT_WARNING_MANAGEMENT, 289 kManagement, "management", IDS_EXTENSION_PROMPT_WARNING_MANAGEMENT,
252 ExtensionPermissionMessage::kManagement, kFlagNone, kTypeDefault); 290 ExtensionPermissionMessage::kManagement, kFlagNone, kTypeDefault);
253 info->RegisterPermission( 291 info->RegisterPermission(
254 kPageCapture, "pageCapture", 292 kPageCapture, "pageCapture",
255 IDS_EXTENSION_PROMPT_WARNING_ALL_PAGES_CONTENT, 293 IDS_EXTENSION_PROMPT_WARNING_ALL_PAGES_CONTENT,
256 ExtensionPermissionMessage::kAllPageContent, kFlagNone, kTypeDefault); 294 ExtensionPermissionMessage::kAllPageContent, kFlagNone, kTypeDefault);
257 info->RegisterPermission( 295 info->RegisterPermission(
258 kPrivacy, "privacy", IDS_EXTENSION_PROMPT_WARNING_PRIVACY, 296 kPrivacy, "privacy", IDS_EXTENSION_PROMPT_WARNING_PRIVACY,
259 ExtensionPermissionMessage::kPrivacy, kFlagNone, kTypeDefault); 297 ExtensionPermissionMessage::kPrivacy, kFlagNone, kTypeDefault);
260 info->RegisterPermission( 298 info->RegisterPermission(
261 kTab, "tabs", IDS_EXTENSION_PROMPT_WARNING_TABS, 299 kTab, "tabs", IDS_EXTENSION_PROMPT_WARNING_TABS,
262 ExtensionPermissionMessage::kTabs, kFlagNone, 300 ExtensionPermissionMessage::kTabs, kFlagNone,
263 kTypeDefault - kTypePlatformApp); 301 kTypeDefault - kTypePlatformApp);
264 info->RegisterPermission( 302 info->RegisterPermission(
265 kTerminalPrivate, "terminalPrivate", 0, 303 kTerminalPrivate, "terminalPrivate", 0, ExtensionPermissionMessage::kNone,
266 ExtensionPermissionMessage::kNone, kFlagCannotBeOptional, kTypeDefault); 304 kFlagCannotBeOptional | kFlagEnforceWhitelist, kTypeDefault);
267 info->RegisterPermission( 305 info->RegisterPermission(
268 kTts, "tts", 0, 306 kTts, "tts", 0,
269 ExtensionPermissionMessage::kNone, kFlagCannotBeOptional, kTypeDefault); 307 ExtensionPermissionMessage::kNone, kFlagCannotBeOptional, kTypeDefault);
270 info->RegisterPermission( 308 info->RegisterPermission(
271 kTtsEngine, "ttsEngine", IDS_EXTENSION_PROMPT_WARNING_TTS_ENGINE, 309 kTtsEngine, "ttsEngine", IDS_EXTENSION_PROMPT_WARNING_TTS_ENGINE,
272 ExtensionPermissionMessage::kTtsEngine, 310 ExtensionPermissionMessage::kTtsEngine,
273 kFlagCannotBeOptional, kTypeDefault); 311 kFlagCannotBeOptional, kTypeDefault);
274 info->RegisterPermission( 312 info->RegisterPermission(
275 kWebNavigation, "webNavigation", 313 kWebNavigation, "webNavigation",
276 IDS_EXTENSION_PROMPT_WARNING_TABS, ExtensionPermissionMessage::kTabs, 314 IDS_EXTENSION_PROMPT_WARNING_TABS, ExtensionPermissionMessage::kTabs,
277 kFlagNone, kTypeDefault - kTypePlatformApp); 315 kFlagNone, kTypeDefault - kTypePlatformApp);
278 info->RegisterPermission( 316 info->RegisterPermission(
279 kWebRequest, "webRequest", 0, ExtensionPermissionMessage::kNone, 317 kWebRequest, "webRequest", 0, ExtensionPermissionMessage::kNone,
280 kFlagNone, kTypeDefault - kTypePlatformApp); 318 kFlagNone, kTypeDefault - kTypePlatformApp);
281 info->RegisterPermission( 319 info->RegisterPermission(
282 kWebRequestBlocking, "webRequestBlocking", 0, 320 kWebRequestBlocking, "webRequestBlocking", 0,
283 ExtensionPermissionMessage::kNone, kFlagNone, 321 ExtensionPermissionMessage::kNone, kFlagNone,
284 kTypeDefault - kTypePlatformApp); 322 kTypeDefault - kTypePlatformApp);
285 info->RegisterPermission( 323 info->RegisterPermission(
286 kWebSocketProxyPrivate, "webSocketProxyPrivate", 0, 324 kWebSocketProxyPrivate, "webSocketProxyPrivate", 0,
287 ExtensionPermissionMessage::kNone, kFlagCannotBeOptional, 325 ExtensionPermissionMessage::kNone,
326 kFlagCannotBeOptional | kFlagEnforceWhitelist,
288 kTypeDefault - kTypePlatformApp); 327 kTypeDefault - kTypePlatformApp);
289 328
290 // Register private permissions. 329 // Register private permissions.
291 info->RegisterPermission( 330 info->RegisterPermission(
292 kChromeosInfoPrivate, "chromeosInfoPrivate", 0, 331 kChromeosInfoPrivate, "chromeosInfoPrivate", 0,
293 ExtensionPermissionMessage::kNone, 332 ExtensionPermissionMessage::kNone,
294 kFlagComponentOnly | kFlagCannotBeOptional, kTypeDefault); 333 kFlagComponentOnly | kFlagCannotBeOptional, kTypeDefault);
295 info->RegisterPermission( 334 info->RegisterPermission(
296 kFileBrowserPrivate, "fileBrowserPrivate", 0, 335 kFileBrowserPrivate, "fileBrowserPrivate", 0,
297 ExtensionPermissionMessage::kNone, 336 ExtensionPermissionMessage::kNone,
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
331 // Platform-app permissions. 370 // Platform-app permissions.
332 info->RegisterPermission( 371 info->RegisterPermission(
333 kSocket, "socket", 0, ExtensionPermissionMessage::kNone, 372 kSocket, "socket", 0, ExtensionPermissionMessage::kNone,
334 kFlagCannotBeOptional, kTypePlatformApp); 373 kFlagCannotBeOptional, kTypePlatformApp);
335 374
336 // Register aliases. 375 // Register aliases.
337 info->RegisterAlias("unlimitedStorage", kOldUnlimitedStoragePermission); 376 info->RegisterAlias("unlimitedStorage", kOldUnlimitedStoragePermission);
338 info->RegisterAlias("tabs", kWindowsPermission); 377 info->RegisterAlias("tabs", kWindowsPermission);
339 } 378 }
340 379
380 // static
381 void ExtensionAPIPermission::RegisterWhitelists(
382 ExtensionPermissionsInfo* info) {
383 info->RegisterWhitelist(kTerminalPrivate,
384 kTerminalPrivateWhitelist,
385 arraysize(kTerminalPrivateWhitelist));
386 info->RegisterWhitelist(kWebSocketProxyPrivate,
387 kChromeOSWhitelist,
388 arraysize(kChromeOSWhitelist));
389 info->RegisterWhitelist(kChromePrivate,
390 kChromePrivateWhitelist,
391 arraysize(kChromePrivateWhitelist));
392 info->RegisterWhitelist(kInputMethodPrivate,
393 kChromeOSWhitelist,
394 arraysize(kChromeOSWhitelist));
395 info->RegisterWhitelist(kChromeAuthPrivate,
396 kChromeAuthPrivateWhitelist,
397 arraysize(kChromeAuthPrivateWhitelist));
398 info->RegisterWhitelist(kWebstorePrivate,
399 kWebstorePrivateWhitelist,
400 arraysize(kWebstorePrivateWhitelist));
401 }
402
403 bool ExtensionAPIPermission::IsWhitelisted(
404 const std::string& extension_id) const {
405 DCHECK(should_enforce_whitelist());
406 return whitelist_.find(extension_id) != whitelist_.end();
407 }
408
409 void ExtensionAPIPermission::AddToWhitelist(const std::string& extension_id) {
410 DCHECK(should_enforce_whitelist());
411 whitelist_.insert(extension_id);
412 }
413
341 // 414 //
342 // ExtensionPermissionsInfo 415 // ExtensionPermissionsInfo
343 // 416 //
344 417
345 // static 418 // static
346 ExtensionPermissionsInfo* ExtensionPermissionsInfo::GetInstance() { 419 ExtensionPermissionsInfo* ExtensionPermissionsInfo::GetInstance() {
347 return Singleton<ExtensionPermissionsInfo>::get(); 420 return Singleton<ExtensionPermissionsInfo>::get();
348 } 421 }
349 422
350 ExtensionAPIPermission* ExtensionPermissionsInfo::GetByID( 423 ExtensionAPIPermission* ExtensionPermissionsInfo::GetByID(
(...skipping 28 matching lines...) Expand all
379 452
380 ExtensionPermissionsInfo::~ExtensionPermissionsInfo() { 453 ExtensionPermissionsInfo::~ExtensionPermissionsInfo() {
381 for (IDMap::iterator i = id_map_.begin(); i != id_map_.end(); ++i) 454 for (IDMap::iterator i = id_map_.begin(); i != id_map_.end(); ++i)
382 delete i->second; 455 delete i->second;
383 } 456 }
384 457
385 ExtensionPermissionsInfo::ExtensionPermissionsInfo() 458 ExtensionPermissionsInfo::ExtensionPermissionsInfo()
386 : hosted_app_permission_count_(0), 459 : hosted_app_permission_count_(0),
387 permission_count_(0) { 460 permission_count_(0) {
388 ExtensionAPIPermission::RegisterAllPermissions(this); 461 ExtensionAPIPermission::RegisterAllPermissions(this);
462 ExtensionAPIPermission::RegisterWhitelists(this);
389 } 463 }
390 464
391 void ExtensionPermissionsInfo::RegisterAlias( 465 void ExtensionPermissionsInfo::RegisterAlias(
392 const char* name, const char* alias) { 466 const char* name, const char* alias) {
393 DCHECK(name_map_.find(name) != name_map_.end()); 467 DCHECK(name_map_.find(name) != name_map_.end());
394 DCHECK(name_map_.find(alias) == name_map_.end()); 468 DCHECK(name_map_.find(alias) == name_map_.end());
395 name_map_[alias] = name_map_[name]; 469 name_map_[alias] = name_map_[name];
396 } 470 }
397 471
398 void ExtensionPermissionsInfo::RegisterPermission( 472 void ExtensionPermissionsInfo::RegisterPermission(
399 ExtensionAPIPermission::ID id, 473 ExtensionAPIPermission::ID id,
400 const char* name, 474 const char* name,
401 int l10n_message_id, 475 int l10n_message_id,
402 ExtensionPermissionMessage::ID message_id, 476 ExtensionPermissionMessage::ID message_id,
403 int flags, 477 int flags,
404 int type_restrictions) { 478 int type_restrictions) {
405 DCHECK(id_map_.find(id) == id_map_.end()); 479 DCHECK(id_map_.find(id) == id_map_.end());
406 DCHECK(name_map_.find(name) == name_map_.end()); 480 DCHECK(name_map_.find(name) == name_map_.end());
407 481
408 ExtensionAPIPermission* permission = new ExtensionAPIPermission( 482 ExtensionAPIPermission* permission = new ExtensionAPIPermission(
409 id, name, l10n_message_id, message_id, flags, type_restrictions); 483 id, name, l10n_message_id, message_id, flags, type_restrictions);
410 484
411 id_map_[id] = permission; 485 id_map_[id] = permission;
412 name_map_[name] = permission; 486 name_map_[name] = permission;
413 487
414 permission_count_++; 488 permission_count_++;
415 } 489 }
416 490
491 void ExtensionPermissionsInfo::RegisterWhitelist(
492 ExtensionAPIPermission::ID id,
493 const char* whitelist[],
494 int whitelist_length) {
495 ExtensionAPIPermission* permission = GetByID(id);
496 DCHECK(permission->should_enforce_whitelist());
497
498 for (int x = 0; x < whitelist_length; ++x)
499 permission->AddToWhitelist(whitelist[x]);
500
501 // Tests can add an extension ID to the whitelists via the command line.
502 CommandLine* command_line = CommandLine::ForCurrentProcess();
503 if (command_line->HasSwitch(switches::kWhitelistedExtensionID))
504 permission->AddToWhitelist(command_line->GetSwitchValueASCII(
505 switches::kWhitelistedExtensionID));
506 }
507
417 // 508 //
418 // ExtensionPermissionSet 509 // ExtensionPermissionSet
419 // 510 //
420 511
421 ExtensionPermissionSet::ExtensionPermissionSet() { 512 ExtensionPermissionSet::ExtensionPermissionSet() {
422 } 513 }
423 514
424 ExtensionPermissionSet::ExtensionPermissionSet( 515 ExtensionPermissionSet::ExtensionPermissionSet(
425 const Extension* extension, 516 const Extension* extension,
426 const ExtensionAPIPermissionSet& apis, 517 const ExtensionAPIPermissionSet& apis,
(...skipping 440 matching lines...) Expand 10 before | Expand all | Expand 10 after
867 std::set<std::string> new_hosts_set(GetDistinctHosts(new_list, false, false)); 958 std::set<std::string> new_hosts_set(GetDistinctHosts(new_list, false, false));
868 std::set<std::string> old_hosts_set(GetDistinctHosts(old_list, false, false)); 959 std::set<std::string> old_hosts_set(GetDistinctHosts(old_list, false, false));
869 std::set<std::string> new_hosts_only; 960 std::set<std::string> new_hosts_only;
870 961
871 std::set_difference(new_hosts_set.begin(), new_hosts_set.end(), 962 std::set_difference(new_hosts_set.begin(), new_hosts_set.end(),
872 old_hosts_set.begin(), old_hosts_set.end(), 963 old_hosts_set.begin(), old_hosts_set.end(),
873 std::inserter(new_hosts_only, new_hosts_only.begin())); 964 std::inserter(new_hosts_only, new_hosts_only.begin()));
874 965
875 return !new_hosts_only.empty(); 966 return !new_hosts_only.empty();
876 } 967 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698