diff --git a/pkg/models/task_collection_filter.go b/pkg/models/task_collection_filter.go index fa5e11f75..7d2970277 100644 --- a/pkg/models/task_collection_filter.go +++ b/pkg/models/task_collection_filter.go @@ -284,6 +284,18 @@ func getFilterComparatorFromOp(op fexpr.SignOp) (taskFilterComparator, error) { } } +// safeDatemathParse wraps datemath.Parse with a recover to catch panics from +// the datemath lexer when given malformed input (e.g. "no" triggers a +// "scanner internal error" panic in the generated lexer). +func safeDatemathParse(s string) (expr datemath.Expression, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("datemath parse error: %v", r) + } + }() + return datemath.Parse(s) +} + func getValueForField(field reflect.StructField, rawValue string, loc *time.Location) (value interface{}, err error) { if loc == nil { @@ -305,7 +317,7 @@ func getValueForField(field reflect.StructField, rawValue string, loc *time.Loca if field.Type == schemas.TimeType { var t datemath.Expression var tt time.Time - t, err = datemath.Parse(rawValue) + t, err = safeDatemathParse(rawValue) if err == nil { tt = t.Time(datemath.WithLocation(loc)).In(config.GetTimeZone()) tt = adjustDateForMysql(tt) diff --git a/pkg/models/task_collection_filter_test.go b/pkg/models/task_collection_filter_test.go index e8954e752..577cf79e6 100644 --- a/pkg/models/task_collection_filter_test.go +++ b/pkg/models/task_collection_filter_test.go @@ -311,4 +311,11 @@ func TestParseFilter(t *testing.T) { assert.Equal(t, taskFilterComparatorEquals, firstSet[1].comparator) assert.Equal(t, int64(1), firstSet[1].value) }) + t.Run("invalid date value should not panic", func(t *testing.T) { + // "no" triggers a panic in the datemath lexer because it starts + // recognizing "now" but hits EOF after "no". The safeDatemathParse + // wrapper must recover from this panic and return an error instead. + _, err := getTaskFiltersFromFilterString("due_date = no", "UTC") + require.Error(t, err) + }) }