From 46b2d930c1f2432276132f32b03565260dfbe0bf Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 8 Jun 2026 21:24:39 +0000 Subject: [PATCH] ds: introduce do_gets to simplify NNTP/IMAP/POP3 callers Supporting non-blocking readline (aka `gets') will likely be useful in lei and other places. For now, we can use it to simplify public-facing NNTP, IMAP, and POP3 implementations. I wanted to be able to use PublicInbox::IO::my_gets and the buffering in PublicInbox::IO for this, but unfortunately supporting compression and TLS with these network protocols proved too tricky to get right with tied IO. Thus the {rbuf} in PublicInbox::DS remains separate from {pi_io_rbuf} in PublicInbox::IO. --- lib/PublicInbox/DS.pm | 20 ++++++++++++++++++++ lib/PublicInbox/IMAP.pm | 19 ++++++------------- lib/PublicInbox/NNTP.pm | 19 ++++++------------- lib/PublicInbox/POP3.pm | 17 ++++++----------- 4 files changed, 38 insertions(+), 37 deletions(-) diff --git a/lib/PublicInbox/DS.pm b/lib/PublicInbox/DS.pm index 12ca38a35..3ad7fae43 100644 --- a/lib/PublicInbox/DS.pm +++ b/lib/PublicInbox/DS.pm @@ -455,6 +455,26 @@ sub do_read ($$$;$) { $r; # undef or 0 (EOF) } +sub rbuf_size { length(${$_[0]->{rbuf} // return}) } + +# the final record MUST end with $delim, otherwise it is stuck in $self->{rbuf} +sub do_gets { + my ($self, $delim) = @_; + my ($rec, $r, $rbuf); + $rbuf = $self->{rbuf} // \(my $x = ''); + $delim //= "\n"; + while (1) { + if (($r = index($$rbuf, $delim)) >= 0) { + $rec = substr $$rbuf, 0, $r + length($delim), ''; + rbuf_idle $self, $rbuf; + return $rec; + } + # do_read may be implemented in PublicInbox::DSdeflate + $r = $self->do_read($rbuf, 65536, length($$rbuf)) // return; + return '' if !$r; + } +} + # drop the socket if we hit unrecoverable errors on our system which # require BOFH attention: ENOSPC, EFBIG, EIO, EMFILE, ENFILE... sub drop ($@) { diff --git a/lib/PublicInbox/IMAP.pm b/lib/PublicInbox/IMAP.pm index c85804922..74c76f549 100644 --- a/lib/PublicInbox/IMAP.pm +++ b/lib/PublicInbox/IMAP.pm @@ -1196,28 +1196,21 @@ sub event_step { # only read more requests if we've drained the write buffer, # otherwise we can be buffering infinitely w/o backpressure - my $rbuf = $self->{rbuf} // \(my $x = ''); - my $line = index($$rbuf, "\n"); - while ($line < 0) { - if (length($$rbuf) >= LINE_MAX) { + my $line = $self->do_gets // do { + if (($self->rbuf_size // 0) >= LINE_MAX) { $self->write(\"\* BAD request too long\r\n"); return $self->close; } - $self->do_read($rbuf, LINE_MAX, length($$rbuf)) or - return uo2m_hibernate($self); - $line = index($$rbuf, "\n"); - } - $line = substr($$rbuf, 0, $line + 1, ''); - $line =~ s/\r?\n\z//s; - return $self->close if $line =~ /[[:cntrl:]]/s; + return $self->{sock} ? uo2m_hibernate($self) : undef; + }; + ($line eq '' || !($line =~ s/\r?\n\z//s) || $line =~ /[[:cntrl:]]/s) + and return $self->close; my $t0 = now(); my $fd = fileno($self->{sock}); my $r = eval { process_line($self, $line) }; my $pending = $self->{wbuf} ? ' pending' : ''; out($self, "[$fd] %s - %0.6f$pending - $r", $line, now() - $t0); - return $self->close if $r < 0; - $self->rbuf_idle($rbuf); # maybe there's more pipelined data, or we'll have # to register it for socket-readiness notifications diff --git a/lib/PublicInbox/NNTP.pm b/lib/PublicInbox/NNTP.pm index 49ba1bddb..49bdf4458 100644 --- a/lib/PublicInbox/NNTP.pm +++ b/lib/PublicInbox/NNTP.pm @@ -966,25 +966,18 @@ sub event_step { # only read more requests if we've drained the write buffer, # otherwise we can be buffering infinitely w/o backpressure - - my $rbuf = $self->{rbuf} // \(my $x = ''); - my $line = index($$rbuf, "\n"); - while ($line < 0) { - return $self->close if length($$rbuf) >= LINE_MAX; - $self->do_read($rbuf, LINE_MAX, length($$rbuf)) or return; - $line = index($$rbuf, "\n"); - } - $line = substr($$rbuf, 0, $line + 1, ''); - $line =~ s/\r?\n\z//s; - return $self->close if $line =~ /[[:cntrl:]]/s; - + my $line = $self->do_gets // do { + return ($self->rbuf_size // 0) >= LINE_MAX || !$self->{sock} ? + $self->close : undef; + }; + ($line eq '' || !($line =~ s/\r?\n\z//s) || $line =~ /[[:cntrl:]]/s) + and return $self->close; my $t0 = now(); my $fd = fileno($self->{sock}); my $r = eval { process_line($self, $line) }; my $pending = $self->{wbuf} ? ' pending' : ''; out($self, "[$fd] %s - %0.6f$pending", $line, now() - $t0); return $self->close if $r < 0; - $self->rbuf_idle($rbuf); # maybe there's more pipelined data, or we'll have # to register it for socket-readiness notifications diff --git a/lib/PublicInbox/POP3.pm b/lib/PublicInbox/POP3.pm index fae02df88..619049c4e 100644 --- a/lib/PublicInbox/POP3.pm +++ b/lib/PublicInbox/POP3.pm @@ -399,23 +399,18 @@ sub event_step { # only read more requests if we've drained the write buffer, # otherwise we can be buffering infinitely w/o backpressure - my $rbuf = $self->{rbuf} // \(my $x = ''); - my $line = index($$rbuf, "\n"); - while ($line < 0) { - return $self->close if length($$rbuf) >= LINE_MAX; - $self->do_read($rbuf, LINE_MAX, length($$rbuf)) or return; - $line = index($$rbuf, "\n"); - } - $line = substr($$rbuf, 0, $line + 1, ''); - $line =~ s/\r?\n\z//s; - return $self->close if $line =~ /[[:cntrl:]]/s; + my $line = $self->do_gets // do { + return ($self->rbuf_size // 0) >= LINE_MAX || !$self->{sock} ? + $self->close : undef; + }; + ($line eq '' || !($line =~ s/\r?\n\z//s) || $line =~ /[[:cntrl:]]/s) + and return $self->close; my $t0 = now(); my $fd = fileno($self->{sock}); # may become invalid after process_line my $r = eval { process_line($self, $line) }; my $pending = $self->{wbuf} ? ' pending' : ''; out($self, "[$fd] %s - %0.6f$pending - $r", $line, now() - $t0); return $self->close if $r < 0; - $self->rbuf_idle($rbuf); # maybe there's more pipelined data, or we'll have # to register it for socket-readiness notifications -- 2.47.3