qw(:bytes :lines Xref To Cc)) . "\r\n";
# disable commands with easy DoS potential:
-# LISTGROUP could get pretty bad, too...
my %DISABLED; # = map { $_ => 1 } qw(xover list_overview_fmt newnews xhdr);
my $EXPMAP; # fd -> [ idle_time, $self ]
sub process_line ($$) {
my ($self, $l) = @_;
my ($req, @args) = split(/\s+/, $l);
+ return unless defined($req);
$req = lc($req);
$req = eval {
no strict 'refs';
}
$self->{ng} or return '412 no newsgroup selected';
- long_response($self, 0, long_response_limit, sub {
- my ($i) = @_;
- my $nr = $self->{ng}->mm->id_batch($$i, sub {
- my ($ary) = @_;
- more($self, join("\r\n", @$ary));
- });
-
- # -1 to adjust for implicit increment in long_response
- $$i = $nr ? $$i + $nr - 1 : long_response_limit;
+ my $n = 0;
+ long_response($self, sub {
+ my $ary = $self->{ng}->mm->ids_after(\$n);
+ scalar @$ary or return;
+ more($self, join("\r\n", @$ary));
+ 1;
});
}
return '.' unless @srch;
my $prev = 0;
- long_response($self, 0, long_response_limit, sub {
- my ($i) = @_;
+ long_response($self, sub {
my $srch = $srch[0];
my $msgs = $srch->query_ts($ts, $prev);
if (scalar @$msgs) {
shift @srch;
if (@srch) { # continue onto next newsgroup
$prev = 0;
+ return 1;
} else { # break out of the long response.
- $$i = long_response_limit;
+ return;
}
}
});
$hdr->header_set($k, @v, $v);
}
-sub set_nntp_headers {
- my ($hdr, $ng, $n, $mid) = @_;
+sub xref ($$$$) {
+ my ($self, $ng, $n, $mid) = @_;
+ my $ret = "$ng->{domain} $ng->{newsgroup}:$n";
+
+ # num_for is pretty cheap and sometimes we'll lookup the existence
+ # of an article without getting even the OVER info. In other words,
+ # I'm not sure if its worth optimizing by scanning To:/Cc: and
+ # PublicInbox::ExtMsg on the PSGI end is just as expensive
+ foreach my $other (@{$self->{nntpd}->{grouplist}}) {
+ next if $ng eq $other;
+ my $num = eval { $other->mm->num_for($mid) } or next;
+ $ret .= " $other->{newsgroup}:$num";
+ }
+ $ret;
+}
+
+sub set_nntp_headers ($$$$$) {
+ my ($self, $hdr, $ng, $n, $mid) = @_;
# clobber some
- $hdr->header_set('Newsgroups', $ng->{newsgroup});
- $hdr->header_set('Xref', xref($ng, $n));
+ my $xref = xref($self, $ng, $n, $mid);
+ $hdr->header_set('Xref', $xref);
+ $xref =~ s/:\d+//g;
+ $hdr->header_set('Newsgroups', (split(/ /, $xref, 2))[1]);
header_append($hdr, 'List-Post', "<mailto:$ng->{-primary_address}>");
if (my $url = $ng->base_url) {
$mid = mid_escape($mid);
my $msg = $ng->msg_by_smsg($smsg) or return $err;
my $s = Email::Simple->new($msg);
if ($set_headers) {
- set_nntp_headers($s->header_obj, $ng, $n, $mid);
+ set_nntp_headers($self, $s->header_obj, $ng, $n, $mid);
# must be last
$s->body_set('') if ($set_headers == 2);
[ $beg, $end ];
}
-sub long_response ($$$$) {
- my ($self, $beg, $end, $cb) = @_;
+sub long_response ($$) {
+ my ($self, $cb) = @_;
die "BUG: nested long response" if $self->{long_res};
my $fd = $self->{fd};
$self->watch_read(0);
my $t0 = now();
$self->{long_res} = sub {
- # limit our own running time for fairness with other
- # clients and to avoid buffering too much:
- my $lim = $end == long_response_limit ? 1 : 100;
-
- my $err;
- do {
- eval { $cb->(\$beg) };
- } until (($err = $@) || $self->{closed} ||
- ++$beg > $end || !--$lim || $self->{write_buf_size});
-
- if ($err || $self->{closed}) {
+ my $more = eval { $cb->() };
+ if ($@ || $self->{closed}) {
$self->{long_res} = undef;
- if ($err) {
+ if ($@) {
err($self,
"%s during long response[$fd] - %0.6f",
- $err, now() - $t0);
+ $@, now() - $t0);
}
if ($self->{closed}) {
out($self, " deferred[$fd] aborted - %0.6f",
update_idle_time($self);
$self->watch_read(1);
}
- } elsif (!$lim || $self->{write_buf_size}) {
+ } elsif ($more) { # $self->{write_buf_size}:
# no recursion, schedule another call ASAP
# but only after all pending writes are done
update_idle_time($self);
my $mm = $self->{ng}->mm;
my ($beg, $end) = @$r;
more($self, $xhdr ? r221 : r225);
- long_response($self, $beg, $end, sub {
- my ($i) = @_;
- my $mid = $mm->mid_for($$i);
- more($self, "$$i <$mid>") if defined $mid;
+ long_response($self, sub {
+ my $r = $mm->msg_range(\$beg, $end);
+ @$r or return;
+ more($self, join("\r\n", map {
+ "$_->[0] <$_->[1]>"
+ } @$r));
+ 1;
});
}
}
-sub xref ($$) {
- my ($ng, $n) = @_;
- "$ng->{domain} $ng->{newsgroup}:$n"
-}
-
sub mid_lookup ($$) {
my ($self, $mid) = @_;
my $self_ng = $self->{ng};
my ($self, $xhdr, $range) = @_;
if (defined $range && $range =~ /\A<(.+)>\z/) { # Message-ID
- my ($ng, $n) = mid_lookup($self, $1);
+ my $mid = $1;
+ my ($ng, $n) = mid_lookup($self, $mid);
return r430 unless $n;
- hdr_mid_response($self, $xhdr, $ng, $n, $range, xref($ng, $n));
+ hdr_mid_response($self, $xhdr, $ng, $n, $range,
+ xref($self, $ng, $n, $mid));
} else { # numeric range
$range = $self->{article} unless defined $range;
my $r = get_range($self, $range);
my $mm = $ng->mm;
my ($beg, $end) = @$r;
more($self, $xhdr ? r221 : r225);
- long_response($self, $beg, $end, sub {
- my ($i) = @_;
- my $mid = $mm->mid_for($$i);
- more($self, "$$i ".xref($ng, $$i)) if defined $mid;
+ long_response($self, sub {
+ my $r = $mm->msg_range(\$beg, $end);
+ @$r or return;
+ more($self, join("\r\n", map {
+ my $num = $_->[0];
+ "$num ".xref($self, $ng, $num, $_->[1]);
+ } @$r));
+ 1;
});
}
}
my ($beg, $end) = @$r;
more($self, $xhdr ? r221 : r225);
my $cur = $beg;
- long_response($self, 0, long_response_limit, sub {
- my ($i) = @_;
+ long_response($self, sub {
my $msgs = $srch->query_xover($cur, $end);
- my $nr = scalar @$msgs or
- return ($$i = long_response_limit);
+ my $nr = scalar @$msgs or return;
my $tmp = '';
foreach my $s (@$msgs) {
- $tmp .= $s->num . ' ' . $s->$field . "\r\n";
+ $tmp .= $s->{num} . ' ' . $s->$field . "\r\n";
}
utf8::encode($tmp);
do_more($self, $tmp);
my $mm = $ng->mm;
my $srch = $ng->search;
more($self, '224 Overview information follows');
- long_response($self, $beg, $end, sub {
- my ($i) = @_;
- my $num = $$i;
- my $h = search_header_for($srch, $num, 'references');
- defined $h or return;
- more($self, "$num $h");
+
+ long_response($self, sub {
+ my $h = search_header_for($srch, $beg, 'references');
+ more($self, "$beg $h") if defined($h);
+ $beg++ < $end;
});
}
more($self, "224 Overview information follows for $beg to $end");
my $srch = $self->{ng}->search;
my $cur = $beg;
- long_response($self, 0, long_response_limit, sub {
- my ($i) = @_;
+ long_response($self, sub {
my $msgs = $srch->query_xover($cur, $end);
- my $nr = scalar @$msgs or return ($$i = long_response_limit);
+ my $nr = scalar @$msgs or return;
# OVERVIEW.FMT
more($self, join("\r\n", map {
over_line($_->{num}, $_);
} @$msgs));
$cur = $msgs->[-1]->{num} + 1;
- 1;
});
}
sub event_read {
my ($self) = @_;
use constant LINE_MAX => 512; # RFC 977 section 2.3
- my $r = 1;
- my $buf = $self->read(LINE_MAX) or return $self->close;
- $self->{rbuf} .= $$buf;
- while ($r > 0 && $self->{rbuf} =~ s/\A\s*([^\r\n]+)\r?\n//) {
+ if (index($self->{rbuf}, "\n") < 0) {
+ my $buf = $self->read(LINE_MAX) or return $self->close;
+ $self->{rbuf} .= $$buf;
+ }
+ my $r = 1;
+ while ($r > 0 && $self->{rbuf} =~ s/\A\s*([^\r\n]*)\r?\n//) {
my $line = $1;
return $self->close if $line =~ /[[:cntrl:]]/s;
my $t0 = now();
sub watch_read {
my ($self, $bool) = @_;
my $rv = $self->SUPER::watch_read($bool);
- if ($bool && $self->{rbuf} ne '') {
+ if ($bool && index($self->{rbuf}, "\n") >= 0) {
# Force another read if there is a pipelined request.
# We don't know if the socket has anything for us to read,
# and we must double-check again by the time the timer fires