internal/postgres: make exclusions work componentwise

Change the way excluded prefixes match paths to work by path component.

Previously prefix "bad" would exclude "bad", "badness" and "bad/ness".
This CL makes it exclude "bad" (an exact match) and "bad/ness"
(component suffix), but not "badness".

For golang/go#41844

Change-Id: I021d8a7ba8816e3d356f883313ffe3eb53ec717d
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/262179
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/internal/postgres/excluded.go b/internal/postgres/excluded.go
index 69d9ea8..b1cef35 100644
--- a/internal/postgres/excluded.go
+++ b/internal/postgres/excluded.go
@@ -15,12 +15,20 @@
 )
 
 // IsExcluded reports whether the path matches the excluded list.
+// A path matches an entry on the excluded list if it equals the entry, or
+// is a component-wise suffix of the entry.
+// So path "bad/ness" matches entries "bad" and "bad/", but path "badness"
+// matches neither of those.
 func (db *DB) IsExcluded(ctx context.Context, path string) (_ bool, err error) {
 	defer derrors.Wrap(&err, "DB.IsExcluded(ctx, %q)", path)
 
 	eps := db.expoller.Current().([]string)
 	for _, prefix := range eps {
-		if strings.HasPrefix(path, prefix) {
+		prefixSlash := prefix
+		if !strings.HasSuffix(prefix, "/") {
+			prefixSlash += "/"
+		}
+		if path == prefix || strings.HasPrefix(path, prefixSlash) {
 			log.Infof(ctx, "path %q matched excluded prefix %q", path, prefix)
 			return true, nil
 		}
diff --git a/internal/postgres/excluded_test.go b/internal/postgres/excluded_test.go
index 6fa3b61..70f5840 100644
--- a/internal/postgres/excluded_test.go
+++ b/internal/postgres/excluded_test.go
@@ -17,6 +17,9 @@
 	if err := testDB.InsertExcludedPrefix(ctx, "bad", "someone", "because"); err != nil {
 		t.Fatal(err)
 	}
+	if err := testDB.InsertExcludedPrefix(ctx, "badslash/", "someone", "because"); err != nil {
+		t.Fatal(err)
+	}
 	for _, test := range []struct {
 		path string
 		want bool
@@ -24,8 +27,11 @@
 		{"fine", false},
 		{"ba", false},
 		{"bad", true},
-		{"badness", true},
-		{"bad.com/foo", true},
+		{"badness", false},
+		{"bad/ness", true},
+		{"bad.com/foo", false},
+		{"badslash", false},
+		{"badslash/more", true},
 	} {
 		got, err := testDB.IsExcluded(ctx, test.path)
 		if err != nil {
@@ -35,4 +41,5 @@
 			t.Errorf("%q: got %t, want %t", test.path, got, test.want)
 		}
 	}
+
 }
diff --git a/internal/postgres/search_test.go b/internal/postgres/search_test.go
index 3f1cf1c..6a0bdc3 100644
--- a/internal/postgres/search_test.go
+++ b/internal/postgres/search_test.go
@@ -619,7 +619,7 @@
 
 	// Insert a module with two packages.
 	const domain = "exclude.com"
-	sm := sample.LegacyModule(domain, "v1.2.3", "pkg", "exclude")
+	sm := sample.LegacyModule(domain, "v1.2.3", "pkg", "ex/clude")
 	if err := testDB.InsertModule(ctx, sm); err != nil {
 		t.Fatal(err)
 	}