+ more($self, $xhdr ? r221 : r225);
+ my $off = 0;
+ long_response($self, $beg, $end, sub {
+ my ($i, $lim) = @_;
+ my $res = $srch->query_xover($beg, $end, $off);
+ my $msgs = $res->{msgs};
+ my $nr = scalar @$msgs or return;
+ $off += $nr;
+ $$lim -= $nr;
+ my $tmp = '';
+ 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;
+ });
+ }
+}
+
+sub do_hdr ($$$;$) {
+ my ($self, $xhdr, $header, $range) = @_;
+ my $sub = lc $header;
+ if ($sub eq 'message-id') {
+ hdr_message_id($self, $xhdr, $range);
+ } elsif ($sub eq 'xref') {
+ hdr_xref($self, $xhdr, $range);
+ } elsif ($sub =~ /\A(?:subject|references|date|from|to|cc|
+ bytes|lines)\z/x) {
+ hdr_searchmsg($self, $xhdr, $sub, $range);
+ } elsif ($sub =~ /\A:(bytes|lines)\z/) {
+ hdr_searchmsg($self, $xhdr, $1, $range);
+ } else {
+ $xhdr ? (r221 . "\r\n.") : "503 HDR not permitted on $header";
+ }
+}
+
+# RFC 3977
+sub cmd_hdr ($$;$) {
+ my ($self, $header, $range) = @_;
+ do_hdr($self, 0, $header, $range);
+}
+
+# RFC 2980
+sub cmd_xhdr ($$;$) {
+ my ($self, $header, $range) = @_;
+ do_hdr($self, 1, $header, $range);
+}
+
+sub hdr_mid_prefix ($$$$$) {
+ my ($self, $xhdr, $ng, $n, $mid) = @_;
+ return $mid if $xhdr;
+
+ # HDR for RFC 3977 users
+ if (my $self_ng = $self->{ng}) {
+ ($self_ng eq $ng) ? $n : '0';
+ } else {
+ '0';
+ }
+}
+
+sub hdr_mid_response ($$$$$$) {
+ my ($self, $xhdr, $ng, $n, $mid, $v) = @_; # r: art_lookup result
+ 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, $ng, $n, $mid);
+ $res .= "$pfx $v\r\n";
+ }
+ res($self, $res .= '.');
+ undef;
+}
+
+sub cmd_xrover ($;$) {
+ my ($self, $range) = @_;
+ my $ng = $self->{ng} or return '412 no newsgroup selected';
+ (defined $range && $range =~ /[<>]/) and
+ return '420 No article(s) selected'; # no message IDs
+
+ $range = $self->{article} unless defined $range;
+ my $r = get_range($self, $range);
+ return $r unless ref $r;
+ my ($beg, $end) = @$r;
+ my $mm = $ng->mm;
+ my $srch = $ng->search;
+ more($self, '224 Overview information follows');
+ long_response($self, $beg, $end, sub {
+ my ($i) = @_;
+ my $mid = $mm->mid_for($$i) or return;
+ my $h = search_header_for($srch, $mid, 'references');
+ more($self, "$$i $h");
+ });
+}
+
+sub over_line ($$) {
+ my ($num, $smsg) = @_;
+ # n.b. field access and procedural calls can be
+ # 10%-15% faster than OO method calls:
+ my $s = join("\t", $num,
+ $smsg->{subject},
+ $smsg->{from},
+ PublicInbox::SearchMsg::date($smsg),
+ '<'.PublicInbox::SearchMsg::mid($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_mail($range) or
+ return '430 No article with that message-id';
+ more($self, '224 Overview information follows (multi-line)');
+
+ # Only set article number column if it's the current group
+ my $self_ng = $self->{ng};
+ $n = 0 if (!$self_ng || $self_ng ne $ng);
+ more($self, over_line($n, $smsg));
+ '.';
+ } else {
+ cmd_xover($self, $range);