97 lines
3.5 KiB
Go
97 lines
3.5 KiB
Go
// 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
|
||
}
|