diff --git a/parse.go b/parse.go index 060c23d..6aeca34 100644 --- a/parse.go +++ b/parse.go @@ -144,6 +144,7 @@ var ( ErrInvalidException = errors.New("invalid exception identifier") ErrMissingOperand = errors.New("missing operand") ErrInvalidSpecialValue = errors.New("NONE and NOASSERTION must be standalone") + ErrExpressionTooLarge = errors.New("expression too large") ) // tokenType represents the type of a lexer token. @@ -246,10 +247,16 @@ func (l *lexer) next() (token, error) { return token{typ: tokenLicense, value: word}, nil } +const ( + maxParseDepth = 256 + maxParseLength = 1 << 20 // 1 MiB +) + // parser parses SPDX expressions. type parser struct { lexer *lexer current token + depth int } func newParser(input string) (*parser, error) { @@ -288,6 +295,9 @@ func Parse(expression string) (Expression, error) { if expression == "" { return nil, ErrEmptyExpression } + if len(expression) > maxParseLength { + return nil, ErrExpressionTooLarge + } // Pre-process: normalize informal license names while preserving operators normalized, err := normalizeExpressionString(expression) @@ -326,6 +336,9 @@ func ParseStrict(expression string) (Expression, error) { if expression == "" { return nil, ErrEmptyExpression } + if len(expression) > maxParseLength { + return nil, ErrExpressionTooLarge + } p, err := newParser(expression) if err != nil { @@ -431,6 +444,11 @@ func (p *parser) parseWith() (Expression, error) { func (p *parser) parseAtom() (Expression, error) { switch p.current.typ { case tokenOpenParen: + p.depth++ + if p.depth > maxParseDepth { + return nil, ErrExpressionTooLarge + } + if err := p.advance(); err != nil { return nil, err } @@ -448,6 +466,7 @@ func (p *parser) parseAtom() (Expression, error) { return nil, err } + p.depth-- return expr, nil case tokenLicense: diff --git a/spdx_test.go b/spdx_test.go index 0d116d7..6358891 100644 --- a/spdx_test.go +++ b/spdx_test.go @@ -1,6 +1,8 @@ package spdx import ( + "errors" + "strings" "testing" ) @@ -568,3 +570,30 @@ func TestParseStrictRejectsInformalLicenses(t *testing.T) { }) } } + +func TestParseDeeplyNestedReturnsError(t *testing.T) { + input := strings.Repeat("(", 1000) + "MIT" + strings.Repeat(")", 1000) + _, err := ParseStrict(input) + if !errors.Is(err, ErrExpressionTooLarge) { + t.Fatalf("expected ErrExpressionTooLarge, got %v", err) + } +} + +func TestParseStrictRejectsOversizedInput(t *testing.T) { + input := strings.Repeat("X", maxParseLength+1) + _, err := ParseStrict(input) + if !errors.Is(err, ErrExpressionTooLarge) { + t.Fatalf("expected ErrExpressionTooLarge, got %v", err) + } +} + +func TestParseModerateNestingStillWorks(t *testing.T) { + input := strings.Repeat("(", 200) + "MIT" + strings.Repeat(")", 200) + expr, err := ParseStrict(input) + if err != nil { + t.Fatalf("ParseStrict on moderate nesting failed: %v", err) + } + if expr.String() != "MIT" { + t.Errorf("expected MIT, got %q", expr.String()) + } +}