| Index: gclient.py | 
| diff --git a/gclient.py b/gclient.py | 
| index 3007487a5930d0f09aeac1260defe3be12bd4360..bbe6d582b8aaa12e412e8ce3a9eb51288739e0dc 100755 | 
| --- a/gclient.py | 
| +++ b/gclient.py | 
| @@ -342,6 +342,11 @@ class Dependency(gclient_utils.WorkItem, DependencySettings): | 
| # A cache of the files affected by the current operation, necessary for | 
| # hooks. | 
| self._file_list = [] | 
| +    # List of host names from which dependencies are allowed. | 
| +    # Default is an empty set, meaning unspecified in DEPS file, and hence all | 
| +    # hosts will be allowed. Non-empty set means whitelist of hosts. | 
| +    # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive. | 
| +    self._allowed_hosts = frozenset() | 
| # If it is not set to True, the dependency wasn't processed for its child | 
| # dependency, i.e. its DEPS wasn't read. | 
| self._deps_parsed = False | 
| @@ -687,6 +692,18 @@ class Dependency(gclient_utils.WorkItem, DependencySettings): | 
| rel_deps.add(os.path.normpath(os.path.join(self.name, d))) | 
| self.recursedeps = rel_deps | 
|  | 
| +    if 'allowed_hosts' in local_scope: | 
| +      try: | 
| +        self._allowed_hosts = frozenset(local_scope.get('allowed_hosts')) | 
| +      except TypeError:  # raised if non-iterable | 
| +        pass | 
| +      if not self._allowed_hosts: | 
| +        logging.warning("allowed_hosts is specified but empty %s", | 
| +                        self._allowed_hosts) | 
| +        raise gclient_utils.Error( | 
| +            'ParseDepsFile(%s): allowed_hosts must be absent ' | 
| +            'or a non-empty iterable' % self.name) | 
| + | 
| # Convert the deps into real Dependency. | 
| deps_to_add = [] | 
| for name, url in deps.iteritems(): | 
| @@ -756,6 +773,21 @@ class Dependency(gclient_utils.WorkItem, DependencySettings): | 
| print('Using parent\'s revision date %s since we are in a ' | 
| 'different repository.' % options.revision) | 
|  | 
| +  def findDepsFromNotAllowedHosts(self): | 
| +    """Returns a list of depenecies from not allowed hosts. | 
| + | 
| +    If allowed_hosts is not set, allows all hosts and returns empty list. | 
| +    """ | 
| +    if not self._allowed_hosts: | 
| +      return [] | 
| +    bad_deps = [] | 
| +    for dep in self._dependencies: | 
| +      if isinstance(dep.url, basestring): | 
| +        parsed_url = urlparse.urlparse(dep.url) | 
| +        if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts: | 
| +          bad_deps.append(dep) | 
| +    return bad_deps | 
| + | 
| # Arguments number differs from overridden method | 
| # pylint: disable=W0221 | 
| def run(self, revision_overrides, command, args, work_queue, options): | 
| @@ -1053,6 +1085,11 @@ class Dependency(gclient_utils.WorkItem, DependencySettings): | 
|  | 
| @property | 
| @gclient_utils.lockedmethod | 
| +  def allowed_hosts(self): | 
| +    return self._allowed_hosts | 
| + | 
| +  @property | 
| +  @gclient_utils.lockedmethod | 
| def file_list(self): | 
| return tuple(self._file_list) | 
|  | 
| @@ -1077,7 +1114,8 @@ class Dependency(gclient_utils.WorkItem, DependencySettings): | 
| out = [] | 
| for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps', | 
| 'custom_vars', 'deps_hooks', 'file_list', 'should_process', | 
| -              'processed', 'hooks_ran', 'deps_parsed', 'requirements'): | 
| +              'processed', 'hooks_ran', 'deps_parsed', 'requirements', | 
| +              'allowed_hosts'): | 
| # First try the native property if it exists. | 
| if hasattr(self, '_' + i): | 
| value = getattr(self, '_' + i, False) | 
| @@ -2086,6 +2124,27 @@ def CMDhookinfo(parser, args): | 
| return 0 | 
|  | 
|  | 
| +def CMDverify(parser, args): | 
| +  """Verifies the DEPS file deps are only from allowed_hosts.""" | 
| +  (options, args) = parser.parse_args(args) | 
| +  client = GClient.LoadCurrentConfig(options) | 
| +  if not client: | 
| +    raise gclient_utils.Error('client not configured; see \'gclient config\'') | 
| +  client.RunOnDeps(None, []) | 
| +  # Look at each first-level dependency of this gclient only. | 
| +  for dep in client.dependencies: | 
| +    bad_deps = dep.findDepsFromNotAllowedHosts() | 
| +    if not bad_deps: | 
| +      continue | 
| +    print "There are deps from not allowed hosts in file %s" % dep.deps_file | 
| +    for bad_dep in bad_deps: | 
| +      print "\t%s at %s" % (bad_dep.name, bad_dep.url) | 
| +    print "allowed_hosts:", ', '.join(dep.allowed_hosts) | 
| +    sys.stdout.flush() | 
| +    raise gclient_utils.Error( | 
| +        'dependencies from disallowed hosts; check your DEPS file.') | 
| +  return 0 | 
| + | 
| class OptionParser(optparse.OptionParser): | 
| gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient') | 
|  | 
|  |