Keep _redirects rules that target a path on the site
What does this MR do?
When a _redirects rule's "to" starts with a splat, such as /blog/* /:splat 301, and the splat itself starts with a slash, the templated target starts with //. regexMultipleSlashes in internal/redirects/matching.go only collapses a run of slashes that follows another character, so the leading // is kept and url.Parse reads the first segment of the splat as a host. The rule names a path on the site, but the redirect goes to another host.
This MR:
- Collapses a run of slashes at the start of the templated target as well:
(^|[^:])//+. The^branch leaves$1empty, and the://of an absolute target is still left alone. - Adds a check to
Redirects.Rewrite: when the rewritten URL has a host or a scheme that the rule's "to" does not have, it returns an error instead of the URL. With the first change this cannot happen, so the check only keeps a later change to the matching from sending a rule that targets a path to another host.tryRedirectsalready treats a rewrite error as no redirect.
| Rule | Request | master |
This branch |
|---|---|---|---|
/blog/* /:splat 301 |
/blog//other.example/x |
//other.example/x |
/other.example/x |
/blog/* /:splat 301 |
/blog///other.example/x |
//other.example/x |
/other.example/x |
/blog/* /:splat/index.html 301 |
/blog//other.example |
//other.example/index.html |
/other.example/index.html |
/blog/* /:splat 200 |
/blog//other.example/x |
//other.example/x |
/other.example/x |
Related to #1216
Tests
TestMatchesRule: one case,/foo/* /:splatwith/foo//bar/baz.TestRedirectsRewrite: the four rows of the table above.TestRedirectsRewriteRefusesURLThatLeavesSite: puts the old pattern back for the test, so that templating gives//other.example/x, and checks thatRewritereturnserrRewriteLeavesSiteand no URL.Test_leavesSite: a path rewritten to a path, to a host and to a scheme, a URL rewritten to a URL, and a "to" that does not parse.
On master (fa2bfa8c) with this branch's test files, the five new cases of TestMatchesRule and TestRedirectsRewrite fail on the target they get. The two tests of the check call code that master does not have, so they were left out of that run:
--- FAIL: TestMatchesRule/splat_at_start_of_target_collapses_leading_slashes
expected: "/bar/baz"
actual : "//bar/baz"
--- FAIL: TestRedirectsRewrite/splat_target_with_leading_slashes_stays_on_site
--- FAIL: TestRedirectsRewrite/splat_target_with_three_leading_slashes_stays_on_site
--- FAIL: TestRedirectsRewrite/splat_target_with_suffix_stays_on_site
--- FAIL: TestRedirectsRewrite/splat_rewrite_with_leading_slashes_stays_on_siteEach of these mutations makes at least one new test fail: the old pattern, Rewrite without the check, leavesSite without its host or its scheme clause, ignoring the host or the scheme of the rule's own "to", and accepting a "to" that does not parse.
The checks I ran
With go1.26.10 and go1.27.3, the versions of the CI matrix:
make unit-test: 1040 tests pass.make acceptance: 413 tests pass.
With go1.26.10:
go build ./...,go vet ./...andgofmt -l: clean.golangci-lint run ./...with v2.12.2, the versioncode_qualityuses:0 issues.make validate-log-fields,make deps-checkandmake mocks-check: ok.make race: no race reported. Every package passes buttest/acceptance, where a few timing-dependent tests (rate limits and auth) failed on this loaded host, different ones on each run.make raceonmasterfails the same way here, andTestNamespaceInPathEnabled_IPRateLimitsfails now and then onmastereven when run alone.
TODO
- Feature flag
- Added feature flag:
- This feature does not require a feature flag
- I added the
Changelogtrailer to the commits that need to be included in the changelog (e.g.Changelog: added) - I added unit tests or they are not required
- I added acceptance tests or they are not required
- I added documentation (or it's not required)
- I followed code review guidelines
- I followed Go Style guidelines