diff --git a/internal/news/nntpd/range_test.go b/internal/news/nntpd/range_test.go index 7cd35bf..c9eddef 100644 --- a/internal/news/nntpd/range_test.go +++ b/internal/news/nntpd/range_test.go @@ -1,6 +1,9 @@ package nntpd -import "testing" +import ( + "math" + "testing" +) func TestParseRangeSingleArticle(t *testing.T) { low, high := parseRange("5") @@ -8,3 +11,27 @@ func TestParseRangeSingleArticle(t *testing.T) { t.Fatalf(`parseRange("5") = %d, %d; want 5, 5`, low, high) } } + +func TestParseRange(t *testing.T) { + // spec -> (low, high). Empty bounds mean the extreme; malformed -> (0, 0). + cases := []struct { + spec string + low int64 + high int64 + }{ + {"5", 5, 5}, + {"5-10", 5, 10}, + {"5-", 5, math.MaxInt64}, + {"-10", 0, 10}, + {"", 0, math.MaxInt64}, + {"abc", 0, 0}, + {"5-abc", 0, 0}, + {"abc-10", 0, 0}, + } + for _, c := range cases { + low, high := parseRange(c.spec) + if low != c.low || high != c.high { + t.Errorf("parseRange(%q) = %d, %d; want %d, %d", c.spec, low, high, c.low, c.high) + } + } +} diff --git a/internal/news/nntpd/server.go b/internal/news/nntpd/server.go index 8ac5a50..4a7998d 100644 --- a/internal/news/nntpd/server.go +++ b/internal/news/nntpd/server.go @@ -165,27 +165,35 @@ func (s *Server) Process(nc net.Conn) { } } +// parseRange parses an NNTP article range spec (RFC 3977 §6.2.3 / RFC 2980). +// An empty bound means "the extreme": "5-" is article 5 through the last +// article, "-10" is the first article through 10, "5-10" is 5..10, "5" is the +// single article 5, and "" is all articles. A genuinely malformed bound yields +// an empty range (0, 0). func parseRange(spec string) (low, high int64) { if spec == "" { return 0, math.MaxInt64 } - parts := strings.Split(spec, "-") - if len(parts) == 1 { - h, err := strconv.ParseInt(parts[0], 10, 64) + parts := strings.SplitN(spec, "-", 2) + low, high = 0, math.MaxInt64 + if parts[0] != "" { + v, err := strconv.ParseInt(parts[0], 10, 64) if err != nil { - return 0, 0 // malformed — empty range instead of all articles + return 0, 0 // malformed low bound — empty range } - return h, h + low = v } - l, err := strconv.ParseInt(parts[0], 10, 64) - if err != nil { - return 0, 0 // malformed — empty range + if len(parts) == 1 { // single article, e.g. "5" + return low, low } - h, err := strconv.ParseInt(parts[1], 10, 64) - if err != nil { - return 0, 0 // malformed — empty range + if parts[1] != "" { + v, err := strconv.ParseInt(parts[1], 10, 64) + if err != nil { + return 0, 0 // malformed high bound — empty range + } + high = v } - return l, h + return low, high } func handleOver(args []string, s *session, c *textproto.Conn) error {