X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FNNTP.pm;h=e4ca7d1462e292d1127f8f1abe02731b1c7d2afd;hb=806b83f28401901daaf9170b030fca6fd2fd930e;hp=0b43cdbc5b9308378d953b250e45f3e8da398ea6;hpb=0d38f65c490466837ae091afa7a7b6f59d04ce7c;p=public-inbox.git diff --git a/lib/PublicInbox/NNTP.pm b/lib/PublicInbox/NNTP.pm index 0b43cdbc..e4ca7d14 100644 --- a/lib/PublicInbox/NNTP.pm +++ b/lib/PublicInbox/NNTP.pm @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 all contributors +# Copyright (C) all contributors # License: AGPL-3.0+ # # Each instance of this represents a NNTP client socket @@ -9,6 +9,7 @@ # long_cb: long_response private data package PublicInbox::NNTP; use strict; +use v5.10.1; use parent qw(PublicInbox::DS); use PublicInbox::MID qw(mid_escape $MID_EXTRACT); use PublicInbox::Eml; @@ -23,9 +24,9 @@ use constant { LINE_MAX => 512, # RFC 977 section 2.3 r501 => '501 command syntax error', r502 => '502 Command unavailable', - r221 => '221 Header follows', + r221 => "221 Header follows\r\n", r224 => '224 Overview information follows (multi-line)', - r225 => '225 Headers follow (multi-line)', + r225 => "225 Headers follow (multi-line)\r\n", r430 => '430 No article with that message-id', }; use PublicInbox::Syscall qw(EPOLLIN EPOLLONESHOT); @@ -64,14 +65,13 @@ sub new ($$$) { } else { greet($self); } - $self->update_idle_time; $self; } sub args_ok ($$) { my ($cb, $argc) = @_; my $tot = prototype $cb; - my ($nreq, undef) = split(';', $tot); + my ($nreq, undef) = split(/;/, $tot); $nreq = ($nreq =~ tr/$//) - 1; $tot = ($tot =~ tr/$//) - 1; ($argc <= $tot && $argc >= $nreq); @@ -82,8 +82,8 @@ sub process_line ($$) { my ($self, $l) = @_; my ($req, @args) = split(/[ \t]+/, $l); return 1 unless defined($req); # skip blank line - $req = $self->can('cmd_'.lc($req)); - return res($self, '500 command not recognized') unless $req; + $req = $self->can('cmd_'.lc($req)) // + return $self->write(\"500 command not recognized\r\n"); return res($self, r501) unless args_ok($req, scalar @args); my $res = eval { $req->($self, @args) }; @@ -117,7 +117,7 @@ sub cmd_slave ($) { '202 slave status noted' } sub cmd_xgtitle ($;$) { my ($self, $wildmat) = @_; - more($self, '282 list of groups and descriptions follows'); + $self->msg_more("282 list of groups and descriptions follows\r\n"); list_newsgroups($self, $wildmat); } @@ -150,7 +150,7 @@ sub list_active_times_i { for my $ngname (@window) { my $ibx = $groups->{$ngname} or next; my $c = eval { $ibx->uidvalidity } // time; - more($self, "$ngname $c <$ibx->{-primary_address}>"); + $self->msg_more("$ngname $c <$ibx->{-primary_address}>\r\n"); } scalar(@$groupnames); # continue if there's more } @@ -169,7 +169,7 @@ sub list_newsgroups_i { my $ibx; for my $ngname (@window) { $ibx = $groups->{$ngname} and - more($self, "$ngname ".$ibx->description); + $self->msg_more("$ngname ".$ibx->description."\r\n"); } scalar(@$groupnames); # continue if there's more } @@ -191,10 +191,10 @@ sub cmd_list ($;$$) { $arg = "list_$arg"; $arg = $self->can($arg); return r501 unless $arg && args_ok($arg, scalar @args); - more($self, '215 information follows'); + $self->msg_more("215 information follows\r\n"); $arg->($self, @args); } else { - more($self, '215 list of newsgroups follows'); + $self->msg_more("215 list of newsgroups follows\r\n"); long_response($self, \&list_active_i, [ # copy array @{$self->{nntpd}->{groupnames}} ]); } @@ -202,7 +202,7 @@ sub cmd_list ($;$$) { sub listgroup_range_i { my ($self, $beg, $end) = @_; - my $r = $self->{ibx}->mm->msg_range($beg, $end, 'num'); + my $r = $self->{ibx}->mm(1)->msg_range($beg, $end, 'num'); scalar(@$r) or return; $self->msg_more(join('', map { "$_->[0]\r\n" } @$r)); 1; @@ -210,9 +210,9 @@ sub listgroup_range_i { sub listgroup_all_i { my ($self, $num) = @_; - my $ary = $self->{ibx}->mm->ids_after($num); + my $ary = $self->{ibx}->over(1)->ids_after($num); scalar(@$ary) or return; - more($self, join("\r\n", @$ary)); + $self->msg_more(join("\r\n", @$ary, '')); 1; } @@ -221,7 +221,7 @@ sub cmd_listgroup ($;$$) { if (defined $group) { my $res = cmd_group($self, $group); return $res if ($res !~ /\A211 /); - more($self, $res); + $self->msg_more($res .= "\r\n"); } $self->{ibx} or return '412 no newsgroup selected'; if (defined $range) { @@ -241,7 +241,7 @@ sub parse_time ($$;$) { $gmt = 1; } my ($YYYY, $MM, $DD); - if (bytes::length($date) == 8) { # RFC 3977 allows YYYYMMDD + if (length($date) == 8) { # RFC 3977 allows YYYYMMDD ($YYYY, $MM, $DD) = unpack('A4A2A2', $date); } else { # legacy clients send YYMMDD my $YY; @@ -261,8 +261,8 @@ sub parse_time ($$;$) { sub group_line ($$) { my ($self, $ibx) = @_; - my ($min, $max) = $ibx->mm->minmax; - more($self, "$ibx->{newsgroup} $max $min n"); + my ($min, $max) = $ibx->mm(1)->minmax; + $self->msg_more("$ibx->{newsgroup} $max $min n\r\n"); } sub newgroups_i { @@ -284,7 +284,7 @@ sub cmd_newgroups ($$$;$$) { return r501 if $@; # TODO dists - more($self, '231 list of new newsgroups follows'); + $self->msg_more("231 list of new newsgroups follows\r\n"); long_response($self, \&newgroups_i, $ts, \(my $i = 0), $self->{nntpd}->{groupnames}); } @@ -348,8 +348,8 @@ sub cmd_newnews ($$$$;$$) { my ($self, $newsgroups, $date, $time, $gmt, $dists) = @_; my $ts = eval { parse_time($date, $time, $gmt) }; return r501 if $@; - more($self, '230 list of new articles by message-id follows'); - my ($keep, $skip) = split('!', $newsgroups, 2); + $self->msg_more("230 list of new articles by message-id follows\r\n"); + my ($keep, $skip) = split(/!/, $newsgroups, 2); ngpat2re($keep); ngpat2re($skip); my @names = grep(!/$skip/, grep(/$keep/, @@ -367,7 +367,7 @@ sub cmd_group ($$) { $nntpd->idler_start; $self->{ibx} = $ibx; - my ($min, $max) = $ibx->mm->minmax; + my ($min, $max) = $ibx->mm(1)->minmax; $self->{article} = $min; my $est_size = $max - $min; "211 $est_size $min $max $group"; @@ -381,11 +381,10 @@ sub article_adj ($$) { defined $n or return '420 no current article has been selected'; $n += $off; - my $mid = $ibx->mm->mid_for($n); - unless ($mid) { + my $mid = $ibx->mm(1)->mid_for($n) // do { $n = $off > 0 ? 'next' : 'previous'; return "421 no $n article in this group"; - } + }; $self->{article} = $n; "223 $n <$mid> article retrieved - request text separately"; } @@ -404,20 +403,11 @@ sub cmd_post ($) { sub cmd_quit ($) { my ($self) = @_; - res($self, '205 closing connection - goodbye!'); + $self->write(\"205 closing connection - goodbye!\r\n"); $self->shutdn; undef; } -sub header_append ($$$) { - my ($hdr, $k, $v) = @_; - my @v = $hdr->header_raw($k); - foreach (@v) { - return if $v eq $_; - } - $hdr->header_set($k, @v, $v); -} - sub xref_by_tc ($$$) { my ($xref, $pi_cfg, $smsg) = @_; my $by_addr = $pi_cfg->{-by_addr}; @@ -427,9 +417,8 @@ sub xref_by_tc ($$$) { $by_addr->{lc($_)} // () } (PublicInbox::Address::emails($smsg->{$f} // '')); for my $ibx (@ibxs) { - my $ngname = $ibx->{newsgroup} // next; - next if defined $xref->{$ngname}; - $xref->{$ngname} = eval { $ibx->mm->num_for($mid) }; + $xref->{$ibx->{newsgroup}} //= + $ibx->mm(1)->num_for($mid); } } } @@ -446,13 +435,15 @@ sub xref ($$$) { $xref = { $cur_ng => $smsg->{num} }; my $mid = $smsg->{mid}; for my $ibx (values %{$nntpd->{pi_cfg}->{-by_newsgroup}}) { - next if defined($xref->{$ibx->{newsgroup}}); - my $num = eval { $ibx->mm->num_for($mid) } // next; - $xref->{$ibx->{newsgroup}} = $num; + $xref->{$ibx->{newsgroup}} //= + $ibx->mm(1)->num_for($mid); } } my $ret = "$nntpd->{servername} $cur_ng:".delete($xref->{$cur_ng}); - $ret .= " $_:$xref->{$_}" for (sort keys %$xref); + for my $ng (sort keys %$xref) { + my $num = $xref->{$ng} // next; + $ret .= " $ng:$num"; + } $ret; } @@ -487,13 +478,6 @@ sub set_nntp_headers ($$) { # *something* here is required for leafnode, try to follow # RFC 5536 3.1.5... $hdr->header_set('Path', $server_name . '!not-for-mail'); - - header_append($hdr, 'List-Post', "{-primary_address}>"); - if (my $url = $ibx->base_url) { - $mid = mid_escape($mid); - header_append($hdr, 'Archived-At', "<$url$mid/>"); - header_append($hdr, 'List-Archive', "<$url>"); - } } sub art_lookup ($$$) { @@ -520,7 +504,7 @@ find_ibx: return '412 no newsgroup has been selected'; } found: - my $smsg = $ibx->over->get_art($n) or return $err; + my $smsg = $ibx->over(1)->get_art($n) or return $err; $smsg->{-ibx} = $ibx; if ($code == 223) { # STAT set_art($self, $n); @@ -531,7 +515,7 @@ found: $smsg->{nntp_code} = $code; set_art($self, $art); # this dereferences to `undef' - ${git_async_cat($ibx->git, $smsg->{blob}, \&blob_cb, $smsg)}; + ${ibx_async_cat($ibx, $smsg->{blob}, \&blob_cb, $smsg)}; } } @@ -565,7 +549,7 @@ sub msg_hdr_write ($$) { $smsg->{nntp}->msg_more($$hdr); } -sub blob_cb { # called by git->cat_async via git_async_cat +sub blob_cb { # called by git->cat_async via ibx_async_cat my ($bref, $oid, $type, $size, $smsg) = @_; my $self = $smsg->{nntp}; my $code = $smsg->{nntp_code}; @@ -581,15 +565,15 @@ sub blob_cb { # called by git->cat_async via git_async_cat my $r = "$code $smsg->{num} <$smsg->{mid}> article retrieved - "; my $eml = PublicInbox::Eml->new($bref); if ($code == 220) { - more($self, $r .= 'head and body follow'); + $self->msg_more($r .= "head and body follow\r\n"); msg_hdr_write($eml, $smsg); $self->msg_more("\r\n"); msg_body_write($self, $bref); } elsif ($code == 221) { - more($self, $r .= 'head follows'); + $self->msg_more($r .= "head follows\r\n"); msg_hdr_write($eml, $smsg); } elsif ($code == 222) { - more($self, $r .= 'body follows'); + $self->msg_more($r .= "body follows\r\n"); msg_body_write($self, $bref); } else { $self->close; @@ -625,7 +609,7 @@ sub cmd_date ($) { '111 '.strftime('%Y%m%d%H%M%S', gmtime(time)) } sub cmd_help ($) { my ($self) = @_; - more($self, '100 help text follows'); + $self->msg_more("100 help text follows\r\n"); '.' } @@ -634,7 +618,7 @@ sub get_range ($$) { my $ibx = $self->{ibx} or return '412 no news group has been selected'; defined $range or return '420 No article(s) selected'; my ($beg, $end); - my ($min, $max) = $ibx->mm->minmax; + my ($min, $max) = $ibx->mm(1)->minmax; if ($range =~ /\A([0-9]+)\z/) { $beg = $end = $1; } elsif ($range =~ /\A([0-9]+)-\z/) { @@ -666,8 +650,6 @@ sub long_step { out($self, " deferred[$fd] aborted - %0.6f", $elapsed); $self->close; } elsif ($more) { # $self->{wbuf}: - $self->update_idle_time; - # COMPRESS users all share the same DEFLATE context. # Flush it here to ensure clients don't see # each other's data @@ -681,7 +663,7 @@ sub long_step { $self->requeue if $new_size == 1; } else { # all done! delete $self->{long_cb}; - res($self, '.'); + $self->write(\".\r\n"); my $elapsed = now() - $t0; my $fd = fileno($self->{sock}); out($self, " deferred[$fd] done - %0.6f", $elapsed); @@ -704,7 +686,7 @@ sub long_response ($$;@) { sub hdr_msgid_range_i { my ($self, $beg, $end) = @_; - my $r = $self->{ibx}->mm->msg_range($beg, $end); + my $r = $self->{ibx}->mm(1)->msg_range($beg, $end); @$r or return; $self->msg_more(join('', map { "$_->[0] <$_->[1]>\r\n" } @$r)); 1; @@ -721,7 +703,7 @@ sub hdr_message_id ($$$) { # optimize XHDR Message-ID [range] for slrnpull. $range = $self->{article} unless defined $range; my $r = get_range($self, $range); return $r unless ref $r; - more($self, $xhdr ? r221 : r225); + $self->msg_more($xhdr ? r221 : r225); long_response($self, \&hdr_msgid_range_i, @$r); } } @@ -730,7 +712,7 @@ sub mid_lookup ($$) { my ($self, $mid) = @_; my $cur_ibx = $self->{ibx}; if ($cur_ibx) { - my $n = $cur_ibx->mm->num_for($mid); + my $n = $cur_ibx->mm(1)->num_for($mid); return ($cur_ibx, $n) if defined $n; } my $pi_cfg = $self->{nntpd}->{pi_cfg}; @@ -760,7 +742,7 @@ EOF } else { # slow path for non-ALL users for my $ibx (values %{$pi_cfg->{-by_newsgroup}}) { next if defined $cur_ibx && $ibx eq $cur_ibx; - my $n = $ibx->mm->num_for($mid); + my $n = $ibx->mm(1)->num_for($mid); return ($ibx, $n) if defined $n; } } @@ -770,7 +752,7 @@ EOF sub xref_range_i { my ($self, $beg, $end) = @_; my $ibx = $self->{ibx}; - my $msgs = $ibx->over->query_xover($$beg, $end); + my $msgs = $ibx->over(1)->query_xover($$beg, $end); scalar(@$msgs) or return; $$beg = $msgs->[-1]->{num} + 1; $self->msg_more(join('', map { @@ -786,29 +768,28 @@ sub hdr_xref ($$$) { # optimize XHDR Xref [range] for rtin my $mid = $1; my ($ibx, $n) = mid_lookup($self, $mid); return r430 unless $n; - my $smsg = $ibx->over->get_art($n) or return; + my $smsg = $ibx->over(1)->get_art($n) or return; hdr_mid_response($self, $xhdr, $ibx, $n, $range, xref($self, $ibx, $smsg)); } else { # numeric range $range = $self->{article} unless defined $range; my $r = get_range($self, $range); return $r unless ref $r; - more($self, $xhdr ? r221 : r225); + $self->msg_more($xhdr ? r221 : r225); long_response($self, \&xref_range_i, @$r); } } sub over_header_for { - my ($over, $num, $field) = @_; - my $smsg = $over->get_art($num) or return; + my ($ibx, $num, $field) = @_; + my $smsg = $ibx->over(1)->get_art($num) or return; return PublicInbox::Smsg::date($smsg) if $field eq 'date'; $smsg->{$field}; } sub smsg_range_i { my ($self, $beg, $end, $field) = @_; - my $over = $self->{ibx}->over; - my $msgs = $over->query_xover($$beg, $end); + my $msgs = $self->{ibx}->over(1)->query_xover($$beg, $end); scalar(@$msgs) or return; my $tmp = ''; @@ -832,13 +813,13 @@ sub hdr_smsg ($$$$) { if (defined $range && $range =~ $ONE_MSGID) { my ($ibx, $n) = mid_lookup($self, $1); return r430 unless defined $n; - my $v = over_header_for($ibx->over, $n, $field); + my $v = over_header_for($ibx, $n, $field); hdr_mid_response($self, $xhdr, $ibx, $n, $range, $v); } else { # numeric range $range = $self->{article} unless defined $range; my $r = get_range($self, $range); return $r unless ref $r; - more($self, $xhdr ? r221 : r225); + $self->msg_more($xhdr ? r221 : r225); long_response($self, \&smsg_range_i, @$r, $field); } } @@ -856,7 +837,7 @@ sub do_hdr ($$$;$) { } elsif ($sub =~ /\A:(bytes|lines)\z/) { hdr_smsg($self, $xhdr, $1, $range); } else { - $xhdr ? (r221 . "\r\n.") : "503 HDR not permitted on $header"; + $xhdr ? (r221 . '.') : "503 HDR not permitted on $header"; } } @@ -886,23 +867,16 @@ sub hdr_mid_prefix ($$$$$) { sub hdr_mid_response ($$$$$$) { my ($self, $xhdr, $ibx, $n, $mid, $v) = @_; - my $res = ''; - if ($xhdr) { - $res .= r221 . "\r\n"; - $res .= "$mid $v\r\n"; - } else { - $res .= r225 . "\r\n"; - my $pfx = hdr_mid_prefix($self, $xhdr, $ibx, $n, $mid); - $res .= "$pfx $v\r\n"; - } - res($self, $res .= '.'); + $self->write(($xhdr ? r221.$mid : + r225.hdr_mid_prefix($self, $xhdr, $ibx, $n, $mid)) . + " $v\r\n.\r\n"); undef; } sub xrover_i { my ($self, $beg, $end) = @_; - my $h = over_header_for($self->{ibx}->over, $$beg, 'references'); - more($self, "$$beg $h") if defined($h); + my $h = over_header_for($self->{ibx}, $$beg, 'references'); + $self->msg_more("$$beg $h\r\n") if defined($h); $$beg++ < $end; } @@ -915,7 +889,7 @@ sub cmd_xrover ($;$) { $range = $self->{article} unless defined $range; my $r = get_range($self, $range); return $r unless ref $r; - more($self, '224 Overview information follows'); + $self->msg_more("224 Overview information follows\r\n"); long_response($self, \&xrover_i, @$r); } @@ -941,8 +915,9 @@ sub cmd_over ($;$) { if ($range && $range =~ $ONE_MSGID) { my ($ibx, $n) = mid_lookup($self, $1); defined $n or return r430; - my $smsg = $ibx->over->get_art($n) or return r430; - more($self, '224 Overview information follows (multi-line)'); + my $smsg = $ibx->over(1)->get_art($n) or return r430; + $self->msg_more( + "224 Overview information follows (multi-line)\r\n"); # Only set article number column if it's the current group # (RFC 3977 8.3.2) @@ -962,7 +937,7 @@ sub cmd_over ($;$) { sub xover_i { my ($self, $beg, $end) = @_; my $ibx = $self->{ibx}; - my $msgs = $ibx->over->query_xover($$beg, $end); + my $msgs = $ibx->over(1)->query_xover($$beg, $end); my $nr = scalar @$msgs or return; # OVERVIEW.FMT @@ -978,7 +953,8 @@ sub cmd_xover ($;$) { my $r = get_range($self, $range); return $r unless ref $r; my ($beg, $end) = @$r; - more($self, "224 Overview information follows for $$beg to $end"); + $self->msg_more( + "224 Overview information follows for $$beg to $end\r\n"); long_response($self, \&xover_i, @$r); } @@ -991,7 +967,7 @@ sub cmd_starttls ($) { return r502 if ($sock->can('accept_SSL') || $self->compressed); my $opt = $self->{nntpd}->{accept_tls} or return '580 can not initiate TLS negotiation'; - res($self, '382 Continue with TLS negotiation'); + $self->write(\"382 Continue with TLS negotiation\r\n"); $self->{sock} = IO::Socket::SSL->start_SSL($sock, %$opt); $self->requeue if PublicInbox::DS::accept_tls_step($self); undef; @@ -1030,7 +1006,7 @@ sub cmd_xpath ($$) { } } else { # slow path, no point in using long_response for my $ibx (values %$groups) { - my $n = $ibx->mm->num_for($mid) // next; + my $n = $ibx->mm(1)->num_for($mid) // next; push @paths, "$ibx->{newsgroup}/$n"; } } @@ -1040,8 +1016,6 @@ sub cmd_xpath ($$) { sub res ($$) { do_write($_[0], $_[1] . "\r\n") } -sub more ($$) { $_[0]->msg_more($_[1] . "\r\n") } - sub do_write ($$) { my $self = $_[0]; my $done = $self->write(\($_[1])); @@ -1066,7 +1040,6 @@ sub event_step { return unless $self->flush_write && $self->{sock} && !$self->{long_cb}; - $self->update_idle_time; # only read more requests if we've drained the write buffer, # otherwise we can be buffering infinitely w/o backpressure @@ -1088,17 +1061,15 @@ sub event_step { out($self, "[$fd] %s - %0.6f$pending", $line, now() - $t0); return $self->close if $r < 0; $self->rbuf_idle($rbuf); - $self->update_idle_time; # maybe there's more pipelined data, or we'll have # to register it for socket-readiness notifications $self->requeue unless $pending; } -# for graceful shutdown in PublicInbox::Daemon: -sub busy { - my ($self, $now) = @_; - ($self->{rbuf} || $self->{wbuf} || $self->not_idle_long($now)); +sub busy { # for graceful shutdown in PublicInbox::Daemon: + my ($self) = @_; + defined($self->{rbuf}) || defined($self->{wbuf}) } 1;