15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import json
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import logging
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import urlparse
8c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)from sdk_update_common import Error
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)SOURCE_WHITELIST = [
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  'http://localhost/',  # For testing.
125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  'https://storage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk',
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)]
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def IsSourceValid(url):
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # E1101: Instance of 'ParseResult' has no 'scheme' member
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # pylint: disable=E1101
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  given = urlparse.urlparse(url)
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for allowed_url in SOURCE_WHITELIST:
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    allowed = urlparse.urlparse(allowed_url)
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (given.scheme == allowed.scheme and
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        given.hostname == allowed.hostname and
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        given.path.startswith(allowed.path)):
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return True
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return False
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Config(dict):
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, data=None):
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    dict.__init__(self)
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if data:
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.update(data)
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.sources = []
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
37c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  def LoadJson(self, json_data):
38c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    try:
39c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      self.update(json.loads(json_data))
40c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    except Exception as e:
41c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      raise Error('Error reading json config:\n%s' % str(e))
42c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ToJson(self):
44c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    try:
45c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      return json.dumps(self, sort_keys=False, indent=2)
46c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    except Exception as e:
47c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      raise Error('Json encoding error writing config:\n%s' % e)
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __getattr__(self, name):
50c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if name in self:
51c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      return self[name]
52c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    else:
53c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      raise AttributeError('Config does not contain: %s' % name)
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __setattr__(self, name, value):
56c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    self[name] = value
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddSource(self, source):
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not IsSourceValid(source):
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.warn('Only whitelisted sources are allowed. Ignoring \"%s\".' % (
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          source,))
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if source in self.sources:
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.info('Source \"%s\" already in Config.' % (source,))
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.sources.append(source)
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def RemoveSource(self, source):
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if source not in self.sources:
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.warn('Source \"%s\" not in Config.' % (source,))
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.sources.remove(source)
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def RemoveAllSources(self):
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not self.sources:
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.info('No sources to remove.')
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.sources = []
80