use PublicInbox::Search;
use PublicInbox::Msgmap;
use PublicInbox::Git;
-use PublicInbox::MID qw(mid2path);
-use Email::MIME;
-use Data::Dumper qw(Dumper);
+require PublicInbox::EvCleanup;
+use Email::Simple;
use POSIX qw(strftime);
use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
+use URI::Escape qw(uri_escape_utf8);
use constant {
r501 => '501 command syntax error',
r221 => '221 Header follows',
my %DISABLED; # = map { $_ => 1 } qw(xover list_overview_fmt newnews xhdr);
my $EXPMAP; # fd -> [ idle_time, $self ]
-my $EXPTIMER;
+my $expt;
our $EXPTIME = 180; # 3 minutes
my $WEAKEN = {}; # string(nntpd) -> nntpd
-my $WEAKTIMER;
+my $weakt;
+my $nextt;
-my $next_tick;
my $nextq = [];
sub next_tick () {
- $next_tick = undef;
+ $nextt = undef;
my $q = $nextq;
$nextq = [];
foreach my $nntp (@$q) {
# reduce FD pressure by closing some "git cat-file --batch" processes
# and unused FDs for msgmap and Xapian indices
sub weaken_groups () {
- $WEAKTIMER = undef;
+ $weakt = undef;
foreach my $nntpd (values %$WEAKEN) {
$_->weaken_all foreach (@{$nntpd->{grouplist}});
}
my $now = now();
my $exp = $EXPTIME;
my $old = $now - $exp;
- my $next = $now + $exp;
my $nr = 0;
my %new;
while (my ($fd, $v) = each %$EXPMAP) {
if ($idle_time < $old) {
$nntp->close; # idempotent
} else {
- my $nexp = $idle_time + $exp;
- $next = $nexp if ($nexp < $next);
++$nr;
$new{$fd} = $v;
}
}
$EXPMAP = \%new;
if ($nr) {
- $next -= $now;
- $next = 0 if $next < 0;
- $EXPTIMER = Danga::Socket->AddTimer($next, *expire_old);
+ $expt = PublicInbox::EvCleanup::later(*expire_old);
weaken_groups();
} else {
- $EXPTIMER = undef;
+ $expt = undef;
# noop to kick outselves out of the loop ASAP so descriptors
# really get closed
- Danga::Socket->AddTimer(0, sub {});
+ PublicInbox::EvCleanup::asap(sub {});
# grace period for reaping resources
- $WEAKTIMER ||= Danga::Socket->AddTimer(30, *weaken_groups);
+ $weakt ||= PublicInbox::EvCleanup::later(*weaken_groups);
}
}
sub new ($$$) {
my ($class, $sock, $nntpd) = @_;
my $self = fields::new($class);
- binmode $sock, ':utf8'; # RFC 3977
$self->SUPER::new($sock);
$self->{nntpd} = $nntpd;
res($self, '201 server ready - post via email');
$self->watch_read(1);
update_idle_time($self);
$WEAKEN->{"$nntpd"} = $nntpd;
- $EXPTIMER ||= Danga::Socket->AddTimer($EXPTIME, *expire_old);
+ $expt ||= PublicInbox::EvCleanup::later(*expire_old);
$self;
}
my $res = eval { $req->($self, @args) };
my $err = $@;
if ($err && !$self->{closed}) {
- chomp($l = Dumper(\$l));
+ chomp($l);
err($self, 'error from: %s (%s)', $l, $err);
$res = '503 program fault - command not performed';
}
my ($self, $wildmat) = @_;
wildmat2re($wildmat);
foreach my $ng (@{$self->{nntpd}->{grouplist}}) {
- $ng->{name} =~ $wildmat or next;
+ $ng->{newsgroup} =~ $wildmat or next;
group_line($self, $ng);
}
}
my ($self, $wildmat) = @_;
wildmat2re($wildmat);
foreach my $ng (@{$self->{nntpd}->{grouplist}}) {
- $ng->{name} =~ $wildmat or next;
+ $ng->{newsgroup} =~ $wildmat or next;
my $c = eval { $ng->mm->created_at } || time;
- more($self, "$ng->{name} $c $ng->{address}");
+ more($self, "$ng->{newsgroup} $c $ng->{-primary_address}");
}
}
my ($self, $wildmat) = @_;
wildmat2re($wildmat);
foreach my $ng (@{$self->{nntpd}->{grouplist}}) {
- $ng->{name} =~ $wildmat or next;
+ $ng->{newsgroup} =~ $wildmat or next;
my $d = $ng->description;
- more($self, "$ng->{name} $d");
+ more($self, "$ng->{newsgroup} $d");
}
}
sub group_line ($$) {
my ($self, $ng) = @_;
my ($min, $max) = $ng->mm->minmax;
- more($self, "$ng->{name} $max $min n") if defined $min && defined $max;
+ more($self, "$ng->{newsgroup} $max $min n") if defined $min && defined $max;
}
sub cmd_newgroups ($$$;$$) {
ngpat2re($skip);
my @srch;
foreach my $ng (@{$self->{nntpd}->{grouplist}}) {
- $ng->{name} =~ $keep or next;
- $ng->{name} =~ $skip and next;
+ $ng->{newsgroup} =~ $keep or next;
+ $ng->{newsgroup} =~ $skip and next;
my $srch = $ng->search or next;
push @srch, $srch;
};
sub cmd_post ($) {
my ($self) = @_;
my $ng = $self->{ng};
- $ng ? "440 mailto:$ng->{address} to post" : '440 posting not allowed'
+ $ng ? "440 mailto:$ng->{-primary_address} to post"
+ : '440 posting not allowed'
}
sub cmd_quit ($) {
undef;
}
+sub header_append ($$$) {
+ my ($hdr, $k, $v) = @_;
+ my @v = $hdr->header($k);
+ foreach (@v) {
+ return if $v eq $_;
+ }
+ $hdr->header_set($k, @v, $v);
+}
+
+sub set_nntp_headers {
+ my ($hdr, $ng, $n, $mid) = @_;
+
+ # clobber some
+ $hdr->header_set('Newsgroups', $ng->{newsgroup});
+ $hdr->header_set('Xref', xref($ng, $n));
+ header_append($hdr, 'List-Post', "<mailto:$ng->{-primary_address}>");
+ if (my $url = $ng->base_url) {
+ $mid = uri_escape_utf8($mid);
+ header_append($hdr, 'Archived-At', "<$url$mid/>");
+ header_append($hdr, 'List-Archive', "<$url>");
+ }
+}
+
sub art_lookup ($$$) {
my ($self, $art, $set_headers) = @_;
my $ng = $self->{ng};
defined $mid or return $err;
}
found:
- my $o = 'HEAD:' . mid2path($mid);
my $bytes;
- my $s = eval { Email::MIME->new($ng->gcf->cat_file($o, \$bytes)) };
- return $err unless $s;
+ my $s = eval { $ng->msg_by_mid($mid, \$bytes) } or return $err;
+ $s = Email::Simple->new($s);
my $lines;
if ($set_headers) {
- $s->header_set('Newsgroups', $ng->{name});
- $s->header_set('Xref', xref($ng, $n));
+ set_nntp_headers($s->header_obj, $ng, $n, $mid);
$lines = $s->body =~ tr!\n!\n!;
# must be last
$self->{article} = $art if defined $art && $art =~ /\A\d+\z/;
}
+sub _header ($) {
+ my $hdr = $_[0]->header_obj->as_string;
+ utf8::encode($hdr);
+ $hdr
+}
+
sub cmd_article ($;$) {
my ($self, $art) = @_;
my $r = art_lookup($self, $art, 1);
my ($n, $mid, $s) = @$r;
set_art($self, $art);
more($self, "220 $n <$mid> article retrieved - head and body follow");
- do_more($self, $s->header_obj->as_string);
+ do_more($self, _header($s));
do_more($self, "\r\n");
simple_body_write($self, $s);
}
my ($n, $mid, $s) = @$r;
set_art($self, $art);
more($self, "221 $n <$mid> article retrieved - head follows");
- do_more($self, $s->header_obj->as_string);
+ do_more($self, _header($s));
'.'
}
update_idle_time($self);
push @$nextq, $self;
- $next_tick ||= Danga::Socket->AddTimer(0, *next_tick);
+ $nextt ||= PublicInbox::EvCleanup::asap(*next_tick);
} else { # all done!
$self->{long_res} = undef;
$self->watch_read(1);
sub xref ($$) {
my ($ng, $n) = @_;
- "$ng->{domain} $ng->{name}:$n"
+ "$ng->{domain} $ng->{newsgroup}:$n"
}
sub mid_lookup ($$) {
sub search_header_for {
my ($srch, $mid, $field) = @_;
- my $smsg = $srch->lookup_message($mid) or return;
- $smsg = PublicInbox::SearchMsg->load_doc($smsg->{doc});
+ my $smsg = $srch->lookup_mail($mid) or return;
$smsg->$field;
}
foreach my $s (@$msgs) {
$tmp .= $s->num . ' ' . $s->$field . "\r\n";
}
+ utf8::encode($tmp);
do_more($self, $tmp);
# -1 to adjust for implicit increment in long_response
$$i = $nr ? $$i + $nr - 1 : long_response_limit;
my ($num, $smsg) = @_;
# n.b. field access and procedural calls can be
# 10%-15% faster than OO method calls:
- join("\t", $num,
+ my $s = join("\t", $num,
$smsg->{subject},
$smsg->{from},
PublicInbox::SearchMsg::date($smsg),
$smsg->{references},
PublicInbox::SearchMsg::bytes($smsg),
PublicInbox::SearchMsg::lines($smsg));
+ utf8::encode($s);
+ $s
}
sub cmd_over ($;$) {
my ($self, $range) = @_;
if ($range && $range =~ /\A<(.+)>\z/) {
my ($ng, $n) = mid_lookup($self, $1);
- my $smsg = $ng->search->lookup_message($range) or
+ my $smsg = $ng->search->lookup_mail($range) or
return '430 No article with that message-id';
more($self, '224 Overview information follows (multi-line)');
- $smsg = PublicInbox::SearchMsg->load_doc($smsg->{doc});
# Only set article number column if it's the current group
my $self_ng = $self->{ng};
my @paths;
foreach my $ng (values %{$self->{nntpd}->{groups}}) {
my $n = $ng->mm->num_for($mid);
- push @paths, "$ng->{name}/$n" if defined $n;
+ push @paths, "$ng->{newsgroup}/$n" if defined $n;
}
return '430 no such article on server' unless @paths;
'223 '.join(' ', @paths);
$self->{rbuf} .= $$buf;
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();
my $fd = $self->{fd};
$r = eval { process_line($self, $line) };
# in case we really did dispatch a read event and started
# another long response.
push @$nextq, $self;
- $next_tick ||= Danga::Socket->AddTimer(0, *next_tick);
+ $nextt ||= PublicInbox::EvCleanup::asap(*next_tick);
}
$rv;
}