import os
import re
import sys
from typing import List
_THIS_DIR = os.path.abspath(os.path.dirname(__file__))
_ROOT_DIR = os.path.abspath(os.path.join(_THIS_DIR, ".."))
sys.path.insert(0, _ROOT_DIR)
import metadata.fields.known as known_fields
import metadata.dependency_metadata as dm
import metadata.fields.custom.mitigated
DEPENDENCY_DIVIDER = re.compile(r"^-{20} DEPENDENCY DIVIDER -{20}$")
FIELD_DELIMITER = ":"
_PATTERN_FIELD_NAME_WORD_HEURISTIC = r"[A-Z]\w+"
_PATTERN_FIELD_NAME_HEURISTIC = re.compile(
r"^({}(?: {})*){}[\b\s]".format(
_PATTERN_FIELD_NAME_WORD_HEURISTIC,
_PATTERN_FIELD_NAME_WORD_HEURISTIC,
FIELD_DELIMITER,
)
)
_DEFAULT_TO_STRUCTURED_TEXT = False
_PATTERN_KNOWN_FIELD_DECLARATION = re.compile(
"^({}){}".format(
"|".join(
list(known_fields.ALL_FIELD_NAMES)
+ [metadata.fields.custom.mitigated.PATTERN_VULN_ID.pattern]
),
FIELD_DELIMITER,
),
re.IGNORECASE,
)
def parse_content(content: str) -> List[dm.DependencyMetadata]:
"""Reads and parses the metadata from the given string.
Args:
content: the string to parse metadata from.
Returns: all the metadata, which may be for zero or more
dependencies, from the given string.
"""
dependencies = []
current_metadata = dm.DependencyMetadata()
current_field_spec = None
current_field_name = None
current_field_value = ""
for line_number, line in enumerate(content.splitlines(keepends=True), 1):
if current_field_spec:
expect_structured_field_value = current_field_spec.is_structured()
else:
expect_structured_field_value = _DEFAULT_TO_STRUCTURED_TEXT
if DEPENDENCY_DIVIDER.match(line):
if current_field_name:
current_metadata.add_entry(
current_field_name, current_field_value
)
if current_metadata.has_entries():
dependencies.append(current_metadata)
current_metadata = dm.DependencyMetadata()
current_field_spec = None
current_field_name = None
current_field_value = ""
elif _PATTERN_KNOWN_FIELD_DECLARATION.match(line) or (
expect_structured_field_value
and _PATTERN_FIELD_NAME_HEURISTIC.match(line)
):
if current_field_name:
current_metadata.add_entry(
current_field_name, current_field_value
)
current_field_name, current_field_value = line.split(
FIELD_DELIMITER, 1
)
current_field_spec = known_fields.get_field(current_field_name)
current_metadata.record_line(line_number)
if current_field_spec:
current_metadata.record_field_line_number(
current_field_spec, line_number
)
elif current_field_name:
if line.strip():
current_metadata.record_line(line_number)
if current_field_spec:
current_metadata.record_field_line_number(
current_field_spec, line_number
)
current_field_value += line
else:
if line.strip():
current_metadata.record_line(line_number)
if current_field_spec and current_field_spec.should_terminate_field(
current_field_value
):
assert current_field_name
current_metadata.record_line(line_number)
if current_field_spec:
current_metadata.record_field_line_number(
current_field_spec, line_number
)
current_metadata.add_entry(current_field_name, current_field_value)
current_field_spec = None
current_field_name = None
current_field_value = ""
if current_field_name:
current_metadata.add_entry(current_field_name, current_field_value)
if current_metadata.has_entries():
dependencies.append(current_metadata)
return dependencies