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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user