1#! /usr/bin/python
2#
3# SPDX-License-Identifier: Apache-2.0
4# Zephyr's Twister library
5#
6# pylint: disable=unused-import
7#
8# Set of code that other projects can also import to do things on
9# Zephyr's sanity check testcases.
10
11import logging
12import yaml
13try:
14    # Use the C LibYAML parser if available, rather than the Python parser.
15    # It's much faster.
16    from yaml import CLoader as Loader
17    from yaml import CSafeLoader as SafeLoader
18    from yaml import CDumper as Dumper
19except ImportError:
20    from yaml import Loader, SafeLoader, Dumper
21
22log = logging.getLogger("scl")
23
24
25class EmptyYamlFileException(Exception):
26    pass
27
28
29#
30#
31def yaml_load(filename):
32    """
33    Safely load a YAML document
34
35    Follows recommendations from
36    https://security.openstack.org/guidelines/dg_avoid-dangerous-input-parsing-libraries.html.
37
38    :param str filename: filename to load
39    :raises yaml.scanner: On YAML scan issues
40    :raises: any other exception on file access errors
41    :return: dictionary representing the YAML document
42    """
43    try:
44        with open(filename, 'r', encoding='utf-8') as f:
45            return yaml.load(f, Loader=SafeLoader)
46    except yaml.scanner.ScannerError as e:	# For errors parsing schema.yaml
47        mark = e.problem_mark
48        cmark = e.context_mark
49        log.error("%s:%d:%d: error: %s (note %s context @%s:%d:%d %s)",
50                  mark.name, mark.line, mark.column, e.problem,
51                  e.note, cmark.name, cmark.line, cmark.column, e.context)
52        raise
53
54# If pykwalify is installed, then the validate function will work --
55# otherwise, it is a stub and we'd warn about it.
56try:
57    import pykwalify.core
58    # Don't print error messages yourself, let us do it
59    logging.getLogger("pykwalify.core").setLevel(50)
60
61    def _yaml_validate(data, schema):
62        if not schema:
63            return
64        c = pykwalify.core.Core(source_data=data, schema_data=schema)
65        c.validate(raise_exception=True)
66
67except ImportError as e:
68    log.warning("can't import pykwalify; won't validate YAML (%s)", e)
69    def _yaml_validate(data, schema):
70        pass
71
72def yaml_load_verify(filename, schema):
73    """
74    Safely load a testcase/sample yaml document and validate it
75    against the YAML schema, returning in case of success the YAML data.
76
77    :param str filename: name of the file to load and process
78    :param dict schema: loaded YAML schema (can load with :func:`yaml_load`)
79
80    # 'document.yaml' contains a single YAML document.
81    :raises yaml.scanner.ScannerError: on YAML parsing error
82    :raises pykwalify.errors.SchemaError: on Schema violation error
83    """
84    # 'document.yaml' contains a single YAML document.
85    y = yaml_load(filename)
86    if not y:
87        raise EmptyYamlFileException('No data in YAML file: %s' % filename)
88    _yaml_validate(y, schema)
89    return y
90