aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/design.md7
-rw-r--r--internal/engine/exclude_test.go42
-rw-r--r--internal/engine/facts.go9
-rw-r--r--man/krino.conf.52
4 files changed, 57 insertions, 3 deletions
diff --git a/docs/design.md b/docs/design.md
index 5b70183..a232baf 100644
--- a/docs/design.md
+++ b/docs/design.md
@@ -202,7 +202,9 @@ no rule has run yet, `(matched)` is never true inside an exclude.
An exclude fails closed: when evaluating it reaches a content test that
cannot read the file (over `max-read`, a tool missing, failing or timing
out), the exclude holds, and the file is set aside as "(content
-unreadable)" with the warning. An exclude protects files, so a file krino
+unreadable)" with the warning. A file whose format has no text at all (an
+image, an archive) is not unreadable: it contains no keyword, so its
+content tests are simply false and raise no warning. An exclude protects files, so a file krino
could not check is left alone. Mistakes in krino.conf's excludes are
reported even while no directory is included.
@@ -255,7 +257,8 @@ UTF-8 is replaced by U+FFFD before folding.
`path`, then `duplicate`, then `content`. Content is extracted at most once per file,
and only if evaluation reaches a `content` test.
4. If a file's text cannot be extracted, `content` is false and the plan
- shows a warning naming the rule that wanted it. Inside an `exclude` the
+ shows a warning naming the rule that wanted it. A format with no text
+ (§6, "anything else") is not a failure: `content` is false, silently. Inside an `exclude` the
whole exclude then holds instead (§4.6).
### 5.5 Duplicates
diff --git a/internal/engine/exclude_test.go b/internal/engine/exclude_test.go
index 87fa16c..4bb1512 100644
--- a/internal/engine/exclude_test.go
+++ b/internal/engine/exclude_test.go
@@ -260,3 +260,45 @@ func TestLoadChecksMainExcludesWithoutDirectories(t *testing.T) {
t.Error("a broken krino.conf exclude was not reported")
}
}
+
+// TestNoTextFormatIsNoMatch: a file whose format has no text cannot contain
+// a keyword, so a content exclude without a type does not set it aside, and
+// no "content unreadable" warning is raised - while a real read failure (over
+// max-read) still fails closed (re-review N2, Łukasz's decision).
+func TestNoTextFormatIsNoMatch(t *testing.T) {
+ big := "confidential " + strings.Repeat("x", 2048)
+ h, dl := excludeTree(t, map[string]string{"photo.jpg": "\xff\xd8\xff\x00\x01binary", "big.txt": big})
+ main := writeConfig(t, h, `(include "dl")`, map[string]string{"dl": `
+(path "~/dl")
+(max-read 1K)
+(exclude (content "confidential"))
+(rule "pics" (when (type jpg)) (move "Pictures"))
+`})
+ e, errs := Load(main)
+ if len(errs) > 0 {
+ t.Fatal(errs)
+ }
+ r, err := e.Match(context.Background(), e.Dirs[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, fm := range r.Matched {
+ switch fm.File.Rel {
+ case "photo.jpg":
+ if fm.Excluded != "" || len(fm.Rules) != 1 || len(fm.Warnings) != 0 {
+ t.Errorf("photo.jpg: Excluded %q, rules %d, warnings %v; want the pics rule and no warning", fm.Excluded, len(fm.Rules), fm.Warnings)
+ }
+ case "big.txt":
+ if !strings.HasSuffix(fm.Excluded, "(content unreadable)") {
+ t.Errorf("big.txt: Excluded %q; a real read failure must still fail closed", fm.Excluded)
+ }
+ }
+ }
+ x, err := e.Explain(context.Background(), filepath.Join(dl, "photo.jpg"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if x.Excluded != "" {
+ t.Errorf("explain: Excluded %q, want none", x.Excluded)
+ }
+}
diff --git a/internal/engine/facts.go b/internal/engine/facts.go
index 487604b..ccdf18d 100644
--- a/internal/engine/facts.go
+++ b/internal/engine/facts.go
@@ -4,6 +4,7 @@ package engine
import (
"context"
+ "errors"
"path/filepath"
"sort"
"strings"
@@ -178,7 +179,13 @@ func (f *facts) ContentContains(opt cond.Options, keywords []string) (int, error
func (f *facts) extract(opt cond.Options, keywords []string) {
f.contentDone = true
text, err := f.run.e.Extract.Text(f.run.ctx, f.file.Path, f.file.Size, f.run.d.Settings.MaxRead)
- if err != nil {
+ switch {
+ case errors.Is(err, extract.ErrUnsupported):
+ // A format with no text cannot contain a keyword: every answer is
+ // no, with no warning, and the answers are cached like any other
+ // (re-review N2). Only a real read failure is "unreadable".
+ text = ""
+ case err != nil:
f.contentErr = err
return
}
diff --git a/man/krino.conf.5 b/man/krino.conf.5
index 5153255..1fc113d 100644
--- a/man/krino.conf.5
+++ b/man/krino.conf.5
@@ -279,6 +279,8 @@ fails closed: when a content test it reaches cannot read the file
the exclude holds and the file is set aside as
.Dq (content unreadable) ,
with a warning.
+A file whose format has no text at all, such as an image or an archive,
+is not unreadable: its content tests are false, with no warning.
.Sh RULES
.Bd -literal -offset indent
(rule NAME ITEM...)