Skip to content

Commit 2f2212f

Browse files
Thomas StrombergThomas Stromberg
authored andcommitted
lint
1 parent 6201a93 commit 2f2212f

File tree

6 files changed

+18
-3
lines changed

6 files changed

+18
-3
lines changed

email/templates.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
)
1111

1212
func (s *Sender) formatNotificationBody(sub *notifier.Subscription, thread *notifier.Thread, posts []*notifier.Post) string {
13-
var b strings.Builder
13+
var b strings.Builder //nolint:varnamelen // Standard short variable name for strings.Builder
1414

1515
b.WriteString("<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n")
1616
b.WriteString("<meta charset=\"utf-8\">\n")
@@ -52,6 +52,7 @@ func (s *Sender) formatNotificationBody(sub *notifier.Subscription, thread *noti
5252
for _, post := range posts {
5353
b.WriteString("<div class=\"post\">\n")
5454
b.WriteString("<div class=\"meta\">\n")
55+
//nolint:gocritic // %q would add extra quotes in HTML context
5556
b.WriteString(fmt.Sprintf("<a href=\"%s\" class=\"post-number\">#%s</a>\n", escapeHTML(post.URL), escapeHTML(post.ID)))
5657
b.WriteString(fmt.Sprintf("<span class=\"author\"> &bull; %s</span>\n", escapeHTML(post.Author)))
5758
if post.Timestamp != "" {
@@ -82,6 +83,7 @@ func (s *Sender) formatNotificationBody(sub *notifier.Subscription, thread *noti
8283
if len(posts) > 1 {
8384
footerClass = "footer with-border"
8485
}
86+
//nolint:gocritic // %q would add extra quotes in HTML context
8587
b.WriteString(fmt.Sprintf("<div class=\"%s\">\n", footerClass))
8688

8789
// Link to the last page with anchor to latest post (e.g., .../page-12#post-12345)
@@ -90,9 +92,11 @@ func (s *Sender) formatNotificationBody(sub *notifier.Subscription, thread *noti
9092
if len(posts) > 0 && posts[len(posts)-1].URL != "" {
9193
threadLink = posts[len(posts)-1].URL
9294
}
95+
//nolint:gocritic // %q would add extra quotes in HTML context
9396
b.WriteString(fmt.Sprintf("<a href=\"%s\">View thread on ADVrider</a>\n", escapeHTML(threadLink)))
9497

9598
manageURL := fmt.Sprintf("%s/manage?token=%s", s.baseURL, url.QueryEscape(sub.Token))
99+
//nolint:gocritic // %q would add extra quotes in HTML context
96100
b.WriteString(fmt.Sprintf("<a href=\"%s\">Manage subscriptions</a>\n", escapeHTML(manageURL)))
97101
b.WriteString("</div>\n")
98102

@@ -144,8 +148,10 @@ func (s *Sender) formatWelcomeBody(sub *notifier.Subscription, thread *notifier.
144148
b.WriteString("</div>\n")
145149

146150
b.WriteString("<div class=\"footer\">\n")
151+
//nolint:gocritic // %q would add extra quotes in HTML context
147152
b.WriteString(fmt.Sprintf("<a href=\"%s\">View thread on ADVrider</a>\n", escapeHTML(thread.ThreadURL)))
148153
b.WriteString(" &bull; \n")
154+
//nolint:gocritic // %q would add extra quotes in HTML context
149155
b.WriteString(fmt.Sprintf("<a href=\"%s\">Manage subscriptions</a>\n", escapeHTML(manageURL)))
150156
b.WriteString("</div>\n")
151157

@@ -166,6 +172,8 @@ func escapeHTML(s string) string {
166172
// sanitizeHTML sanitizes untrusted HTML content using a strict whitelist approach.
167173
// Only allows safe tags and attributes to prevent XSS, phishing, and tracking.
168174
// This is designed for email contexts where security is critical.
175+
//
176+
//nolint:gocognit,funlen // Security-critical HTML sanitizer - complexity justified for comprehensive safety
169177
func sanitizeHTML(html string) string {
170178
// Whitelist of allowed tags (no scripts, forms, iframes, etc.)
171179
allowedTags := map[string]bool{
@@ -190,6 +198,7 @@ func sanitizeHTML(html string) string {
190198
inTag := false
191199
tagStart := 0
192200

201+
//nolint:intrange,varnamelen // Index used for look-ahead parsing - range loop not suitable
193202
for i := 0; i < len(html); i++ {
194203
if html[i] == '<' {
195204
if inTag {

email/templates_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,8 @@ func TestSanitizeHTMLXSSAttempts(t *testing.T) {
248248
// TestSanitizeHTMLPlaceholders tests that dangerous media tags show placeholders.
249249
func TestSanitizeHTMLPlaceholders(t *testing.T) {
250250
tests := []struct {
251-
name string
252-
input string
251+
name string
252+
input string
253253
shouldContain string
254254
}{
255255
{"iframe with URL", `<iframe src="https://youtube.com/embed/xyz"></iframe>`, "[iframe: <a href=\"https://youtube.com/embed/xyz\">"},

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ func main() {
5252
}
5353

5454
// Local development mode
55+
//nolint:nestif // Local vs production initialization - complexity justified for environment handling
5556
if localStorage != "" {
5657
logger.Info("Running in local development mode", "storage_path", localStorage)
5758
if baseURL == "" {

scraper/scraper.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ func parsePage(body interface{ Read([]byte) (int, error) }, threadURL string) (*
298298

299299
// Extract posts
300300
var posts []*notifier.Post
301+
//nolint:revive // goquery callback requires index parameter
301302
doc.Find("li.message").Each(func(i int, s *goquery.Selection) {
302303
// Extract post ID from id attribute
303304
postIDAttr, exists := s.Attr("id")

server/manage.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func (s *Server) handleUnsubscribe(w http.ResponseWriter, r *http.Request) {
2525
http.Redirect(w, r, "/manage?token="+url.QueryEscape(token), http.StatusSeeOther)
2626
}
2727

28+
//nolint:varnamelen // Standard http.Handler parameter names
2829
func (s *Server) handleManage(w http.ResponseWriter, r *http.Request) {
2930
// Rate limiting by IP to prevent token enumeration
3031
ip := clientIP(r)
@@ -54,6 +55,7 @@ func (s *Server) handleManage(w http.ResponseWriter, r *http.Request) {
5455
}
5556

5657
// Handle unsubscribe from specific thread
58+
//nolint:nestif // POST handler with CSRF validation - complexity justified for security
5759
if r.Method == http.MethodPost {
5860
action := r.FormValue("action")
5961
threadID := r.FormValue("thread_id")

server/subscribe.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"advrider-notifier/poll"
1212
)
1313

14+
//nolint:maintidx,funlen,varnamelen // HTTP handler with comprehensive validation - complexity justified for security
1415
func (s *Server) handleSubscribe(w http.ResponseWriter, r *http.Request) {
1516
if r.Method != http.MethodPost {
1617
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
@@ -180,6 +181,7 @@ func (s *Server) handleSubscribe(w http.ResponseWriter, r *http.Request) {
180181
// Trigger immediate poll to notify all existing subscribers about any new posts
181182
// This runs asynchronously so we don't block the HTTP response
182183
// Use background context since we don't want this tied to the HTTP request lifecycle
184+
//nolint:contextcheck // Background context intentional - we don't want this tied to HTTP request lifecycle
183185
go func() {
184186
pollCtx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
185187
defer cancel()

0 commit comments

Comments
 (0)