Files
sm-seo-plugin/classes/verification.go
Jakub Zych ec74062624 feat: verification files API
- GET /_seo/api/v1/verification/{name} serves a verification file stored
  in the SEO settings with its content type, nosniff and a sandboxing CSP
  (D-05, D-103); the name pattern is enforced in the route and the
  lookup, and no route is registered at the site root
2026-10-27 11:52:55 +01:00

54 lines
1.6 KiB
Go

package classes
import (
"context"
"regexp"
"strings"
)
// VerificationNamePattern is the form of a verification file name (D-05,
// T-32-21): no slashes, an html, txt or xml extension.
const VerificationNamePattern = `[A-Za-z0-9._-]+\.(html|txt|xml)`
var verificationName = regexp.MustCompile(`^` + VerificationNamePattern + `$`)
// VerificationFile is one stored search-engine verification file.
type VerificationFile struct {
Name string
Body string
ContentType string
}
// verificationTypes are the content types per extension.
var verificationTypes = map[string]string{
".html": "text/html; charset=utf-8",
".txt": "text/plain; charset=utf-8",
".xml": "application/xml; charset=utf-8",
}
// VerificationFileByName returns the verification file stored in the SEO
// settings under name (D-05, D-103), found false for an unknown or
// malformed name. The frontend serves it at the site root (Phase 33); the
// plugin never registers a root route for it.
func (s *Service) VerificationFileByName(ctx context.Context, name string) (VerificationFile, bool, error) {
if !verificationName.MatchString(name) {
return VerificationFile{}, false, nil
}
db, err := s.DB(ctx)
if err != nil {
return VerificationFile{}, false, err
}
settings, err := LoadSettings(db)
if err != nil {
return VerificationFile{}, false, err
}
for _, f := range settings.Files() {
if strings.TrimSpace(f.Filename) != name {
continue
}
ext := name[strings.LastIndex(name, "."):]
return VerificationFile{Name: name, Body: f.Body, ContentType: verificationTypes[strings.ToLower(ext)]}, true, nil
}
return VerificationFile{}, false, nil
}