Repository: spdx/tools-python

Issue:
Exception not catched with LicenseRef- containing slash
[slash.spdx.txt](https://github.com/spdx/tools-python/files/13284277/slash.spdx.txt)

File contains the following line:
```
LicenseConcluded: LicenseRef-foo/bar
```
which is invalid due to the slash.

This results in a big ugly error.
The exception should be catched.

```
pyspdxtools -i slash.spdx
Traceback (most recent call last):
  File "/opt/homebrew/bin/pyspdxtools", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/click/core.py", line 1157, in __call__
    return self.main(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/click/core.py", line 1078, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/click/core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/spdx_tools/spdx/clitools/pyspdxtools.py", line 61, in main
    document: Document = parse_file(infile)
                         ^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/spdx_tools/spdx/parser/parse_anything.py", line 25, in parse_file
    return tagvalue_parser.parse_from_file(file_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/spdx_tools/spdx/parser/tagvalue/tagvalue_parser.py", line 12, in parse_from_file
    document: Document = parser.parse(data)
                         ^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/spdx_tools/spdx/parser/tagvalue/parser.py", line 524, in parse
    self.yacc.parse(text, lexer=self.lex)
  File "/opt/homebrew/lib/python3.11/site-packages/ply/yacc.py", line 333, in parse
    return self.parseopt_notrack(input, lexer, debug, tracking, tokenfunc)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/ply/yacc.py", line 1120, in parseopt_notrack
    p.callable(pslice)
  File "/opt/homebrew/lib/python3.11/site-packages/spdx_tools/spdx/parser/tagvalue/parser.py", line 236, in p_license
    p[0] = get_spdx_licensing().parse(p[1])
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/license_expression/__init__.py", line 540, in parse
    tokens = list(self.tokenize(
             ^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/license_expression/__init__.py", line 604, in tokenize
    for token in tokens:
  File "/opt/homebrew/lib/python3.11/site-packages/license_expression/__init__.py", line 997, in replace_with_subexpression_by_license_symbol
    for token_group in token_groups:
  File "/opt/homebrew/lib/python3.11/site-packages/license_expression/__init__.py", line 936, in build_token_groups_for_with_subexpression
    tokens = list(tokens)
             ^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/license_expression/__init__.py", line 598, in <genexpr>
    tokens = (t for t in tokens if t.string and t.string.strip())
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/license_expression/__init__.py", line 922, in build_symbols_from_unknown_tokens
    for symtok in build_token_with_symbol():
  File "/opt/homebrew/lib/python3.11/site-packages/license_expression/__init__.py", line 902, in build_token_with_symbol
    toksym = LicenseSymbol(string)
             ^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/license_expression/__init__.py", line 1214, in __init__
    raise ExpressionError(
license_expression.ExpressionError: Invalid license key: the valid characters are: letters and numbers, underscore, dot, colon or hyphen signs and spaces: 'LicenseRef-foo/bar'
```


Discussion:

Patch:
diff --git a/src/spdx_tools/spdx/parser/tagvalue/parser.py b/src/spdx_tools/spdx/parser/tagvalue/parser.py
index a11f086..32be114 100644
--- a/src/spdx_tools/spdx/parser/tagvalue/parser.py
+++ b/src/spdx_tools/spdx/parser/tagvalue/parser.py
@@ -14,7 +14,7 @@
 import re
 
 from beartype.typing import Any, Dict, List
-from license_expression import get_spdx_licensing
+from license_expression import ExpressionError, get_spdx_licensing
 from ply import yacc
 from ply.yacc import LRParser
 
@@ -233,7 +233,13 @@ class Parser:
 
     @grammar_rule("license_or_no_assertion_or_none : LINE")
     def p_license(self, p):
-        p[0] = get_spdx_licensing().parse(p[1])
+        try:
+            p[0] = get_spdx_licensing().parse(p[1])
+        except ExpressionError as err:
+            error_message = f"Error while parsing license expression: {p[1]}"
+            if err.args:
+                error_message += f": {err.args[0]}"
+            self.current_element["logger"].append(error_message)
 
     @grammar_rule("actor_or_no_assertion : PERSON_VALUE\n | ORGANIZATION_VALUE")
     def p_actor_values(self, p):


Test patch:
diff --git a/tests/spdx/parser/tagvalue/test_tag_value_parser.py b/tests/spdx/parser/tagvalue/test_tag_value_parser.py
index 33defcb..9f347fc 100644
--- a/tests/spdx/parser/tagvalue/test_tag_value_parser.py
+++ b/tests/spdx/parser/tagvalue/test_tag_value_parser.py
@@ -98,3 +98,41 @@ def test_document_with_mixed_values():
         "Element Package is not the current element in scope, probably the expected "
         "tag to start the element (PackageName) is missing. Line: 4"
     ]
+
+
+def test_faulty_license_expression():
+    parser = Parser()
+    document_str = "\n".join(
+        [
+            f"SPDXID:{DOCUMENT_SPDX_ID}",
+            "FileName: File with faulty license expression",
+            "SPDXID: SPDXRef-File",
+            "FileChecksum: SHA1: d6a770ba38583ed4bb4525bd96e50461655d2759",
+            "LicenseConcluded: LicenseRef-foo/bar",
+            "PackageName: Package with faulty license expression",
+            "SPDXID: SPDXRef-Package",
+            "PackageDownloadLocation: www.download.com",
+            "PackageLicenseConcluded: LicenseRef-bar/foo",
+            "SnippetSPDXID: SPDXRef-Snippet",
+            "SnippetName: Snippet with faulty license expression",
+            "SnippetLicenseConcluded: LicenseRef-foo/foo",
+        ]
+    )
+
+    with pytest.raises(SPDXParsingError) as err:
+        parser.parse(document_str)
+
+    assert err.value.get_messages() == [
+        'Error while parsing File: ["Error while parsing license expression: '
+        "LicenseRef-foo/bar: Invalid license key: the valid characters are: letters "
+        "and numbers, underscore, dot, colon or hyphen signs and spaces: "
+        "'LicenseRef-foo/bar'\"]",
+        'Error while parsing Package: ["Error while parsing license expression: '
+        "LicenseRef-bar/foo: Invalid license key: the valid characters are: letters "
+        "and numbers, underscore, dot, colon or hyphen signs and spaces: "
+        "'LicenseRef-bar/foo'\"]",
+        'Error while parsing Snippet: ["Error while parsing license expression: '
+        "LicenseRef-foo/foo: Invalid license key: the valid characters are: letters "
+        "and numbers, underscore, dot, colon or hyphen signs and spaces: "
+        "'LicenseRef-foo/foo'\"]",
+    ]