From 7952dfc9ba1255400f85068364150bc5e0db869d Mon Sep 17 00:00:00 2001
From: Eric Wong <e@yhbt.net>
Date: Wed, 10 Jun 2020 07:04:05 +0000
Subject: [PATCH] imap: implement STATUS command

I'm not sure if there's much use for this command, but it's
part of RFC3501 and works read-only.
---
 lib/PublicInbox/IMAP.pm | 29 +++++++++++++++++++++++++++++
 t/imapd.t               |  5 +++++
 2 files changed, 34 insertions(+)

diff --git a/lib/PublicInbox/IMAP.pm b/lib/PublicInbox/IMAP.pm
index c8592dc0..a2d59e5c 100644
--- a/lib/PublicInbox/IMAP.pm
+++ b/lib/PublicInbox/IMAP.pm
@@ -306,6 +306,35 @@ sub uid_fetch_m { # long_response
 	1;
 }
 
+sub cmd_status ($$$;@) {
+	my ($self, $tag, $mailbox, @items) = @_;
+	my $ibx = $self->{imapd}->{groups}->{$mailbox} or
+		return "$tag NO Mailbox doesn't exist: $mailbox\r\n";
+	return "$tag BAD no items\r\n" if !scalar(@items);
+	($items[0] !~ s/\A\(//s || $items[-1] !~ s/\)\z//s) and
+		return "$tag BAD invalid args\r\n";
+
+	my $mm = $ibx->mm;
+	my ($max, @it);
+	for my $it (@items) {
+		$it = uc($it);
+		push @it, $it;
+		if ($it =~ /\A(?:MESSAGES|UNSEEN|RECENT)\z/) {
+			push(@it, ($max //= $mm->max // 0));
+		} elsif ($it eq 'UIDNEXT') {
+			push(@it, ($max //= $mm->max // 0) + 1);
+		} elsif ($it eq 'UIDVALIDITY') {
+			push(@it, $mm->created_at //
+				return("$tag BAD UIDVALIDITY\r\n"));
+		} else {
+			return "$tag BAD invalid item\r\n";
+		}
+	}
+	return "$tag BAD no items\r\n" if !@it;
+	"* STATUS $mailbox (".join(' ', @it).")\r\n" .
+	"$tag OK Status complete\r\n";
+}
+
 sub cmd_uid_fetch ($$$;@) {
 	my ($self, $tag, $range, @want) = @_;
 	my $ibx = $self->{ibx} or return "$tag BAD No mailbox selected\r\n";
diff --git a/t/imapd.t b/t/imapd.t
index b0caa8f1..7512bb90 100644
--- a/t/imapd.t
+++ b/t/imapd.t
@@ -81,6 +81,11 @@ ok(!$mic->select('foo') && ($e = $@), 'EXAMINE non-existent');
 like($e, qr/\bNO\b/, 'got a NO on EXAMINE for non-existent');
 ok($mic->select('inbox.i1'), 'SELECT succeeds');
 ok($mic->examine('inbox.i1'), 'EXAMINE succeeds');
+my @raw = $mic->status('inbox.i1', qw(Messages uidnext uidvalidity));
+is(scalar(@raw), 2, 'got status response');
+like($raw[0], qr/\A\*\x20STATUS\x20inbox\.i1\x20
+	\(MESSAGES\x20\d+\x20UIDNEXT\x20\d+\x20UIDVALIDITY\x20\d+\)\r\n/sx);
+like($raw[1], qr/\A\S+ OK /, 'finished status response');
 
 my $ret = $mic->search('all') or BAIL_OUT "SEARCH FAIL $@";
 is_deeply($ret, [ 1 ], 'search all works');
-- 
2.51.0