diff --git a/spdxexp/parse.go b/spdxexp/parse.go index f66de71..17df0ab 100644 --- a/spdxexp/parse.go +++ b/spdxexp/parse.go @@ -99,6 +99,11 @@ func (t *tokenStream) parseParenthesizedExpression() *node { // paren not found return nil } + if !t.hasMore() { + // catch the case where there are no tokens after an open parenthesis + t.err = errors.New("open parenthesis does not have a matching close parenthesis") + return nil + } expr := t.parseExpression() if t.err != nil { @@ -106,13 +111,14 @@ func (t *tokenStream) parseParenthesizedExpression() *node { } if !t.hasMore() { - // no more tokens, so missing closing paren + // catch the case where there are no tokens after processing expression starting with an open parenthesis t.err = errors.New("open parenthesis does not have a matching close parenthesis") return nil } closeParen := t.parseOperator(")") if closeParen == nil { + // catch the case where the next token is not the expected close parenthesis t.err = errors.New("open parenthesis does not have a matching close parenthesis") return nil } @@ -281,6 +287,9 @@ func (t *tokenStream) parseLicenseRef() *node { ref := referenceNodePartial{documentRef: "", hasDocumentRef: false, licenseRef: ""} token := t.peek() + if token == nil { + return nil + } if token.role == documentRefToken { ref.documentRef = token.value ref.hasDocumentRef = true @@ -294,6 +303,12 @@ func (t *tokenStream) parseLicenseRef() *node { } token = t.peek() + if token == nil { + if ref.hasDocumentRef { + t.err = errors.New("expected 'LicenseRef-...' after 'DocumentRef-...'") + } + return nil + } if token.role != licenseRefToken && ref.hasDocumentRef { t.err = errors.New("expected 'LicenseRef-...' after 'DocumentRef-...'") return nil @@ -315,6 +330,9 @@ func (t *tokenStream) parseLicenseRef() *node { // an error is returned. Advances the index if a valid license is found. func (t *tokenStream) parseLicense() *node { token := t.peek() + if token == nil { + return nil + } if token.role != licenseToken { return nil } @@ -361,6 +379,9 @@ func (t *tokenStream) parseLicense() *node { // Advances the index if the operator is found. func (t *tokenStream) parseOperator(operator string) *string { token := t.peek() + if token == nil { + return nil + } if token.role == operatorToken && token.value == operator { t.next() return &(token.value) diff --git a/spdxexp/parse_test.go b/spdxexp/parse_test.go index 1d19abe..f0d16bf 100644 --- a/spdxexp/parse_test.go +++ b/spdxexp/parse_test.go @@ -41,6 +41,10 @@ func TestParse(t *testing.T) { "MIT", nil}, {"empty expression", "", nil, "", errors.New("parse error - cannot parse empty string")}, + {"operator error - nothing after open parenthesis", "(", nil, "", errors.New("open parenthesis does not have a matching close parenthesis")}, + {"operator error - nested dangling open parenthesis", "((", nil, "", errors.New("open parenthesis does not have a matching close parenthesis")}, + {"operator error - expression ending in open parenthesis", "MIT OR (", nil, "", errors.New("open parenthesis does not have a matching close parenthesis")}, + {"document reference without license reference", "DocumentRef-spdx-tool-1.2:", nil, "", errors.New("expected 'LicenseRef-...' after 'DocumentRef-...'")}, {"invalid license", "NON-EXISTENT-LICENSE", nil, "", errors.New("unknown license 'NON-EXISTENT-LICENSE' at offset 0")}, @@ -1075,6 +1079,7 @@ func TestParseTokens(t *testing.T) { }, "{ LEFT: { LEFT: MIT and RIGHT: Apache-1.0+ } or RIGHT: { LEFT: DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2 or RIGHT: GPL-2.0 with Bison-exception-2.2 } }", nil, }, + {"operator error - missing close parenthesis", getMissingEndParenTokens(0), &node{}, "", errors.New("open parenthesis does not have a matching close parenthesis"), }, @@ -1214,6 +1219,7 @@ func TestParseOperator(t *testing.T) { {"looking for + operator", getPlusClauseTokens(1), "+", false, 2}, {"looking for OR operator, but got AND", getAndClauseTokens(1), "OR", true, 1}, {"looking for OR operator, but got LICENSE", getOrClauseTokens(0), "OR", true, 0}, + {"looking for operator past end", getAndClauseTokens(3), "OR", true, 3}, } for _, test := range tests { diff --git a/spdxexp/satisfies_test.go b/spdxexp/satisfies_test.go index dc81df7..6955d95 100644 --- a/spdxexp/satisfies_test.go +++ b/spdxexp/satisfies_test.go @@ -272,6 +272,15 @@ func TestValidateAndNormalizeLicensesWithOptions_FailComplexExpressions(t *testi } } +func TestValidateAndNormalizeLicensesWithOptions_MalformedTrailingTokens(t *testing.T) { + licenses := []string{"(", "((", "MIT OR (", "DocumentRef-spdx-tool-1.2:"} + + normalizedLicenses, invalidLicenses := ValidateAndNormalizeLicensesWithOptions(licenses, ValidateLicensesOptions{}) + + assert.Empty(t, normalizedLicenses) + assert.Equal(t, licenses, invalidLicenses) +} + func TestValidateAndNormalizeLicensesWithOptions_FailDeprecatedLicenses(t *testing.T) { // eCos-2.0 is a known deprecated SPDX license ID (see TestDeprecatedLicense). deprecatedLicense := "eCos-2.0"