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

97 lines
3.5 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 sitemap строит иерархическую карту сайта из перехваченного
// трафика (аналог Target / Site Map в Burp) и управляет scope —
// набором regex-правил include/exclude, которыми фильтруется, что
// попадает в карту.
//
// Пакет намеренно не зависит от internal/proxy — на вход Build
// принимает свои Record, а не proxy.Entry, чтобы sitemap оставался
// переиспользуемым независимо от того, откуда взялись перехваченные
// запросы (сейчас это Proxy, но с тем же успехом это мог бы быть,
// например, лог из файла).
package sitemap
import (
"fmt"
"regexp"
)
// Rule — одно правило scope: regex-паттерн и его тип (include/exclude).
type Rule struct {
Pattern string
Exclude bool
}
type compiledRule struct {
Rule
re *regexp.Regexp
}
// Scope — упорядоченный набор regex-правил. Порядок добавления не влияет
// на результат сопоставления (см. Matches), но сохраняется для
// отображения в UI в том виде, в котором пользователь их вводил.
type Scope struct {
rules []compiledRule
}
// NewScope создаёт пустой scope — по умолчанию всё считается в scope,
// пока не добавлено хотя бы одно include-правило.
func NewScope() *Scope {
return &Scope{}
}
// Add компилирует pattern и добавляет как include- или exclude-правило.
func (s *Scope) Add(pattern string, exclude bool) error {
re, err := regexp.Compile(pattern)
if err != nil {
return fmt.Errorf("некорректный regex %q: %w", pattern, err)
}
s.rules = append(s.rules, compiledRule{Rule: Rule{Pattern: pattern, Exclude: exclude}, re: re})
return nil
}
// RemoveAt удаляет правило по индексу (в порядке добавления, том же,
// что возвращает Rules). Индекс вне диапазона игнорируется.
func (s *Scope) RemoveAt(idx int) {
if idx < 0 || idx >= len(s.rules) {
return
}
s.rules = append(s.rules[:idx], s.rules[idx+1:]...)
}
// Rules возвращает текущие правила в порядке добавления — для отображения в UI.
func (s *Scope) Rules() []Rule {
out := make([]Rule, len(s.rules))
for i, r := range s.rules {
out[i] = r.Rule
}
return out
}
// Matches решает, попадает ли url в scope:
// - если url совпал с любым exclude-правилом — не в scope, безоговорочно;
// - иначе, если есть хотя бы одно include-правило — url должен совпасть
// хотя бы с одним из них;
// - если include-правил нет вовсе — по умолчанию всё в scope
// (кроме того, что попало под exclude).
func (s *Scope) Matches(url string) bool {
hasInclude := false
includeMatched := false
for _, r := range s.rules {
if r.Exclude {
if r.re.MatchString(url) {
return false
}
continue
}
hasInclude = true
if r.re.MatchString(url) {
includeMatched = true
}
}
if hasInclude {
return includeMatched
}
return true
}