summaryrefslogtreecommitdiff
path: root/buildstream/_project.py
diff options
context:
space:
mode:
Diffstat (limited to 'buildstream/_project.py')
-rw-r--r--buildstream/_project.py105
1 files changed, 100 insertions, 5 deletions
diff --git a/buildstream/_project.py b/buildstream/_project.py
index 6df54f70f..6cbba497f 100644
--- a/buildstream/_project.py
+++ b/buildstream/_project.py
@@ -20,8 +20,10 @@
import gc
import os
+import sys
from collections import OrderedDict
from collections.abc import Mapping
+from pathlib import Path
from pluginbase import PluginBase
from . import utils
from . import _cachekey
@@ -227,6 +229,100 @@ class Project():
return self._cache_key
+ # get_path_from_node()
+ #
+ # Fetches the project path from a dictionary node and validates it
+ #
+ # Paths are asserted to never lead to a directory outside of the project
+ # directory. In addition, paths can not point to symbolic links, fifos,
+ # sockets and block/character devices.
+ #
+ # The `check_is_file` and `check_is_dir` parameters can be used to
+ # perform additional validations on the path. Note that an exception
+ # will always be raised if both parameters are set to ``True``.
+ #
+ # Args:
+ # node (dict): A dictionary loaded from YAML
+ # key (str): The key whose value contains a path to validate
+ # check_is_file (bool): If ``True`` an error will also be raised
+ # if path does not point to a regular file.
+ # Defaults to ``False``
+ # check_is_dir (bool): If ``True`` an error will be also raised
+ # if path does not point to a directory.
+ # Defaults to ``False``
+ # Returns:
+ # (str): The project path
+ #
+ # Raises:
+ # (LoadError): In case that the project path is not valid or does not
+ # exist
+ #
+ def get_path_from_node(self, node, key, *,
+ check_is_file=False, check_is_dir=False):
+ path_str = _yaml.node_get(node, str, key)
+ path = Path(path_str)
+ project_dir_path = Path(self.directory)
+
+ provenance = _yaml.node_get_provenance(node, key=key)
+
+ if (project_dir_path / path).is_symlink():
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
+ "{}: Specified path '{}' must not point to "
+ "symbolic links "
+ .format(provenance, path_str))
+
+ if path.parts and path.parts[0] == '..':
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID,
+ "{}: Specified path '{}' first component must "
+ "not be '..'"
+ .format(provenance, path_str))
+
+ try:
+ full_path = (project_dir_path / path)
+ if sys.version_info[0] == 3 and sys.version_info[1] < 6:
+ full_resolved_path = full_path.resolve()
+ else:
+ full_resolved_path = full_path.resolve(strict=True) # pylint: disable=unexpected-keyword-arg
+ except FileNotFoundError:
+ raise LoadError(LoadErrorReason.MISSING_FILE,
+ "{}: Specified path '{}' does not exist"
+ .format(provenance, path_str))
+
+ is_inside = project_dir_path.resolve() in full_resolved_path.parents or (
+ full_resolved_path == project_dir_path)
+
+ if not is_inside:
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID,
+ "{}: Specified path '{}' must not lead outside of the "
+ "project directory"
+ .format(provenance, path_str))
+
+ if path.is_absolute():
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID,
+ "{}: Absolute path: '{}' invalid.\n"
+ "Please specify a path relative to the project's root."
+ .format(provenance, path))
+
+ if full_resolved_path.is_socket() or (
+ full_resolved_path.is_fifo() or
+ full_resolved_path.is_block_device()):
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
+ "{}: Specified path '{}' points to an unsupported "
+ "file kind"
+ .format(provenance, path_str))
+
+ if check_is_file and not full_resolved_path.is_file():
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
+ "{}: Specified path '{}' is not a regular file"
+ .format(provenance, path_str))
+
+ if check_is_dir and not full_resolved_path.is_dir():
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
+ "{}: Specified path '{}' is not a directory"
+ .format(provenance, path_str))
+
+ return path_str
+
def _validate_node(self, node):
_yaml.node_validate(node, [
'format-version',
@@ -508,8 +604,8 @@ class Project():
self.element_path = os.path.join(
self.directory,
- _yaml.node_get_project_path(pre_config_node, 'element-path', self.directory,
- check_is_dir=True)
+ self.get_path_from_node(pre_config_node, 'element-path',
+ check_is_dir=True)
)
self.config.options = OptionPool(self.element_path)
@@ -857,9 +953,8 @@ class Project():
if group in origin_dict:
del origin_dict[group]
if origin_dict['origin'] == 'local':
- path = _yaml.node_get_project_path(origin, 'path',
- self.directory,
- check_is_dir=True)
+ path = self.get_path_from_node(origin, 'path',
+ check_is_dir=True)
# paths are passed in relative to the project, but must be absolute
origin_dict['path'] = os.path.join(self.directory, path)
destination.append(origin_dict)