about summary refs log tree commit homepage
path: root/lib/PublicInbox/IMAP.pm
diff options
context:
space:
mode:
authorEric Wong <e@yhbt.net>2020-06-10 07:04:25 +0000
committerEric Wong <e@yhbt.net>2020-06-13 07:55:45 +0000
commit71cbe4126d03dc79cfa8f3b13ba83c29af9da5d1 (patch)
treec81e6ed20b31ffef62fedc88e3da60b29156960d /lib/PublicInbox/IMAP.pm
parent1d6c44968bac13bafcd1b056d67261faee52519d (diff)
downloadpublic-inbox-71cbe4126d03dc79cfa8f3b13ba83c29af9da5d1.tar.gz
The RFC 3501 `sequence-set' definition allows comma-delimited
ranges, so we'll support it in case clients send them.

Coalescing overlapping ranges isn't required, so we won't
support it as such an attempt to save bandwidth would waste
memory on the server, instead.
Diffstat (limited to 'lib/PublicInbox/IMAP.pm')
-rw-r--r--lib/PublicInbox/IMAP.pm92
1 files changed, 57 insertions, 35 deletions
diff --git a/lib/PublicInbox/IMAP.pm b/lib/PublicInbox/IMAP.pm
index 51ab8b8c..917833f7 100644
--- a/lib/PublicInbox/IMAP.pm
+++ b/lib/PublicInbox/IMAP.pm
@@ -61,6 +61,9 @@ for my $att (keys %FETCH_ATT) {
         $FETCH_ATT{$att} = \%h;
 }
 
+my $valid_range = '[0-9]+|[0-9]+:[0-9]+|[0-9]+:\*';
+$valid_range = qr/\A(?:$valid_range)(?:,(?:$valid_range))*\z/;
+
 sub greet ($) {
         my ($self) = @_;
         my $capa = capa($self);
@@ -387,7 +390,7 @@ sub requeue_once ($) {
 
 sub uid_fetch_cb { # called by git->cat_async via git_async_msg
         my ($bref, $oid, $type, $size, $fetch_m_arg) = @_;
-        my ($self, undef, $ibx, undef, undef, $msgs, $want) = @$fetch_m_arg;
+        my ($self, undef, $ibx, $msgs, undef, $want) = @$fetch_m_arg;
         my $smsg = shift @$msgs or die 'BUG: no smsg';
         if (!defined($oid)) {
                 # it's possible to have TOCTOU if an admin runs
@@ -441,15 +444,48 @@ sub uid_fetch_cb { # called by git->cat_async via git_async_msg
         requeue_once($self);
 }
 
+sub range_step ($$) {
+        my ($ibx, $range_csv) = @_;
+        my ($beg, $end, $range);
+        if ($$range_csv =~ s/\A([^,]+),//) {
+                $range = $1;
+        } else {
+                $range = $$range_csv;
+                $$range_csv = undef;
+        }
+        if ($range =~ /\A([0-9]+):([0-9]+)\z/) {
+                ($beg, $end) = ($1, $2);
+        } elsif ($range =~ /\A([0-9]+):\*\z/) {
+                ($beg, $end) =  ($1, $ibx->mm->max // 0);
+        } elsif ($range =~ /\A[0-9]+\z/) {
+                $beg = $end = $range;
+        } else {
+                return 'BAD fetch range';
+        }
+        [ $beg, $end, $$range_csv ];
+}
+
+sub refill_range ($$$) {
+        my ($ibx, $msgs, $range_info) = @_;
+        my ($beg, $end, $range_csv) = @$range_info;
+        if (scalar(@$msgs = @{$ibx->over->query_xover($beg, $end)})) {
+                $range_info->[0] = $msgs->[-1]->{num} + 1;
+                return;
+        }
+        return 'OK Fetch done' if !$range_csv;
+        my $next_range = range_step($ibx, \$range_csv);
+        return $next_range if !ref($next_range); # error
+        @$range_info = @$next_range;
+        undef; # keep looping
+}
+
 sub uid_fetch_m { # long_response
-        my ($self, $tag, $ibx, $beg, $end, $msgs, $want) = @_;
-        if (!@$msgs) { # refill
-                @$msgs = @{$ibx->over->query_xover($$beg, $end)};
-                if (!@$msgs) {
-                        $self->write(\"$tag OK Fetch done\r\n");
+        my ($self, $tag, $ibx, $msgs, $range_info, $want) = @_;
+        while (!@$msgs) { # rare
+                if (my $end = refill_range($ibx, $msgs, $range_info)) {
+                        $self->write(\"$tag $end\r\n");
                         return;
                 }
-                $$beg = $msgs->[-1]->{num} + 1;
         }
         git_async_msg($ibx, $msgs->[0], \&uid_fetch_cb, \@_);
 }
@@ -635,7 +671,7 @@ sub partial_emit ($$$) {
 }
 
 sub fetch_common ($$$$) {
-        my ($self, $tag, $range, $want) = @_;
+        my ($self, $tag, $range_csv, $want) = @_;
         my $ibx = $self->{ibx} or return "$tag BAD No mailbox selected\r\n";
         if ($want->[0] =~ s/\A\(//s) {
                 $want->[-1] =~ s/\)\z//s or return "$tag BAD no rparen\r\n";
@@ -658,41 +694,27 @@ sub fetch_common ($$$$) {
                         [ $_, @{$partial{$_}} ]
                 } sort keys %partial ];
         }
-
-        my ($beg, $end);
-        my $msgs = [];
-        if ($range =~ /\A([0-9]+):([0-9]+)\z/s) {
-                ($beg, $end) = ($1, $2);
-        } elsif ($range =~ /\A([0-9]+):\*\z/s) {
-                ($beg, $end) =  ($1, $ibx->mm->max // 0);
-        } elsif ($range =~ /\A[0-9]+\z/) {
-                my $smsg = $ibx->over->get_art($range) or
-                        return "$tag OK Fetch done\r\n"; # really OK(!)
-                push @$msgs, $smsg;
-                ($beg, $end) = ($range, 0);
-        } else {
-                return "$tag BAD fetch range\r\n";
-        }
-        [ $tag, $ibx, \$beg, $end, $msgs, \%want ];
+        $range_csv = 'bad' if $range_csv !~ $valid_range;
+        my $range_info = range_step($ibx, \$range_csv);
+        return "$tag $range_info\r\n" if !ref($range_info);
+        [ $tag, $ibx, [], $range_info, \%want ];
 }
 
 sub cmd_uid_fetch ($$$;@) {
-        my ($self, $tag, $range, @want) = @_;
-        my $args = fetch_common($self, $tag, $range, \@want);
+        my ($self, $tag, $range_csv, @want) = @_;
+        my $args = fetch_common($self, $tag, $range_csv, \@want);
         ref($args) eq 'ARRAY' ?
                 long_response($self, \&uid_fetch_m, @$args) :
                 $args; # error
 }
 
 sub seq_fetch_m { # long_response
-        my ($self, $tag, $ibx, $beg, $end, $msgs, $want) = @_;
-        if (!@$msgs) { # refill
-                @$msgs = @{$ibx->over->query_xover($$beg, $end)};
-                if (!@$msgs) {
-                        $self->write(\"$tag OK Fetch done\r\n");
+        my ($self, $tag, $ibx, $msgs, $range_info, $want) = @_;
+        while (!@$msgs) { # rare
+                if (my $end = refill_range($ibx, $msgs, $range_info)) {
+                        $self->write(\"$tag $end\r\n");
                         return;
                 }
-                $$beg = $msgs->[-1]->{num} + 1;
         }
         my $seq = $want->{-seqno}++;
         my $cur_num = $msgs->[0]->{num};
@@ -711,11 +733,11 @@ sub seq_fetch_m { # long_response
 }
 
 sub cmd_fetch ($$$;@) {
-        my ($self, $tag, $range, @want) = @_;
-        my $args = fetch_common($self, $tag, $range, \@want);
+        my ($self, $tag, $range_csv, @want) = @_;
+        my $args = fetch_common($self, $tag, $range_csv, \@want);
         ref($args) eq 'ARRAY' ? do {
                 my $want = $args->[-1];
-                $want->{-seqno} = ${$args->[2]}; # $$beg
+                $want->{-seqno} = $args->[3]->[0]; # $beg == $range_info->[0];
                 long_response($self, \&seq_fetch_m, @$args)
         } : $args; # error
 }