From f3f9328498f9cb917b314b60d8d8d9a1c6531b93 Mon Sep 17 00:00:00 2001 From: SabFabDev Date: Fri, 10 Jul 2026 22:57:04 +0200 Subject: [PATCH] fix(news): stop IHAVE when duplicate lookup fails --- internal/news/nntpd/server.go | 4 ++- internal/news/nntpd/server_test.go | 48 ++++++++++++++++++++++++++++-- internal/news/tui.go | 1 - 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/internal/news/nntpd/server.go b/internal/news/nntpd/server.go index 5153f38..baecb9f 100644 --- a/internal/news/nntpd/server.go +++ b/internal/news/nntpd/server.go @@ -88,7 +88,6 @@ type Server struct { // ErrLog, when non-nil, receives connection-level errors. Protocol verbs // are never logged (a public server should not log every command). ErrLog *log.Logger - group *nntp.Group } // NewServer builds a server bound to a backend. @@ -371,6 +370,9 @@ func handleIHave(args []string, s *session, c *textproto.Conn) error { return ErrSyntax } article, err := s.backend.GetArticle(nil, args[0]) + if err != nil && err != ErrInvalidMessageID && err != ErrInvalidArticleNumber { + return err + } if article != nil { return ErrNotWanted } diff --git a/internal/news/nntpd/server_test.go b/internal/news/nntpd/server_test.go index d54ca07..c32d1f6 100644 --- a/internal/news/nntpd/server_test.go +++ b/internal/news/nntpd/server_test.go @@ -12,8 +12,10 @@ import ( ) type whitespaceBackend struct { - group *nntp.Group - allowPost bool + group *nntp.Group + allowPost bool + articleErr error + postCalls int } func (b *whitespaceBackend) ListGroups(max int) ([]*nntp.Group, error) { @@ -28,6 +30,9 @@ func (b *whitespaceBackend) GetGroup(name string) (*nntp.Group, error) { } func (b *whitespaceBackend) GetArticle(group *nntp.Group, id string) (*nntp.Article, error) { + if b.articleErr != nil { + return nil, b.articleErr + } return nil, ErrInvalidArticleNumber } @@ -44,6 +49,7 @@ func (b *whitespaceBackend) Authenticate(user, pass string) (Backend, error) { func (b *whitespaceBackend) AllowPost() bool { return b.allowPost } func (b *whitespaceBackend) Post(article *nntp.Article) error { + b.postCalls++ return errors.New("posting disabled") } @@ -127,3 +133,41 @@ func TestIHaveWithoutMessageIDReturnsSyntaxError(t *testing.T) { t.Fatal("server did not close after QUIT") } } + +func TestIHaveStopsWhenDuplicateCheckFails(t *testing.T) { + backend := &whitespaceBackend{ + group: &nntp.Group{Name: "pfs.general", Posting: nntp.PostingPermitted}, + allowPost: true, + articleErr: errors.New("backend unavailable"), + } + server := NewServer(backend) + clientConn, serverConn := net.Pipe() + defer clientConn.Close() + + done := make(chan struct{}) + go func() { + server.Process(serverConn) + close(done) + }() + + client := textproto.NewConn(clientConn) + defer client.Close() + if line, err := client.ReadLine(); err != nil || !strings.HasPrefix(line, "200 ") { + t.Fatalf("greeting = %q, %v", line, err) + } + if err := client.PrintfLine("IHAVE "); err != nil { + t.Fatalf("send IHAVE: %v", err) + } + if line, err := client.ReadLine(); err == nil { + t.Fatalf("IHAVE duplicate-check failure returned %q; want connection close", line) + } + if backend.postCalls != 0 { + t.Fatalf("Post called %d times after duplicate-check failure; want 0", backend.postCalls) + } + + select { + case <-done: + case <-time.After(time.Second): + t.Fatal("server did not close after duplicate-check failure") + } +} diff --git a/internal/news/tui.go b/internal/news/tui.go index 6859a68..baad5de 100644 --- a/internal/news/tui.go +++ b/internal/news/tui.go @@ -17,7 +17,6 @@ var ( nSel = lipgloss.NewStyle().Foreground(lipgloss.Color("#0b1020")).Background(ui.Cyan) nMeta = ui.Dim nFrom = lipgloss.NewStyle().Foreground(ui.Green) - nErr = ui.Danger ) // RunReader connects the member to the loopback NNTP server and drives the