• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2015 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
5import os
6import uuid
7
8
9class AbspathInvalidError(Exception):
10  """Raised if an abspath cannot be sanitized based on an app's source paths."""
11
12
13class UserFriendlyStringInvalidError(Exception):
14  """Raised if a user friendly string cannot be parsed."""
15
16
17class ModuleToLoad(object):
18
19  def __init__(self, href=None, filename=None):
20    if bool(href) == bool(filename):
21      raise Exception('ModuleToLoad must specify exactly one of href and '
22                      'filename.')
23    self.href = href
24    self.filename = filename
25
26  def __repr__(self):
27    if self.href:
28      return 'ModuleToLoad(href="%s")' % self.href
29    return 'ModuleToLoad(filename="%s")' % self.filename
30
31  def AsDict(self):
32    if self.href:
33      return {'href': self.href}
34    return {'filename': self.filename}
35
36  @staticmethod
37  def FromDict(module_dict):
38    return ModuleToLoad(module_dict.get('href'), module_dict.get('filename'))
39
40
41class FunctionHandle(object):
42
43  def __init__(self, modules_to_load=None, function_name=None,
44               options=None, guid=uuid.uuid4()):
45    self.modules_to_load = modules_to_load
46    self.function_name = function_name
47    self.options = options
48    self._guid = guid
49
50  def __repr__(self):
51    return 'FunctionHandle(modules_to_load=[%s], function_name="%s")' % (
52      ', '.join([str(module) for module in self.modules_to_load]),
53      self.function_name)
54
55  @property
56  def guid(self):
57    return self._guid
58
59  @property
60  def has_hrefs(self):
61    return any(module.href for module in self.modules_to_load)
62
63  def AsDict(self):
64    handle_dict = {
65        'function_name': self.function_name
66    }
67
68    if self.modules_to_load is not None:
69      handle_dict['modules_to_load'] = [module.AsDict() for module in
70                                        self.modules_to_load]
71    if self.options is not None:
72      handle_dict['options'] = self.options
73
74    return handle_dict
75
76  def ConvertHrefsToAbsFilenames(self, app):
77    """Converts hrefs to absolute filenames in the context of |app|.
78
79    In an app-serving context, functions must only reside in files which the app
80    is serving, in order to prevent directory traversal attacks. In addition, we
81    rely on paths being absolute when actually executing functions.
82
83    Args:
84      app: A dev server instance requesting abspath conversion.
85
86    Returns:
87      A new FunctionHandle instance with no hrefs.
88
89    Raises:
90      AbspathInvalidError: If there is no source path with which a given abspath
91          shares a common prefix.
92    """
93    new_modules_to_load = []
94    for module in self.modules_to_load:
95      if module.href:
96        abspath = app.GetAbsFilenameForHref(module.href)
97      else:
98        assert os.path.abspath(module.filename) == module.filename
99        abspath = module.filename
100
101      if not abspath:
102        raise AbspathInvalidError('Filename %s invalid', abspath)
103
104      new_modules_to_load.append(ModuleToLoad(filename=abspath))
105
106    return FunctionHandle(modules_to_load=new_modules_to_load,
107                          function_name=self.function_name)
108
109  @staticmethod
110  def FromDict(handle_dict):
111    if handle_dict.get('modules_to_load') is not None:
112      modules_to_load = [ModuleToLoad.FromDict(module_dict) for module_dict in
113                         handle_dict['modules_to_load']]
114    else:
115      modules_to_load = []
116    options = handle_dict.get('options')
117    return FunctionHandle(modules_to_load=modules_to_load,
118                          function_name=handle_dict['function_name'],
119                          options=options)
120
121  def AsUserFriendlyString(self, app):
122    parts = [module.filename for module in
123             self.ConvertHrefsToAbsFilenames(app).modules_to_load]
124    parts.append(self.function_name)
125
126    return ':'.join(parts)
127
128  @staticmethod
129  def FromUserFriendlyString(user_str):
130    parts = user_str.split(':')
131    if len(parts) < 2:
132      raise UserFriendlyStringInvalidError(
133          'Tried to deserialize string with less than two parts: ' + user_str)
134
135    modules_to_load = [ModuleToLoad(filename=name) for name in parts[:-1]]
136
137    return FunctionHandle(modules_to_load=modules_to_load,
138                          function_name=parts[-1])
139
140