Add RSS feed generation and improve security features

- Implemented structured RSS feed generation using XML encoding.
- Enhanced URL registration by incorporating a random salt for hash generation.
- Introduced a bot blocker to the security middleware for improved bot detection.
- Updated security middleware to utilize the new bot blocker and added more entropy to request fingerprinting.
This commit is contained in:
2025-12-27 03:15:42 -06:00
parent f08e148b2f
commit ab3c188e91
4 changed files with 266 additions and 36 deletions

View File

@@ -1,8 +1,11 @@
package api
import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"log"
@@ -56,15 +59,22 @@ type Server struct {
rssCache atomic.Value // stores string
rssLastMod atomic.Value // stores time.Time
avatarCache string
salt []byte
}
func NewServer(token string, initialSoftware []models.Software, statsService *stats.Service) *Server {
salt := make([]byte, 32)
if _, err := rand.Read(salt); err != nil {
log.Fatalf("Failed to generate random salt: %v", err)
}
s := &Server{
GiteaToken: token,
SoftwareList: &SoftwareCache{data: initialSoftware},
Stats: statsService,
urlMap: make(map[string]string),
avatarCache: ".cache/avatars",
salt: salt,
}
s.rssCache.Store("")
s.rssLastMod.Store(time.Time{})
@@ -77,7 +87,11 @@ func NewServer(token string, initialSoftware []models.Software, statsService *st
}
func (s *Server) RegisterURL(targetURL string) string {
hash := fmt.Sprintf("%x", sha256.Sum256([]byte(targetURL)))
h := sha256.New()
h.Write(s.salt)
h.Write([]byte(targetURL))
hash := hex.EncodeToString(h.Sum(nil))
s.urlMapMu.Lock()
s.urlMap[hash] = targetURL
s.urlMapMu.Unlock()
@@ -344,6 +358,46 @@ func (s *Server) AvatarHandler(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write(data)
}
type rssFeed struct {
XMLName xml.Name `xml:"rss"`
Version string `xml:"version,attr"`
Atom string `xml:"xmlns:atom,attr"`
Channel rssChannel `xml:"channel"`
}
type rssChannel struct {
Title string `xml:"title"`
Link string `xml:"link"`
Description string `xml:"description"`
Language string `xml:"language"`
LastBuildDate string `xml:"lastBuildDate"`
AtomLink rssLink `xml:"atom:link"`
Items []rssItem `xml:"item"`
}
type rssLink struct {
Href string `xml:"href,attr"`
Rel string `xml:"rel,attr"`
Type string `xml:"type,attr"`
}
type rssItem struct {
Title string `xml:"title"`
Link string `xml:"link"`
Description rssDescription `xml:"description"`
GUID rssGUID `xml:"guid"`
PubDate string `xml:"pubDate"`
}
type rssDescription struct {
Content string `xml:",cdata"`
}
type rssGUID struct {
Content string `xml:",chardata"`
IsPermaLink bool `xml:"isPermaLink,attr"`
}
func (s *Server) RSSHandler(w http.ResponseWriter, r *http.Request) {
softwareList := s.SoftwareList.Get()
targetSoftware := r.URL.Query().Get("software")
@@ -383,17 +437,22 @@ func (s *Server) RSSHandler(w http.ResponseWriter, r *http.Request) {
selfLink = fmt.Sprintf("%s/api/rss?software=%s", baseURL, targetSoftware)
}
var b strings.Builder
b.WriteString(`<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>` + feedTitle + `</title>
<link>` + baseURL + `</link>
<description>` + feedDescription + `</description>
<language>en-us</language>
<lastBuildDate>` + time.Now().Format(time.RFC1123Z) + `</lastBuildDate>
<atom:link href="` + selfLink + `" rel="self" type="application/rss+xml" />
`)
feed := rssFeed{
Version: "2.0",
Atom: "http://www.w3.org/2005/Atom",
Channel: rssChannel{
Title: feedTitle,
Link: baseURL,
Description: feedDescription,
Language: "en-us",
LastBuildDate: time.Now().Format(time.RFC1123Z),
AtomLink: rssLink{
Href: selfLink,
Rel: "self",
Type: "application/rss+xml",
},
},
}
for i, it := range items {
if i >= 50 {
@@ -406,20 +465,27 @@ func (s *Server) RSSHandler(w http.ResponseWriter, r *http.Request) {
description = it.Release.Body
}
fmt.Fprintf(&b, ` <item>
<title>%s</title>
<link>%s</link>
<description><![CDATA[%s]]></description>
<guid isPermaLink="false">%s-%s</guid>
<pubDate>%s</pubDate>
</item>
`, title, link, description, it.Software.Name, it.Release.TagName, it.Release.CreatedAt.Format(time.RFC1123Z))
feed.Channel.Items = append(feed.Channel.Items, rssItem{
Title: title,
Link: link,
Description: rssDescription{
Content: description,
},
GUID: rssGUID{
Content: fmt.Sprintf("%s-%s", it.Software.Name, it.Release.TagName),
IsPermaLink: false,
},
PubDate: it.Release.CreatedAt.Format(time.RFC1123Z),
})
}
b.WriteString(`</channel>
</rss>`)
w.Header().Set("Content-Type", "application/rss+xml; charset=utf-8")
w.Header().Set("Cache-Control", "public, max-age=300")
_, _ = w.Write([]byte(b.String()))
fmt.Fprint(w, xml.Header)
enc := xml.NewEncoder(w)
enc.Indent("", " ")
if err := enc.Encode(feed); err != nil {
log.Printf("Error encoding RSS feed: %v", err)
}
}