burterm/internal/engine/filter.go
2026-09-14 10:55:07 +03:00

157 lines
5.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package engine
import (
"fmt"
"strconv"
"strings"
)
// StatusFilter решает, проходит ли статус-код фильтр.
type StatusFilter func(code int) bool
// ParseStatusFilter разбирает спецификацию фильтра по статус-коду:
// пустая строка — фильтр не задан (nil, всё проходит); список через
// запятую вида "200,301,404"; диапазоны вида "200-299"; можно смешивать
// в одном списке, напр. "200-299,404".
func ParseStatusFilter(spec string) (StatusFilter, error) {
spec = strings.TrimSpace(spec)
if spec == "" {
return nil, nil
}
var preds []func(int) bool
for _, tok := range strings.Split(spec, ",") {
tok = strings.TrimSpace(tok)
if tok == "" {
continue
}
if strings.Contains(tok, "-") {
parts := strings.SplitN(tok, "-", 2)
lo, err1 := strconv.Atoi(strings.TrimSpace(parts[0]))
hi, err2 := strconv.Atoi(strings.TrimSpace(parts[1]))
if err1 != nil || err2 != nil {
return nil, fmt.Errorf("некорректный диапазон статусов %q", tok)
}
preds = append(preds, func(code int) bool { return code >= lo && code <= hi })
continue
}
n, err := strconv.Atoi(tok)
if err != nil {
return nil, fmt.Errorf("некорректный статус %q", tok)
}
preds = append(preds, func(code int) bool { return code == n })
}
if len(preds) == 0 {
return nil, nil
}
return func(code int) bool {
for _, p := range preds {
if p(code) {
return true
}
}
return false
}, nil
}
// LengthFilter решает, проходит ли длина тела ответа фильтр.
type LengthFilter func(length int64) bool
// ParseLengthFilter разбирает спецификацию фильтра по длине тела:
// пустая строка — фильтр не задан; ">1000", "<500", ">=1000", "<=500" —
// операторы сравнения; "100-500" — диапазон; просто число — точное
// совпадение.
func ParseLengthFilter(spec string) (LengthFilter, error) {
return parseIntRangeFilter(spec)
}
// WordFilter решает, проходит ли число слов в теле ответа фильтр —
// аналог -mw/-fw у ffuf. "Слово" здесь — то же, что возвращает
// strings.Fields (последовательность без пробельных символов).
type WordFilter func(count int64) bool
// ParseWordFilter — тот же синтаксис, что и у ParseLengthFilter, просто
// сравнивается число слов, а не байт.
func ParseWordFilter(spec string) (WordFilter, error) {
return parseIntRangeFilter(spec)
}
// LineFilter решает, проходит ли число строк в теле ответа фильтр —
// аналог -ml/-fl у ffuf.
type LineFilter func(count int64) bool
// ParseLineFilter — тот же синтаксис, что и у остальных числовых фильтров.
func ParseLineFilter(spec string) (LineFilter, error) {
return parseIntRangeFilter(spec)
}
// parseIntRangeFilter — общая операторная грамматика, на которой держатся
// ParseLengthFilter/ParseWordFilter/ParseLineFilter: пустая строка —
// фильтр не задан; ">N", "<N", ">=N", "<=N" — операторы сравнения;
// "N-M" — диапазон; просто число — точное совпадение. Разница между
// тремя фильтрами только в том, что именно считается на стороне
// вызывающего кода (байты/слова/строки) — сам синтаксис один и тот же,
// поэтому парсер один, а не три копии одной и той же логики.
func parseIntRangeFilter(spec string) (func(int64) bool, error) {
spec = strings.TrimSpace(spec)
if spec == "" {
return nil, nil
}
parseNum := func(s string) (int64, error) {
n, err := strconv.ParseInt(strings.TrimSpace(s), 10, 64)
if err != nil {
return 0, fmt.Errorf("некорректное число в %q", spec)
}
return n, nil
}
switch {
case strings.HasPrefix(spec, ">="):
n, err := parseNum(spec[2:])
if err != nil {
return nil, err
}
return func(l int64) bool { return l >= n }, nil
case strings.HasPrefix(spec, "<="):
n, err := parseNum(spec[2:])
if err != nil {
return nil, err
}
return func(l int64) bool { return l <= n }, nil
case strings.HasPrefix(spec, ">"):
n, err := parseNum(spec[1:])
if err != nil {
return nil, err
}
return func(l int64) bool { return l > n }, nil
case strings.HasPrefix(spec, "<"):
n, err := parseNum(spec[1:])
if err != nil {
return nil, err
}
return func(l int64) bool { return l < n }, nil
case strings.Contains(spec, "-"):
parts := strings.SplitN(spec, "-", 2)
lo, err1 := parseNum(parts[0])
hi, err2 := parseNum(parts[1])
if err1 != nil || err2 != nil {
return nil, fmt.Errorf("некорректный диапазон %q", spec)
}
return func(l int64) bool { return l >= lo && l <= hi }, nil
default:
n, err := parseNum(spec)
if err != nil {
return nil, err
}
return func(l int64) bool { return l == n }, nil
}
}