From c60d7bb8e7ff539f20f0ea8ba1a93abb7e1c9040 Mon Sep 17 00:00:00 2001 From: rissrice2105-agent Date: Sun, 21 Jun 2026 19:09:35 -0600 Subject: [PATCH] fix(nntp): reject malformed overview ranges --- internal/news/nntpd/range_test.go | 26 +++++++++++++++++++---- internal/news/nntpd/server.go | 35 ++++++++++++++++++++++--------- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/internal/news/nntpd/range_test.go b/internal/news/nntpd/range_test.go index 7cd35bf..b94a5ef 100644 --- a/internal/news/nntpd/range_test.go +++ b/internal/news/nntpd/range_test.go @@ -1,10 +1,28 @@ package nntpd -import "testing" +import ( + "math" + "testing" +) func TestParseRangeSingleArticle(t *testing.T) { - low, high := parseRange("5") - if low != 5 || high != 5 { - t.Fatalf(`parseRange("5") = %d, %d; want 5, 5`, low, high) + low, high, ok := parseRange("5") + if !ok || low != 5 || high != 5 { + t.Fatalf(`parseRange("5") = %d, %d, %v; want 5, 5, true`, low, high, ok) + } +} + +func TestParseRangeOpenEnded(t *testing.T) { + low, high, ok := parseRange("5-") + if !ok || low != 5 || high != math.MaxInt64 { + t.Fatalf(`parseRange("5-") = %d, %d, %v; want 5, MaxInt64, true`, low, high, ok) + } +} + +func TestParseRangeRejectsMalformed(t *testing.T) { + for _, spec := range []string{"abc", "1-abc", "-5", "10-5", "1-2-3"} { + if low, high, ok := parseRange(spec); ok { + t.Fatalf("parseRange(%q) = %d, %d, true; want invalid", spec, low, high) + } } } diff --git a/internal/news/nntpd/server.go b/internal/news/nntpd/server.go index 4665862..2aa8e13 100644 --- a/internal/news/nntpd/server.go +++ b/internal/news/nntpd/server.go @@ -165,25 +165,37 @@ func (s *Server) Process(nc net.Conn) { } } -func parseRange(spec string) (low, high int64) { +func parseRange(spec string) (low, high int64, ok bool) { if spec == "" { - return 0, math.MaxInt64 + return 0, math.MaxInt64, true } parts := strings.Split(spec, "-") if len(parts) == 1 { h, err := strconv.ParseInt(parts[0], 10, 64) if err != nil { - h = math.MaxInt64 - return 0, h + return 0, 0, false } - return h, h + return h, h, true } - l, _ := strconv.ParseInt(parts[0], 10, 64) - h, err := strconv.ParseInt(parts[1], 10, 64) + if len(parts) != 2 || parts[0] == "" { + return 0, 0, false + } + l, err := strconv.ParseInt(parts[0], 10, 64) if err != nil { - h = math.MaxInt64 + return 0, 0, false + } + var h int64 = math.MaxInt64 + if parts[1] != "" { + parsed, err := strconv.ParseInt(parts[1], 10, 64) + if err != nil { + return 0, 0, false + } + h = parsed } - return l, h + if h < l { + return 0, 0, false + } + return l, h, true } func handleOver(args []string, s *session, c *textproto.Conn) error { @@ -194,7 +206,10 @@ func handleOver(args []string, s *session, c *textproto.Conn) error { if len(args) > 0 { spec = args[0] } - from, to := parseRange(spec) + from, to, ok := parseRange(spec) + if !ok { + return ErrSyntax + } articles, err := s.backend.GetArticles(s.group, from, to) if err != nil { return err