From 7d50d83ab52148285c642158bd57bdd18a1ee6d4 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 16 May 2019 01:05:25 +0200 Subject: interimap: accept C-style escape sequences in 'list-mailbox'. This is useful for defining names containing control characters (incl. \0 for unspecified hierarchy delimiter). --- Changelog | 6 ++++++ interimap | 63 ++++++++++++++++++++++++++++++++++++++++++------------------ interimap.md | 18 ++++++++++------- 3 files changed, 61 insertions(+), 26 deletions(-) diff --git a/Changelog b/Changelog index f261a98..209bb25 100644 --- a/Changelog +++ b/Changelog @@ -1,5 +1,8 @@ interimap (0.5) upstream; + * interimap: the space-speparated list of names and/or patterns in + 'list-mailbox' can now contain C-style escape sequences (backslash + and hexadecimal escape). + interimap: write which --target to use in --delete command suggestions. - libinterimap: bugfix: hierarchy delimiters in LIST responses were @@ -11,6 +14,9 @@ interimap (0.5) upstream; - libinterimap: quote() the empty string as "" instead of a 0-length literal. (This saves 3 bytes + one round-trip on servers not supporting non-synchronizing literals, and 4 bytes otherwise.) + - interimap: unlike what the documentation said, spaces where not + allowed in the 'list-select-opts' configuration option, so at maximum + one selector could be used for the initial LIST command. -- Guilhem Moulin Fri, 10 May 2019 00:58:14 +0200 diff --git a/interimap b/interimap index fa65241..8aeaba4 100755 --- a/interimap +++ b/interimap @@ -2,7 +2,7 @@ #---------------------------------------------------------------------- # Fast bidirectional synchronization for QRESYNC-capable IMAP servers -# Copyright © 2015-2018 Guilhem Moulin +# Copyright © 2015-2019 Guilhem Moulin # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -79,11 +79,11 @@ my $CONF = do { , database => qr/\A(\P{Control}+)\z/ , logfile => qr/\A(\/\P{Control}+)\z/ , 'list-mailbox' => qr/\A([\x01-\x09\x0B\x0C\x0E-\x7F]+)\z/ - , 'list-select-opts' => qr/\A([\x21\x23\x24\x26\x27\x2B-\x5B\x5E-\x7A\x7C-\x7E]+)\z/ + , 'list-select-opts' => qr/\A([\x20\x21\x23\x24\x26\x27\x2B-\x5B\x5E-\x7A\x7C-\x7E]*)\z/ , 'ignore-mailbox' => qr/\A([\x01-\x09\x0B\x0C\x0E-\x7F]+)\z/ ); }; -my ($DBFILE, $LOGGER_FD); +my ($DBFILE, $LOGGER_FD, %LIST); { $DBFILE = $CONF->{_}->{database} if defined $CONF->{_}; @@ -104,6 +104,31 @@ my ($DBFILE, $LOGGER_FD); elsif ($CONFIG{debug}) { $LOGGER_FD = \*STDERR; } + + $LIST{mailbox} = [@ARGV]; + if (!defined $COMMAND or $COMMAND eq 'repair') { + if (!@ARGV and defined (my $v = $CONF->{_}->{'list-mailbox'})) { + my @mailbox; + do { + if ($v =~ s/\A[\x21\x23-\x27\x2A-\x5B\x5D-\x7A\x7C-\x7E]+//p) { + push @mailbox, ${^MATCH}; + } elsif ($v =~ s/\A\"((?: + [\x20\x21\x23-\x5B\x5D-\x7E] | # the above plus \x20\x28\x29\x7B + (?:\\(?:[\x22\x5C0abtnvfr] | x\p{AHex}{2})) # quoted char or hex-encoded pair + )+)\"//x) { + push @mailbox, $1 =~ s/\\(?:[\x22\x5C0abtnvfr]|x\p{AHex}{2})/"\"${^MATCH}\""/greep; + } + } while ($v =~ s/\A\s+//); + die "Invalid value for list-mailbox: ".$CONF->{_}->{'list-mailbox'}."\n" if $v ne ""; + $LIST{mailbox} = \@mailbox; + } + $LIST{'select-opts'} = uc($CONF->{_}->{'list-select-opts'}) + if defined $CONF->{_}->{'list-select-opts'} and $CONF->{_}->{'list-select-opts'} ne ""; + $LIST{params} = [ "SUBSCRIBED" ]; # RFC 5258 - LIST Command Extensions + push @{$LIST{params}}, "STATUS (UIDVALIDITY UIDNEXT HIGHESTMODSEQ)" + # RFC 5819 - Returning STATUS Information in Extended LIST + unless $CONFIG{notify}; + } } my $DBH; @@ -227,20 +252,6 @@ logger(undef, ">>> $NAME $VERSION"); ############################################################################# # Connect to the local and remote IMAP servers -my $LIST = '"" '; -my @LIST_PARAMS; -my %LIST_PARAMS_STATUS = (STATUS => [qw/UIDVALIDITY UIDNEXT HIGHESTMODSEQ/]); -if (!defined $COMMAND or $COMMAND eq 'repair') { - $LIST = '('.uc($CONF->{_}->{'list-select-opts'}).') '.$LIST if defined $CONF->{_}->{'list-select-opts'}; - $LIST .= (defined $CONF->{_}->{'list-mailbox'} ? '('.$CONF->{_}->{'list-mailbox'}.')' : '*') unless @ARGV; - @LIST_PARAMS = ('SUBSCRIBED'); - push @LIST_PARAMS, map { "$_ (".join(' ', @{$LIST_PARAMS_STATUS{$_}}).")" } keys %LIST_PARAMS_STATUS - unless $CONFIG{notify}; -} -$LIST .= $#ARGV == 0 ? Net::IMAP::InterIMAP::quote($ARGV[0]) - : ('('.join(' ',map {Net::IMAP::InterIMAP::quote($_)} @ARGV).')') if @ARGV; - - foreach my $name (qw/local remote/) { my %config = %{$CONF->{$name}}; $config{$_} = $CONFIG{$_} foreach grep {defined $CONFIG{$_}} qw/quiet debug/; @@ -257,7 +268,21 @@ foreach my $name (qw/local remote/) { die "Non LIST-STATUS-capable IMAP server.\n" if !$CONFIG{notify} and $client->incapable('LIST-STATUS'); } -@{$IMAP->{$_}}{qw/mailboxes delims/} = $IMAP->{$_}->{client}->list($LIST, @LIST_PARAMS) for qw/local remote/; +# List mailboxes; don't return anything but update $IMAP->{$name}->{mailboxes} and +# $IMAP->{$name}->{delims} +sub list_mailboxes($) { + my $name = shift; + my $list = ""; + $list .= "(" .$LIST{'select-opts'}. ") " if defined $LIST{'select-opts'}; + $list .= "\"\" "; + my @mailboxes = @{$LIST{mailbox}} ? map {Net::IMAP::InterIMAP::quote($_)} @{$LIST{mailbox}} : "*"; + $list .= $#mailboxes == 0 ? $mailboxes[0] : "(".join(" ", @mailboxes).")"; + my ($mbx, $delims) = $IMAP->{$name}->{client}->list($list, @{$LIST{params} // []}); + $IMAP->{$name}->{mailboxes} = $mbx; + $IMAP->{$name}->{delims} = $delims; +} + +list_mailboxes($_) for qw/local remote/; ############################################################################## @@ -1239,7 +1264,7 @@ while (1) { sleep $CONFIG{watch}; # refresh the mailbox list and status - @{$IMAP->{$_}}{qw/mailboxes delims/} = $IMAP->{$_}->{client}->list($LIST, @LIST_PARAMS) for qw/local remote/; + list_mailboxes($_) for qw/local remote/; @MAILBOXES = sync_mailbox_list(); } } diff --git a/interimap.md b/interimap.md index 4d85eaf..a230c09 100644 --- a/interimap.md +++ b/interimap.md @@ -82,10 +82,10 @@ the *list-mailbox*, *list-select-opts* and *ignore-mailbox* options from the [configuration file](#configuration-file) can be used to shrink that list and save bandwidth. However if some extra argument are provided on the command line, -`interimap` ignores said options and synchronizes the given +`interimap` ignores these options and synchronizes the given *MAILBOX*es instead. Note that each *MAILBOX* is taken “as is”; in particular, it must be [UTF-7 encoded][RFC 2152], unquoted, and the list -wildcards ‘\*’ and ‘%’ are not expanded. +wildcards ‘\*’ and ‘%’ are passed verbatim to the IMAP server. If the synchronization was interrupted during a previous run while some messages were being replicated (but before the `UIDNEXT` or @@ -219,12 +219,16 @@ Valid options are: : A space separated list of mailbox patterns to use when issuing the initial `LIST` command (overridden by the *MAILBOX*es given as command-line arguments). - Note that each pattern containing special characters such as spaces - or brackets (see [RFC 3501] for the exact syntax) must be quoted. + Names containing special characters such as spaces or brackets need + to be enclosed in double quotes. Within double quotes C-style + backslash escape sequences can be used (‘\\t’ for an horizontal tab, + ‘\\n’ for a new line, ‘\\\\’ for a backslash, etc.), as well as + hexadecimal escape sequences ‘\\xHH’. Furthermore, non-ASCII names must be [UTF-7 encoded][RFC 2152]. - Two wildcards are available: a ‘\*’ character matches zero or more - characters, while a ‘%’ character matches zero or more characters up - to the mailbox's hierarchy delimiter. + Two wildcards are available, and passed verbatim to the IMAP server: + a ‘\*’ character matches zero or more characters, while a ‘%’ + character matches zero or more characters up to the hierarchy + delimiter. This option is only available in the default section. (The default pattern, `*`, matches all visible mailboxes on the server.) -- cgit v1.2.3