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