feat: redirect engine with loop refusal and chain notices
- rule set: exact sources by key, patterns by literal prefix, regex by id (stdlib RE2, compiled once per source); ignore case, trailing slash, query ignore/pass, a source with ? matches its query exactly - targets: captures escaped, a relative target never leaves the site, an absolute one keeps its host - SaveRedirect validates, simulates the rule in place (exact, pattern and regex probes, 10 hops) and refuses a loop; chains answer the notice - resolve follows the chain to its final target, 410 answers gone, a runtime cycle answers not_found and is logged once
This commit is contained in:
@@ -7,12 +7,12 @@ import (
|
||||
|
||||
// createRedirects creates golem15_seo_redirects: 301, 302 and 410 rules
|
||||
// with exact, pattern and regex sources (D-16, D-17), one per source and
|
||||
// match.
|
||||
// match, indexed on the lowercased source for the resolver's lookup.
|
||||
var createRedirects = []*gormigrate.Migration{
|
||||
{
|
||||
ID: "202610270006_create_golem15_seo_redirects",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.Exec(`CREATE TABLE golem15_seo_redirects (
|
||||
if err := tx.Exec(`CREATE TABLE golem15_seo_redirects (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
source TEXT NOT NULL,
|
||||
match TEXT NOT NULL DEFAULT 'exact',
|
||||
@@ -26,7 +26,11 @@ var createRedirects = []*gormigrate.Migration{
|
||||
created_at TIMESTAMP(0) NULL,
|
||||
updated_at TIMESTAMP(0) NULL,
|
||||
CONSTRAINT golem15_seo_redirects_source_match UNIQUE (source, match)
|
||||
)`).Error
|
||||
)`).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// The resolver reads exact rules by their lowercased source.
|
||||
return tx.Exec(`CREATE INDEX golem15_seo_redirects_lower_source ON golem15_seo_redirects (lower(source))`).Error
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
return tx.Exec(`DROP TABLE IF EXISTS golem15_seo_redirects`).Error
|
||||
|
||||
Reference in New Issue
Block a user