Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand All @@ -448,6 +466,7 @@ func (p *parser) parseAtom() (Expression, error) {
return nil, err
}

p.depth--
return expr, nil

case tokenLicense:
Expand Down
29 changes: 29 additions & 0 deletions spdx_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package spdx

import (
"errors"
"strings"
"testing"
)

Expand Down Expand Up @@ -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())
}
}