fix(news): stop IHAVE when duplicate lookup fails (#89)
Some checks failed
CI / build (push) Has been cancelled
deploy / deploy (push) Has been cancelled
test / test (push) Has been cancelled

Co-authored-by: SabFabDev <SabFabDev@users.noreply.github.com>
This commit is contained in:
SabFabDev 2026-07-12 09:43:24 +02:00 committed by GitHub
parent c2a06f1cdf
commit 21630c2d17
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 49 additions and 4 deletions

View file

@ -88,7 +88,6 @@ type Server struct {
// ErrLog, when non-nil, receives connection-level errors. Protocol verbs // ErrLog, when non-nil, receives connection-level errors. Protocol verbs
// are never logged (a public server should not log every command). // are never logged (a public server should not log every command).
ErrLog *log.Logger ErrLog *log.Logger
group *nntp.Group
} }
// NewServer builds a server bound to a backend. // NewServer builds a server bound to a backend.
@ -371,6 +370,9 @@ func handleIHave(args []string, s *session, c *textproto.Conn) error {
return ErrSyntax return ErrSyntax
} }
article, err := s.backend.GetArticle(nil, args[0]) article, err := s.backend.GetArticle(nil, args[0])
if err != nil && err != ErrInvalidMessageID && err != ErrInvalidArticleNumber {
return err
}
if article != nil { if article != nil {
return ErrNotWanted return ErrNotWanted
} }

View file

@ -12,8 +12,10 @@ import (
) )
type whitespaceBackend struct { type whitespaceBackend struct {
group *nntp.Group group *nntp.Group
allowPost bool allowPost bool
articleErr error
postCalls int
} }
func (b *whitespaceBackend) ListGroups(max int) ([]*nntp.Group, error) { 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) { func (b *whitespaceBackend) GetArticle(group *nntp.Group, id string) (*nntp.Article, error) {
if b.articleErr != nil {
return nil, b.articleErr
}
return nil, ErrInvalidArticleNumber 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) AllowPost() bool { return b.allowPost }
func (b *whitespaceBackend) Post(article *nntp.Article) error { func (b *whitespaceBackend) Post(article *nntp.Article) error {
b.postCalls++
return errors.New("posting disabled") return errors.New("posting disabled")
} }
@ -127,3 +133,41 @@ func TestIHaveWithoutMessageIDReturnsSyntaxError(t *testing.T) {
t.Fatal("server did not close after QUIT") 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 <new@example.test>"); 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")
}
}

View file

@ -17,7 +17,6 @@ var (
nSel = lipgloss.NewStyle().Foreground(lipgloss.Color("#0b1020")).Background(ui.Cyan) nSel = lipgloss.NewStyle().Foreground(lipgloss.Color("#0b1020")).Background(ui.Cyan)
nMeta = ui.Dim nMeta = ui.Dim
nFrom = lipgloss.NewStyle().Foreground(ui.Green) nFrom = lipgloss.NewStyle().Foreground(ui.Green)
nErr = ui.Danger
) )
// RunReader connects the member to the loopback NNTP server and drives the // RunReader connects the member to the loopback NNTP server and drives the