script/fmt now runs golines -m 77 after gofmt, repeating golines until it changes nothing, since one pass can leave a line the next splits. script/fmt-check fails when either would change a file. script/bootstrap installs golines v0.13.0 with go install, pinned to its commit, and checks the installed version with go version -m. The tree is reformatted. The longer lines this makes are fixed by hand: nolint directives moved onto their own line above the statement, one string split, and 20 functions over funlen's 80 lines shortened or split, RunWAF and limitPercentages among them. Model: opus-5-5
This commit was merged in pull request #363.
This commit is contained in:
1 parent
63e58e326b
commit
0b23352767
118 files changed
+9447
-2670
No files matched your search
+55
-13
@@ -63,7 +63,9 @@ const escapeLength = 3
|
||||
var (
|
||||
// ruleLine is a rule: four fields separated by spaces or tabs, of
|
||||
// which the fourth, the regex, runs to the end of the line.
|
||||
ruleLine = regexp.MustCompile(`^([^ \t]+)[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]+(.+)$`)
|
||||
ruleLine = regexp.MustCompile(
|
||||
`^([^ \t]+)[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]+(.+)$`,
|
||||
)
|
||||
// idChars are the characters of a rule's id.
|
||||
idChars = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
|
||||
)
|
||||
@@ -74,7 +76,8 @@ var (
|
||||
"separated by spaces or tabs")
|
||||
errNotID = errors.New("is not an id of letters, digits, - and _")
|
||||
errNotTarget = errors.New(
|
||||
"is not path, query, uri, method, host, user_agent, referer or header:<Name>")
|
||||
"is not path, query, uri, method, host, user_agent, referer or header:<Name>",
|
||||
)
|
||||
errNotHeaderName = errors.New(
|
||||
"has a character after header: that no header name can have")
|
||||
errHeaderTakenOut = errors.New(
|
||||
@@ -281,7 +284,8 @@ func read(dir string) ([]Rule, string, error) {
|
||||
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
if entry.IsDir() || strings.HasPrefix(name, ".") || filepath.Ext(name) != extension {
|
||||
if entry.IsDir() || strings.HasPrefix(name, ".") ||
|
||||
filepath.Ext(name) != extension {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -298,8 +302,13 @@ func read(dir string) ([]Rule, string, error) {
|
||||
|
||||
// readFile appends the rules of the rule file at path to rules. places
|
||||
// are where each id read so far is, and gain those of the file.
|
||||
func readFile(path string, rules []Rule, places map[string]string) ([]Rule, error) {
|
||||
data, err := os.ReadFile(path) //nolint:gosec // a rule file, in SWW_RULES_DIR
|
||||
func readFile(
|
||||
path string,
|
||||
rules []Rule,
|
||||
places map[string]string,
|
||||
) ([]Rule, error) {
|
||||
//nolint:gosec // a rule file, in SWW_RULES_DIR
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -323,7 +332,13 @@ func readFile(path string, rules []Rule, places map[string]string) ([]Rule, erro
|
||||
|
||||
first, used := places[rule.ID]
|
||||
if used {
|
||||
return nil, fmt.Errorf("%s: the id %q %w %s", place, rule.ID, errUsedTwice, first)
|
||||
return nil, fmt.Errorf(
|
||||
"%s: the id %q %w %s",
|
||||
place,
|
||||
rule.ID,
|
||||
errUsedTwice,
|
||||
first,
|
||||
)
|
||||
}
|
||||
|
||||
places[rule.ID] = place
|
||||
@@ -355,22 +370,42 @@ func parse(line string) (Rule, bool, error) {
|
||||
case !idChars.MatchString(rule.ID):
|
||||
return Rule{}, false, fmt.Errorf("the id %q %w", rule.ID, errNotID)
|
||||
case !isTarget(rule.Target):
|
||||
return Rule{}, false, fmt.Errorf("the target %q %w", rule.Target, errNotTarget)
|
||||
return Rule{}, false, fmt.Errorf(
|
||||
"the target %q %w",
|
||||
rule.Target,
|
||||
errNotTarget,
|
||||
)
|
||||
case isHeader && !config.IsHeaderName(headerName):
|
||||
return Rule{}, false, fmt.Errorf("the target %q %w", rule.Target, errNotHeaderName)
|
||||
return Rule{}, false, fmt.Errorf(
|
||||
"the target %q %w",
|
||||
rule.Target,
|
||||
errNotHeaderName,
|
||||
)
|
||||
case strings.EqualFold(rule.Target, headerTarget+"Host"):
|
||||
return Rule{}, false, fmt.Errorf(
|
||||
"the target %q %w; the request's host is the target host",
|
||||
rule.Target, errHeaderTakenOut)
|
||||
case strings.EqualFold(rule.Target, headerTarget+"Transfer-Encoding"):
|
||||
return Rule{}, false, fmt.Errorf("the target %q %w", rule.Target, errHeaderTakenOut)
|
||||
return Rule{}, false, fmt.Errorf(
|
||||
"the target %q %w",
|
||||
rule.Target,
|
||||
errHeaderTakenOut,
|
||||
)
|
||||
case !slices.Contains([]string{ActionLog, ActionBlock, ActionBan}, rule.Action):
|
||||
return Rule{}, false, fmt.Errorf("the action %q %w", rule.Action, errNotAction)
|
||||
return Rule{}, false, fmt.Errorf(
|
||||
"the action %q %w",
|
||||
rule.Action,
|
||||
errNotAction,
|
||||
)
|
||||
}
|
||||
|
||||
regex, err := regexp.Compile(fields[4])
|
||||
if err != nil {
|
||||
return Rule{}, false, fmt.Errorf("the regex %w: %w", errNotRegex, err)
|
||||
return Rule{}, false, fmt.Errorf(
|
||||
"the regex %w: %w",
|
||||
errNotRegex,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
rule.regex = regex
|
||||
@@ -381,7 +416,13 @@ func parse(line string) (Rule, bool, error) {
|
||||
// isTarget reports whether target is one a rule may have.
|
||||
func isTarget(target string) bool {
|
||||
switch target {
|
||||
case pathTarget, "query", uriTarget, "method", "host", "user_agent", "referer":
|
||||
case pathTarget,
|
||||
"query",
|
||||
uriTarget,
|
||||
"method",
|
||||
"host",
|
||||
"user_agent",
|
||||
"referer":
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -397,7 +438,8 @@ func (rule Rule) matches(r *http.Request) bool {
|
||||
target := value(rule.Target, r)
|
||||
|
||||
if rule.Target == pathTarget || rule.Target == uriTarget {
|
||||
return rule.regex.MatchString(target) || rule.regex.MatchString(DecodeOnce(target))
|
||||
return rule.regex.MatchString(target) ||
|
||||
rule.regex.MatchString(DecodeOnce(target))
|
||||
}
|
||||
|
||||
return rule.regex.MatchString(target)
|
||||
|
||||
@@ -94,7 +94,13 @@ func TestEachTargetMatchesWhatItNames(t *testing.T) {
|
||||
|
||||
got := len(files.Match(r)) == 1
|
||||
if got != tc.want {
|
||||
t.Errorf("%s matches %s: %t, want %t", tc.rule, tc.uri, got, tc.want)
|
||||
t.Errorf(
|
||||
"%s matches %s: %t, want %t",
|
||||
tc.rule,
|
||||
tc.uri,
|
||||
got,
|
||||
tc.want,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -123,7 +129,12 @@ func TestPathMatchedAsTheClientSentIt(t *testing.T) {
|
||||
for _, target := range []string{
|
||||
tc.sent, "http://app.example" + tc.sent, "http:" + tc.sent, "foo:" + tc.sent,
|
||||
} {
|
||||
r := httptest.NewRequestWithContext(t.Context(), http.MethodGet, target, nil)
|
||||
r := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodGet,
|
||||
target,
|
||||
nil,
|
||||
)
|
||||
wantMatched(t, files, r, "as-sent")
|
||||
}
|
||||
}
|
||||
@@ -146,14 +157,20 @@ after path log ^/
|
||||
wantMatched(t, files, get(t, "/page"), "every-path", "after")
|
||||
|
||||
// A ban rule ends it too.
|
||||
files = load(t, ruleFiles{testFile: "ban path ban ^/\nlater path block ^/\n"})
|
||||
files = load(
|
||||
t,
|
||||
ruleFiles{testFile: "ban path ban ^/\nlater path block ^/\n"},
|
||||
)
|
||||
wantMatched(t, files, get(t, "/"), "ban")
|
||||
}
|
||||
|
||||
func TestSpacesAndTabsEndingALineAreNotPartOfItsRegex(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := load(t, ruleFiles{testFile: "env-file path block ^/\\.env$ \t \n"})
|
||||
files := load(
|
||||
t,
|
||||
ruleFiles{testFile: "env-file path block ^/\\.env$ \t \n"},
|
||||
)
|
||||
|
||||
wantMatched(t, files, get(t, "/.env"), "env-file")
|
||||
}
|
||||
@@ -184,7 +201,10 @@ func TestFileWhoseNameStartsWithADotIsNotARuleFile(t *testing.T) {
|
||||
|
||||
// The lock file Emacs makes beside a file while it is edited: a link to
|
||||
// nothing, which cannot be read.
|
||||
err := os.Symlink("user@host.1234:1700000000", filepath.Join(dir, ".#"+firstFile))
|
||||
err := os.Symlink(
|
||||
"user@host.1234:1700000000",
|
||||
filepath.Join(dir, ".#"+firstFile),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("symlink: %v", err)
|
||||
}
|
||||
@@ -276,7 +296,8 @@ func TestFaultStopsTheStartNamingTheFileAndLine(t *testing.T) {
|
||||
dir := writeFiles(t, ruleFiles{"00-default.rules": tc.content})
|
||||
path := filepath.Join(dir, "00-default.rules")
|
||||
|
||||
wantRefused(t, dir, path+", line "+strconv.Itoa(tc.line)+": "+tc.want)
|
||||
want := path + ", line " + strconv.Itoa(tc.line) + ": " + tc.want
|
||||
wantRefused(t, dir, want)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -289,8 +310,12 @@ func TestIDUsedTwiceStopsTheStartNamingBothPlaces(t *testing.T) {
|
||||
"50-b.rules": "other path log ^/b\nprobe path ban ^/c\n",
|
||||
})
|
||||
|
||||
wantRefused(t, dir, filepath.Join(dir, "50-b.rules")+`, line 2: the id "probe" `+
|
||||
"is already the id of the rule at "+filepath.Join(dir, "00-a.rules")+", line 1")
|
||||
wantRefused(
|
||||
t,
|
||||
dir,
|
||||
filepath.Join(dir, "50-b.rules")+`, line 2: the id "probe" `+
|
||||
"is already the id of the rule at "+filepath.Join(dir, "00-a.rules")+", line 1",
|
||||
)
|
||||
}
|
||||
|
||||
func TestDirectoryThatDoesNotExistStopsTheStart(t *testing.T) {
|
||||
@@ -305,7 +330,9 @@ func TestDirectoryThatDoesNotExistStopsTheStart(t *testing.T) {
|
||||
func TestEmptyDirectoryLoadsNoRulesAndSaysSo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params, lines := newParams(writeFiles(t, ruleFiles{"00-default.rules": "# none\n"}))
|
||||
params, lines := newParams(
|
||||
writeFiles(t, ruleFiles{"00-default.rules": "# none\n"}),
|
||||
)
|
||||
|
||||
files, err := rules.Load(params)
|
||||
if err != nil {
|
||||
@@ -314,7 +341,11 @@ func TestEmptyDirectoryLoadsNoRulesAndSaysSo(t *testing.T) {
|
||||
|
||||
line := lines.waitFor(t, read)
|
||||
if files.Len() != 0 || line["rules"] != 0.0 {
|
||||
t.Errorf("%d rules loaded, and the log says %v, want none", files.Len(), line)
|
||||
t.Errorf(
|
||||
"%d rules loaded, and the log says %v, want none",
|
||||
files.Len(),
|
||||
line,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,7 +406,12 @@ func TestBrokenEditKeepsTheRulesAsTheyWere(t *testing.T) {
|
||||
|
||||
// The edit's second line has an unknown action, so the rules stay as
|
||||
// they were, the first line's earlier version included.
|
||||
save(t, dir, firstFile, "first path block ^/edited\nsecond path bann ^/second\n")
|
||||
save(
|
||||
t,
|
||||
dir,
|
||||
firstFile,
|
||||
"first path block ^/edited\nsecond path bann ^/second\n",
|
||||
)
|
||||
|
||||
line := lines.waitFor(t, hasError)
|
||||
want := filepath.Join(dir, firstFile) +
|
||||
@@ -393,7 +429,11 @@ func TestBrokenEditKeepsTheRulesAsTheyWere(t *testing.T) {
|
||||
if len(waiting) != 1 || waiting[0].Event != alerts.EventFileError ||
|
||||
waiting[0].Reason != hasError || waiting[0].Detail["error"] != want ||
|
||||
waiting[0].Detail["file"] != filepath.Join(dir, firstFile) {
|
||||
t.Errorf("alerts waiting %+v, want one file_error alert for %q", waiting, want)
|
||||
t.Errorf(
|
||||
"alerts waiting %+v, want one file_error alert for %q",
|
||||
waiting,
|
||||
want,
|
||||
)
|
||||
}
|
||||
}
|
||||
wantFileError()
|
||||
@@ -402,8 +442,16 @@ func TestBrokenEditKeepsTheRulesAsTheyWere(t *testing.T) {
|
||||
wantMatched(t, files, get(t, "/second"))
|
||||
|
||||
// Once mended, the file is read again, and raises no alert.
|
||||
save(t, dir, firstFile, "first path block ^/edited\nsecond path ban ^/second\n")
|
||||
lines.waitUntil(t, func() bool { return len(files.Match(get(t, "/second"))) == 1 })
|
||||
save(
|
||||
t,
|
||||
dir,
|
||||
firstFile,
|
||||
"first path block ^/edited\nsecond path ban ^/second\n",
|
||||
)
|
||||
lines.waitUntil(
|
||||
t,
|
||||
func() bool { return len(files.Match(get(t, "/second"))) == 1 },
|
||||
)
|
||||
wantMatched(t, files, get(t, "/edited"), "first")
|
||||
wantFileError()
|
||||
}
|
||||
@@ -559,7 +607,10 @@ func load(t *testing.T, files ruleFiles) *rules.Files {
|
||||
// watch loads the rules in dir, runs their Watch until the test ends, and
|
||||
// waits until it watches the directory. It returns the alerts' queue as
|
||||
// well.
|
||||
func watch(t *testing.T, dir string) (*rules.Files, processLog, *alerts.Queue) {
|
||||
func watch(
|
||||
t *testing.T,
|
||||
dir string,
|
||||
) (*rules.Files, processLog, *alerts.Queue) {
|
||||
t.Helper()
|
||||
|
||||
params, lines := newParams(dir)
|
||||
@@ -611,21 +662,31 @@ func get(t *testing.T, target string) *http.Request {
|
||||
|
||||
// wantRefusedBy checks that a GET request for target with the user agent
|
||||
// sent matches rule alone, and that rule refuses it.
|
||||
func wantRefusedBy(t *testing.T, files *rules.Files, target, sent, rule string) {
|
||||
func wantRefusedBy(
|
||||
t *testing.T,
|
||||
files *rules.Files,
|
||||
target, sent, rule string,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
r := get(t, target)
|
||||
r.Header.Set(userAgent, sent)
|
||||
|
||||
matched := files.Match(r)
|
||||
if len(matched) != 1 || matched[0].ID != rule || matched[0].Action == rules.ActionLog {
|
||||
if len(matched) != 1 || matched[0].ID != rule ||
|
||||
matched[0].Action == rules.ActionLog {
|
||||
t.Errorf("%s from %q matched %v, want %s alone, refusing it", target,
|
||||
sent, ids(matched), rule)
|
||||
}
|
||||
}
|
||||
|
||||
// wantMatched checks the ids of the rules r matches, in order.
|
||||
func wantMatched(t *testing.T, files *rules.Files, r *http.Request, want ...string) {
|
||||
func wantMatched(
|
||||
t *testing.T,
|
||||
files *rules.Files,
|
||||
r *http.Request,
|
||||
want ...string,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
got := ids(files.Match(r))
|
||||
|
||||
Reference in new issue
Block a user