浏览代码

metadata: revise CPE validation

Use the "official" XML schema validation pattern from published CPE
schema.

Add a error message to tell owners that they need to provide at least
one component (other than part) in CPE URN format.

Bug: 378273455
Change-Id: I5ac957f02a0f899d069161cdce54fff499fb35f3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/6073136
Commit-Queue: Jiewei Qian <qjw@chromium.org>
Reviewed-by: Anne Redulla <aredulla@google.com>
Jiewei Qian 8 月之前
父节点
当前提交
fd1c03e0be
共有 1 个文件被更改,包括 33 次插入41 次删除
  1. 33 41
      metadata/fields/custom/cpe_prefix.py

+ 33 - 41
metadata/fields/custom/cpe_prefix.py

@@ -19,48 +19,37 @@ import metadata.fields.field_types as field_types
 import metadata.fields.util as util
 import metadata.fields.util as util
 import metadata.validation_result as vr
 import metadata.validation_result as vr
 
 
-# Pattern for CPE 2.3 URI binding (also compatible with CPE 2.2).
-_PATTERN_CPE_URI = re.compile(
-    r"^c[pP][eE]:/[AHOaho]?(:[A-Za-z0-9._\-~%]*){0,6}$")
-
-# Pattern that will match CPE 2.3 formatted string binding.
-_PATTERN_CPE_FORMATTED_STRING = re.compile(r"^cpe:2\.3:[aho\-\*](:[^:]+){10}$")
-
-
-def is_uri_cpe(value: str) -> bool:
-    """Returns whether the value conforms to the CPE 2.3 URI binding
-    (which is compatible with CPE 2.2), with the additional constraint
-    that at least one component other than "part" has been specified.
-
-    For reference, see section 6.1 of the CPE Naming Specification
-    Version 2.3 at
-    https://nvlpubs.nist.gov/nistpubs/Legacy/IR/nistir7695.pdf.
+# Pattern that will match CPE 2.2 and CPE 2.3 URN format.
+#
+# Adapted from https://csrc.nist.gov/schema/cpe/2.3/cpe-naming_2.3.xsd.
+# We added a capture group (i.e. `match.group(1)`) for components-list, so we
+# can determine if the CPE prefix provides sufficient information (see
+# `is_adequate_cpe_urn` below).
+_PATTERN_CPE_URN = re.compile(
+    r"^[c][pP][eE]:/[AHOaho]?((:[A-Za-z0-9\._\-~%]*){0,6})$")
+
+# Pattern that will match CPE 2.3 string format.
+# Taken from https://csrc.nist.gov/schema/cpe/2.3/cpe-naming_2.3.xsd
+_PATTERN_CPE_FORMATTED_STRING = re.compile(
+    r"^cpe:2\.3:[aho\*\-](:(((\?*|\*?)([a-zA-Z0-9\-\._]|(\\[\\\*\?!\"#$$%&'\(\)\+,/:;<=>@\[\]\^`\{\|}~]))+(\?*|\*?))|[\*\-])){5}(:(([a-zA-Z]{2,3}(-([a-zA-Z]{2}|[0-9]{3}))?)|[\*\-]))(:(((\?*|\*?)([a-zA-Z0-9\-\._]|(\\[\\\*\?!\"#$$%&'\(\)\+,/:;<=>@\[\]\^`\{\|}~]))+(\?*|\*?))|[\*\-])){4}$"
+)
+
+
+def is_adequate_cpe_urn(value) -> bool:
+    """Returns whether a given `value` conforms to CPE 2.3 and CPE 2.2 URN
+    format, and the given `value` provides at least one component other than
+    "part".
+
+    See CPE Naming Specification: https://csrc.nist.gov/pubs/ir/7695/final.
     """
     """
-    if not util.matches(_PATTERN_CPE_URI, value):
-        return False
-
-    components = value.split(":")
-    if len(components) < 3:
-        # At most, only part was provided.
+    m = _PATTERN_CPE_URN.match(value)
+    if not m:
+        # Didn't match CPE URN format.
         return False
         return False
 
 
-    # Check at least one component other than "part" has been specified.
-    for component in components[2:]:
-        if component:
-            return True
-
-    return False
-
-
-def is_formatted_string_cpe(value: str) -> bool:
-    """Returns whether the value conforms to the CPE 2.3 formatted
-    string binding.
-
-    For reference, see section 6.2 of the CPE Naming Specification
-    Version 2.3 at
-    https://nvlpubs.nist.gov/nistpubs/Legacy/IR/nistir7695.pdf.
-    """
-    return util.matches(_PATTERN_CPE_FORMATTED_STRING, value)
+    cpe_components = m.group(1).split(':')
+    non_empty_components = list(filter(len, cpe_components))
+    return len(non_empty_components) > 0
 
 
 
 
 class CPEPrefixField(field_types.SingleLineTextField):
 class CPEPrefixField(field_types.SingleLineTextField):
@@ -69,8 +58,8 @@ class CPEPrefixField(field_types.SingleLineTextField):
         super().__init__(name="CPEPrefix")
         super().__init__(name="CPEPrefix")
 
 
     def _is_valid(self, value: str) -> bool:
     def _is_valid(self, value: str) -> bool:
-        return (util.is_unknown(value) or is_formatted_string_cpe(value)
-                or is_uri_cpe(value))
+        return util.is_unknown(value) or is_adequate_cpe_urn(
+            value) or util.matches(_PATTERN_CPE_FORMATTED_STRING, value)
 
 
     def validate(self, value: str) -> Optional[vr.ValidationResult]:
     def validate(self, value: str) -> Optional[vr.ValidationResult]:
         """Checks the given value is either 'unknown', or conforms to
         """Checks the given value is either 'unknown', or conforms to
@@ -86,6 +75,9 @@ class CPEPrefixField(field_types.SingleLineTextField):
                 "or 'unknown'.",
                 "or 'unknown'.",
                 "Search for a CPE tag for the package at "
                 "Search for a CPE tag for the package at "
                 "https://nvd.nist.gov/products/cpe/search.",
                 "https://nvd.nist.gov/products/cpe/search.",
+                "Please provide at least one CPE component other than part "
+                "(e.g. a for Application, h for Hardware, o for Operating "
+                "System)."
                 f"Current value: '{value}'.",
                 f"Current value: '{value}'.",
             ])
             ])