157 lines
5.4 KiB
Go
157 lines
5.4 KiB
Go
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
|
||
}
|
||
}
|