blob: 1efa5d647a9a71ea7c5b7eb96561b5d8c732f55d [file] [log] [blame]
# Copyright (c) 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import uuid
class AbspathInvalidError(Exception):
"""Raised if an abspath cannot be sanitized based on an app's source paths."""
class UserFriendlyStringInvalidError(Exception):
"""Raised if a user friendly string cannot be parsed."""
class ModuleToLoad(object):
def __init__(self, href=None, filename=None):
if bool(href) == bool(filename):
raise Exception('ModuleToLoad must specify exactly one of href and '
'filename.')
self.href = href
self.filename = filename
def __repr__(self):
if self.href:
return 'ModuleToLoad(href="%s")' % self.href
return 'ModuleToLoad(filename="%s")' % self.filename
def AsDict(self):
if self.href:
return {'href': self.href}
return {'filename': self.filename}
@staticmethod
def FromDict(module_dict):
return ModuleToLoad(module_dict.get('href'), module_dict.get('filename'))
class FunctionHandle(object):
def __init__(self, modules_to_load=None, function_name=None,
options=None, guid=uuid.uuid4()):
self.modules_to_load = modules_to_load
self.function_name = function_name
self.options = options
self._guid = guid
def __repr__(self):
return 'FunctionHandle(modules_to_load=[%s], function_name="%s")' % (
', '.join([str(module) for module in self.modules_to_load]),
self.function_name)
@property
def guid(self):
return self._guid
@property
def has_hrefs(self):
return any(module.href for module in self.modules_to_load)
def AsDict(self):
handle_dict = {
'function_name': self.function_name
}
if self.modules_to_load is not None:
handle_dict['modules_to_load'] = [module.AsDict() for module in
self.modules_to_load]
if self.options is not None:
handle_dict['options'] = self.options
return handle_dict
def ConvertHrefsToAbsFilenames(self, app):
"""Converts hrefs to absolute filenames in the context of |app|.
In an app-serving context, functions must only reside in files which the app
is serving, in order to prevent directory traversal attacks. In addition, we
rely on paths being absolute when actually executing functions.
Args:
app: A dev server instance requesting abspath conversion.
Returns:
A new FunctionHandle instance with no hrefs.
Raises:
AbspathInvalidError: If there is no source path with which a given abspath
shares a common prefix.
"""
new_modules_to_load = []
for module in self.modules_to_load:
if module.href:
abspath = app.GetAbsFilenameForHref(module.href)
else:
assert os.path.abspath(module.filename) == module.filename
abspath = module.filename
if not abspath:
raise AbspathInvalidError('Filename %s invalid', abspath)
new_modules_to_load.append(ModuleToLoad(filename=abspath))
return FunctionHandle(modules_to_load=new_modules_to_load,
function_name=self.function_name)
@staticmethod
def FromDict(handle_dict):
if handle_dict.get('modules_to_load') is not None:
modules_to_load = [ModuleToLoad.FromDict(module_dict) for module_dict in
handle_dict['modules_to_load']]
else:
modules_to_load = []
options = handle_dict.get('options')
return FunctionHandle(modules_to_load=modules_to_load,
function_name=handle_dict['function_name'],
options=options)
def AsUserFriendlyString(self, app):
parts = [module.filename for module in
self.ConvertHrefsToAbsFilenames(app).modules_to_load]
parts.append(self.function_name)
return ':'.join(parts)
@staticmethod
def FromUserFriendlyString(user_str):
parts = user_str.split(':')
if len(parts) < 2:
raise UserFriendlyStringInvalidError(
'Tried to deserialize string with less than two parts: ' + user_str)
modules_to_load = [ModuleToLoad(filename=name) for name in parts[:-1]]
return FunctionHandle(modules_to_load=modules_to_load,
function_name=parts[-1])