package api import ( "net/http" "regexp" "strconv" "git.golem15.com/golem15/sm-sitemap-plugin/classes" "git.golem15.com/golem15/summercms/modules/backpack" "git.golem15.com/golem15/summercms/modules/wire" "gorm.io/gorm" ) // GroupPattern is the {group} constraint of the HTML sitemap group route: // a file group slug. const GroupPattern = `[a-z0-9][a-z0-9-]{0,63}` // localePattern is the form a locale query value must have. var localePattern = regexp.MustCompile(`^[a-z]{2,3}([-_][A-Za-z0-9]{2,8})*$`) // pagePattern is the form of the page query value: 1 to 6 digits. var pagePattern = regexp.MustCompile(`^[0-9]{1,6}$`) // htmlRequest checks the shared parts of an HTML sitemap request and // returns the settings and database, or answers 404 (the HTML sitemap is // off, a malformed locale) or 500 itself. func htmlRequest(app func() *backpack.App, w http.ResponseWriter, r *http.Request) (classes.HTMLSitemap, bool) { w.Header().Set("X-Robots-Tag", "noindex") w.Header().Set("Cache-Control", "no-cache, private") a := app() if a == nil { http.NotFound(w, r) return classes.HTMLSitemap{}, false } if loc := r.URL.Query().Get("locale"); loc != "" && !localePattern.MatchString(loc) { http.NotFound(w, r) return classes.HTMLSitemap{}, false } db, ok := a.Lookup[*gorm.DB]() if !ok || db == nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return classes.HTMLSitemap{}, false } return classes.HTMLSitemap{App: a, DB: db.WithContext(r.Context())}, true } // HTMLOverview serves GET /_sitemap/api/v1/html?locale=: the HTML sitemap // overview (D-34; classes.HTMLSitemap.Overview). It lists exactly the // addresses of the XML sitemaps and answers 404 while the HTML sitemap is // off. Every answer carries X-Robots-Tag: noindex. func HTMLOverview(app func() *backpack.App) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { h, ok := htmlRequest(app, w, r) if !ok { return } settings, err := classes.LoadSettings(h.DB) if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } if !settings.HTMLEnabled { http.NotFound(w, r) return } out, err := h.Overview(r.Context(), settings, r.URL.Query().Get("locale")) if err != nil { log.Error("golem15.sitemap: html overview", "error", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } wire.WriteJSON(w, http.StatusOK, out) } } // HTMLGroup serves GET /_sitemap/api/v1/html/{group}?locale=&page=: one // page (from 1, the default) of a group (classes.HTMLSitemap.Group). An // unknown group, a page past the last, a malformed page or the HTML // sitemap off answer 404. func HTMLGroup(app func() *backpack.App) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { h, ok := htmlRequest(app, w, r) if !ok { return } page := 1 if raw := r.URL.Query().Get("page"); raw != "" { n, err := strconv.Atoi(raw) if !pagePattern.MatchString(raw) || err != nil || n < 1 { http.NotFound(w, r) return } page = n } settings, err := classes.LoadSettings(h.DB) if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } if !settings.HTMLEnabled { http.NotFound(w, r) return } out, found, err := h.Group(r.Context(), settings, r.URL.Query().Get("locale"), r.PathValue("group"), page) if err != nil { log.Error("golem15.sitemap: html group", "error", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } if !found { http.NotFound(w, r) return } wire.WriteJSON(w, http.StatusOK, out) } }