| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  |  | 
| 2 |  |  |  |  |  |  | # _{name} methods are undocumented and meant to be private. | 
| 3 |  |  |  |  |  |  |  | 
| 4 |  |  |  |  |  |  | require 5.008_001; | 
| 5 |  |  |  |  |  |  |  | 
| 6 | 3 |  |  | 3 |  | 185450 | use strict; | 
|  | 3 |  |  |  |  | 28 |  | 
|  | 3 |  |  |  |  | 74 |  | 
| 7 | 3 |  |  | 3 |  | 12 | use warnings; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 151 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | package Mail::IMAPClient; | 
| 10 |  |  |  |  |  |  | our $VERSION = '3.42'; | 
| 11 |  |  |  |  |  |  |  | 
| 12 | 3 |  |  | 3 |  | 1044 | use Mail::IMAPClient::MessageSet; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 86 |  | 
| 13 |  |  |  |  |  |  |  | 
| 14 | 3 |  |  | 3 |  | 811 | use IO::Socket qw(:crlf SOL_SOCKET SO_KEEPALIVE); | 
|  | 3 |  |  |  |  | 34434 |  | 
|  | 3 |  |  |  |  | 16 |  | 
| 15 | 3 |  |  | 3 |  | 1784 | use IO::Select (); | 
|  | 3 |  |  |  |  | 3945 |  | 
|  | 3 |  |  |  |  | 65 |  | 
| 16 | 3 |  |  | 3 |  | 15 | use Carp qw(carp);    #local $SIG{__WARN__} = \&Carp::cluck; #DEBUG | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 139 |  | 
| 17 |  |  |  |  |  |  |  | 
| 18 | 3 |  |  | 3 |  | 16 | use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK); | 
|  | 3 |  |  |  |  | 5 |  | 
|  | 3 |  |  |  |  | 115 |  | 
| 19 | 3 |  |  | 3 |  | 14 | use Errno qw(EAGAIN EBADF ECONNRESET EPIPE); | 
|  | 3 |  |  |  |  | 4 |  | 
|  | 3 |  |  |  |  | 273 |  | 
| 20 | 3 |  |  | 3 |  | 17 | use List::Util qw(first min max sum); | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 289 |  | 
| 21 | 3 |  |  | 3 |  | 1116 | use MIME::Base64 qw(encode_base64 decode_base64); | 
|  | 3 |  |  |  |  | 1533 |  | 
|  | 3 |  |  |  |  | 141 |  | 
| 22 | 3 |  |  | 3 |  | 17 | use File::Spec (); | 
|  | 3 |  |  |  |  | 5 |  | 
|  | 3 |  |  |  |  | 71 |  | 
| 23 |  |  |  |  |  |  |  | 
| 24 | 3 |  |  | 3 |  | 16 | use constant APPEND_BUFFER_SIZE => 1024 * 1024; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 287 |  | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | use constant { | 
| 27 | 3 |  |  |  |  | 207 | Unconnected   => 0, | 
| 28 |  |  |  |  |  |  | Connected     => 1,    # connected; not logged in | 
| 29 |  |  |  |  |  |  | Authenticated => 2,    # logged in; no mailbox selected | 
| 30 |  |  |  |  |  |  | Selected      => 3,    # mailbox selected | 
| 31 | 3 |  |  | 3 |  | 17 | }; | 
|  | 3 |  |  |  |  | 5 |  | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | use constant { | 
| 34 | 3 |  |  |  |  | 951 | INDEX => 0,    # Array index for output line number | 
| 35 |  |  |  |  |  |  | TYPE  => 1,    # Array index for line type (OUTPUT, INPUT, or LITERAL) | 
| 36 |  |  |  |  |  |  | DATA  => 2,    # Array index for output line data | 
| 37 | 3 |  |  | 3 |  | 16 | }; | 
|  | 3 |  |  |  |  | 4 |  | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | my %SEARCH_KEYS = map { ( $_ => 1 ) } qw( | 
| 40 |  |  |  |  |  |  | ALL ANSWERED BCC BEFORE BODY CC DELETED DRAFT FLAGGED | 
| 41 |  |  |  |  |  |  | FROM HEADER KEYWORD LARGER NEW NOT OLD ON OR RECENT | 
| 42 |  |  |  |  |  |  | SEEN SENTBEFORE SENTON SENTSINCE SINCE SMALLER SUBJECT | 
| 43 |  |  |  |  |  |  | TEXT TO UID UNANSWERED UNDELETED UNDRAFT UNFLAGGED | 
| 44 |  |  |  |  |  |  | UNKEYWORD UNSEEN); | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  | # modules require(d) during runtime when applicable | 
| 47 |  |  |  |  |  |  | my %Load_Module = ( | 
| 48 |  |  |  |  |  |  | "Compress-Zlib" => "Compress::Zlib", | 
| 49 |  |  |  |  |  |  | "INET"          => "IO::Socket::INET", | 
| 50 |  |  |  |  |  |  | "IP"            => "IO::Socket::IP", | 
| 51 |  |  |  |  |  |  | "SSL"           => "IO::Socket::SSL", | 
| 52 |  |  |  |  |  |  | "UNIX"          => "IO::Socket::UNIX", | 
| 53 |  |  |  |  |  |  | "BodyStructure" => "Mail::IMAPClient::BodyStructure", | 
| 54 |  |  |  |  |  |  | "Envelope"      => "Mail::IMAPClient::BodyStructure::Envelope", | 
| 55 |  |  |  |  |  |  | "Thread"        => "Mail::IMAPClient::Thread", | 
| 56 |  |  |  |  |  |  | ); | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | sub _load_module { | 
| 59 | 0 |  |  | 0 |  | 0 | my $self   = shift; | 
| 60 | 0 |  |  |  |  | 0 | my $modkey = shift; | 
| 61 | 0 |  | 0 |  |  | 0 | my $module = $Load_Module{$modkey} || $modkey; | 
| 62 |  |  |  |  |  |  |  | 
| 63 | 0 |  |  |  |  | 0 | my $err = do { | 
| 64 | 0 |  |  |  |  | 0 | local ($@); | 
| 65 | 0 |  |  |  |  | 0 | eval "require $module"; | 
| 66 | 0 |  |  |  |  | 0 | $@; | 
| 67 |  |  |  |  |  |  | }; | 
| 68 | 0 | 0 |  |  |  | 0 | if ($err) { | 
| 69 | 0 |  |  |  |  | 0 | $self->LastError("Unable to load '$module': $err"); | 
| 70 | 0 |  |  |  |  | 0 | return undef; | 
| 71 |  |  |  |  |  |  | } | 
| 72 | 0 |  |  |  |  | 0 | return $module; | 
| 73 |  |  |  |  |  |  | } | 
| 74 |  |  |  |  |  |  |  | 
| 75 |  |  |  |  |  |  | sub _debug { | 
| 76 | 4 |  |  | 4 |  | 6 | my $self = shift; | 
| 77 | 4 | 50 |  |  |  | 10 | return unless $self->Debug; | 
| 78 |  |  |  |  |  |  |  | 
| 79 | 0 |  |  |  |  | 0 | my $text = join '', @_; | 
| 80 | 0 |  |  |  |  | 0 | $text =~ s/$CRLF/\n  /og; | 
| 81 | 0 |  |  |  |  | 0 | $text =~ s/\s*$/\n/; | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | #use POSIX (); $text = POSIX::strftime("%F %T ", localtime).$text; #DEBUG | 
| 84 | 0 |  | 0 |  |  | 0 | my $fh = $self->{Debug_fh} || \*STDERR; | 
| 85 | 0 |  |  |  |  | 0 | print $fh $text; | 
| 86 |  |  |  |  |  |  | } | 
| 87 |  |  |  |  |  |  |  | 
| 88 |  |  |  |  |  |  | BEGIN { | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | # set-up accessors | 
| 91 | 3 |  |  | 3 |  | 19 | foreach my $datum ( | 
| 92 |  |  |  |  |  |  | qw(Authcallback Authmechanism Authuser Buffer Count Compress | 
| 93 |  |  |  |  |  |  | Debug Debug_fh Domain Folder Ignoresizeerrors Keepalive | 
| 94 |  |  |  |  |  |  | Maxappendstringlength Maxcommandlength Maxtemperrors | 
| 95 |  |  |  |  |  |  | Password Peek Port Prewritemethod Proxy Ranges Readmethod | 
| 96 |  |  |  |  |  |  | Readmoremethod Reconnectretry Server Showcredentials | 
| 97 |  |  |  |  |  |  | Socketargs Ssl Starttls Supportedflags Timeout Uid User) | 
| 98 |  |  |  |  |  |  | ) | 
| 99 |  |  |  |  |  |  | { | 
| 100 | 3 |  |  | 3 |  | 19 | no strict 'refs'; | 
|  | 3 |  |  |  |  | 5 |  | 
|  | 3 |  |  |  |  | 236 |  | 
| 101 |  |  |  |  |  |  | *$datum = sub { | 
| 102 | 37 | 100 |  | 37 |  | 650 | @_ > 1 ? ( $_[0]->{$datum} = $_[1] ) : $_[0]->{$datum}; | 
| 103 | 99 |  |  |  |  | 59311 | }; | 
| 104 |  |  |  |  |  |  | } | 
| 105 |  |  |  |  |  |  | } | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | sub LastError { | 
| 108 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 109 | 0 | 0 |  |  |  | 0 | @_ or return $self->{LastError}; | 
| 110 | 0 |  |  |  |  | 0 | my $err = shift; | 
| 111 |  |  |  |  |  |  |  | 
| 112 |  |  |  |  |  |  | # allow LastError to be reset with undef | 
| 113 | 0 | 0 |  |  |  | 0 | if ( defined $err ) { | 
| 114 | 0 |  |  |  |  | 0 | $err =~ s/$CRLF$//og; | 
| 115 | 0 |  |  |  |  | 0 | local ($!);    # old versions of Carp could reset $! | 
| 116 | 0 |  |  |  |  | 0 | $self->_debug( Carp::longmess("ERROR: $err") ); | 
| 117 |  |  |  |  |  |  |  | 
| 118 |  |  |  |  |  |  | # hopefully this is rare... | 
| 119 | 0 | 0 |  |  |  | 0 | if ( $err =~ /NO not connected/ ) { | 
| 120 | 0 |  | 0 |  |  | 0 | my $lerr = $self->{LastError} || ""; | 
| 121 | 0 |  |  |  |  | 0 | my $emsg = "Trying command when NOT connected!"; | 
| 122 | 0 | 0 |  |  |  | 0 | $emsg .= " LastError was: $lerr" if $lerr; | 
| 123 | 0 |  |  |  |  | 0 | Carp::cluck($emsg); | 
| 124 |  |  |  |  |  |  | } | 
| 125 |  |  |  |  |  |  | } | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | # 2.x API support requires setting $@ | 
| 128 | 0 |  |  |  |  | 0 | $@ = $self->{LastError} = $err; | 
| 129 |  |  |  |  |  |  | } | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | sub Fast_io(;$) { | 
| 132 | 0 |  |  | 0 | 1 | 0 | my ( $self, $use ) = @_; | 
| 133 |  |  |  |  |  |  | defined $use | 
| 134 | 0 | 0 |  |  |  | 0 | or return $self->{Fast_io}; | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  | my $socket = $self->{Socket} | 
| 137 | 0 | 0 |  |  |  | 0 | or return undef; | 
| 138 |  |  |  |  |  |  |  | 
| 139 | 0 |  |  |  |  | 0 | local ( $@, $! );    # avoid stomping on globals | 
| 140 | 0 | 0 |  |  |  | 0 | unless ($use) { | 
| 141 | 0 |  |  |  |  | 0 | eval { fcntl( $socket, F_SETFL, delete $self->{_fcntl} ) } | 
| 142 | 0 | 0 |  |  |  | 0 | if exists $self->{_fcntl}; | 
| 143 | 0 |  |  |  |  | 0 | $self->{Fast_io} = 0; | 
| 144 | 0 |  |  |  |  | 0 | return undef; | 
| 145 |  |  |  |  |  |  | } | 
| 146 |  |  |  |  |  |  |  | 
| 147 | 0 |  |  |  |  | 0 | my $fcntl = eval { fcntl( $socket, F_GETFL, 0 ) }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 148 | 0 | 0 |  |  |  | 0 | if ($@) { | 
| 149 | 0 |  |  |  |  | 0 | $self->{Fast_io} = 0; | 
| 150 |  |  |  |  |  |  | $self->_debug("not using Fast_IO; not available on this platform") | 
| 151 | 0 | 0 |  |  |  | 0 | unless $self->{_fastio_warning_}++; | 
| 152 | 0 |  |  |  |  | 0 | return undef; | 
| 153 |  |  |  |  |  |  | } | 
| 154 |  |  |  |  |  |  |  | 
| 155 | 0 |  |  |  |  | 0 | $self->{Fast_io} = 1; | 
| 156 | 0 |  |  |  |  | 0 | my $newflags = $self->{_fcntl} = $fcntl; | 
| 157 | 0 |  |  |  |  | 0 | $newflags |= O_NONBLOCK; | 
| 158 | 0 |  |  |  |  | 0 | fcntl( $socket, F_SETFL, $newflags ); | 
| 159 |  |  |  |  |  |  | } | 
| 160 |  |  |  |  |  |  |  | 
| 161 |  |  |  |  |  |  | # removed | 
| 162 | 0 |  |  | 0 | 1 | 0 | sub EnableServerResponseInLiteral { undef } | 
| 163 |  |  |  |  |  |  |  | 
| 164 | 0 |  |  | 0 | 0 | 0 | sub Wrap { shift->Clear(@_) } | 
| 165 |  |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  | # The following class method is for creating valid dates in appended msgs: | 
| 167 |  |  |  |  |  |  | my @dow = qw(Sun Mon Tue Wed Thu Fri Sat); | 
| 168 |  |  |  |  |  |  | my @mnt = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  | sub Rfc822_date { | 
| 171 | 0 |  |  | 0 | 1 | 0 | my $class = shift; | 
| 172 | 0 | 0 |  |  |  | 0 | my $date  = $class =~ /^\d+$/ ? $class : shift;    # method or function? | 
| 173 | 0 |  |  |  |  | 0 | my @date  = gmtime($date); | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | #Date: Fri, 09 Jul 1999 13:10:55 -0000 | 
| 176 | 0 |  |  |  |  | 0 | sprintf( | 
| 177 |  |  |  |  |  |  | "%s, %02d %s %04d %02d:%02d:%02d -%04d", | 
| 178 |  |  |  |  |  |  | $dow[ $date[6] ], | 
| 179 |  |  |  |  |  |  | $date[3], | 
| 180 |  |  |  |  |  |  | $mnt[ $date[4] ], | 
| 181 |  |  |  |  |  |  | $date[5] + 1900, | 
| 182 |  |  |  |  |  |  | $date[2], $date[1], $date[0], $date[8] | 
| 183 |  |  |  |  |  |  | ); | 
| 184 |  |  |  |  |  |  | } | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | # The following methods create valid dates for use in IMAP search strings | 
| 187 |  |  |  |  |  |  | # - provide Rfc2060* methods/functions for backwards compatibility | 
| 188 |  |  |  |  |  |  | sub Rfc2060_date { | 
| 189 | 2 | 100 |  | 2 | 0 | 36 | $_[0] =~ /^\d+$/ ? Rfc3501_date(@_) : shift->Rfc3501_date(@_); | 
| 190 |  |  |  |  |  |  | } | 
| 191 |  |  |  |  |  |  |  | 
| 192 |  |  |  |  |  |  | sub Rfc3501_date { | 
| 193 | 4 |  |  | 4 | 1 | 7 | my $class = shift; | 
| 194 | 4 | 100 |  |  |  | 16 | my $stamp = $class =~ /^\d+$/ ? $class : shift; | 
| 195 | 4 |  |  |  |  | 22 | my @date  = gmtime($stamp); | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | # 11-Jan-2000 | 
| 198 | 4 |  |  |  |  | 39 | sprintf( "%02d-%s-%04d", $date[3], $mnt[ $date[4] ], $date[5] + 1900 ); | 
| 199 |  |  |  |  |  |  | } | 
| 200 |  |  |  |  |  |  |  | 
| 201 |  |  |  |  |  |  | sub Rfc2060_datetime($;$) { | 
| 202 | 4 | 100 |  | 4 | 0 | 444 | $_[0] =~ /^\d+$/ ? Rfc3501_datetime(@_) : shift->Rfc3501_datetime(@_); | 
| 203 |  |  |  |  |  |  | } | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  | sub Rfc3501_datetime($;$) { | 
| 206 | 8 |  |  | 8 | 1 | 12 | my $class = shift; | 
| 207 | 8 | 100 |  |  |  | 30 | my $stamp = $class =~ /^\d+$/ ? $class : shift; | 
| 208 | 8 |  | 100 |  |  | 22 | my $zone  = shift || '+0000'; | 
| 209 | 8 |  |  |  |  | 29 | my @date  = gmtime($stamp); | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | # 11-Jan-2000 04:04:04 +0000 | 
| 212 | 8 |  |  |  |  | 74 | sprintf( | 
| 213 |  |  |  |  |  |  | "%02d-%s-%04d %02d:%02d:%02d %s", | 
| 214 |  |  |  |  |  |  | $date[3], | 
| 215 |  |  |  |  |  |  | $mnt[ $date[4] ], | 
| 216 |  |  |  |  |  |  | $date[5] + 1900, | 
| 217 |  |  |  |  |  |  | $date[2], $date[1], $date[0], $zone | 
| 218 |  |  |  |  |  |  | ); | 
| 219 |  |  |  |  |  |  | } | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | # Change CRLF into \n | 
| 222 |  |  |  |  |  |  | sub Strip_cr { | 
| 223 | 0 |  |  | 0 | 1 | 0 | my $class = shift; | 
| 224 | 0 | 0 | 0 |  |  | 0 | if ( !ref $_[0] && @_ == 1 ) { | 
| 225 | 0 |  |  |  |  | 0 | ( my $string = $_[0] ) =~ s/$CRLF/\n/og; | 
| 226 | 0 |  |  |  |  | 0 | return $string; | 
| 227 |  |  |  |  |  |  | } | 
| 228 |  |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  | return wantarray | 
| 230 | 0 |  |  |  |  | 0 | ? map { s/$CRLF/\n/og; $_ } ( ref $_[0] ? @{ $_[0] } : @_ ) | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 231 | 0 | 0 |  |  |  | 0 | : [ map { s/$CRLF/\n/og; $_ } ( ref $_[0] ? @{ $_[0] } : @_ ) ]; | 
|  | 0 | 0 |  |  |  | 0 |  | 
|  | 0 | 0 |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 232 |  |  |  |  |  |  | } | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  | # The following defines a special method to deal with the Clear parameter: | 
| 235 |  |  |  |  |  |  | sub Clear { | 
| 236 | 0 |  |  | 0 | 1 | 0 | my ( $self, $clear ) = @_; | 
| 237 | 0 | 0 |  |  |  | 0 | defined $clear or return $self->{Clear}; | 
| 238 |  |  |  |  |  |  |  | 
| 239 | 0 |  |  |  |  | 0 | my $oldclear = $self->{Clear}; | 
| 240 | 0 |  |  |  |  | 0 | $self->{Clear} = $clear; | 
| 241 |  |  |  |  |  |  |  | 
| 242 | 0 |  |  |  |  | 0 | my @keys = reverse $self->_trans_index; | 
| 243 |  |  |  |  |  |  |  | 
| 244 | 0 |  |  |  |  | 0 | for ( my $i = $clear ; $i < @keys ; $i++ ) { | 
| 245 | 0 |  |  |  |  | 0 | delete $self->{History}{ $keys[$i] }; | 
| 246 |  |  |  |  |  |  | } | 
| 247 |  |  |  |  |  |  |  | 
| 248 | 0 |  |  |  |  | 0 | return $oldclear; | 
| 249 |  |  |  |  |  |  | } | 
| 250 |  |  |  |  |  |  |  | 
| 251 |  |  |  |  |  |  | # read-only access to the transaction number | 
| 252 | 0 |  |  | 0 | 1 | 0 | sub Transaction { shift->Count } | 
| 253 |  |  |  |  |  |  |  | 
| 254 |  |  |  |  |  |  | # remove doubles from list | 
| 255 |  |  |  |  |  |  | sub _remove_doubles(@) { | 
| 256 | 0 |  |  | 0 |  | 0 | my %seen; | 
| 257 | 0 |  |  |  |  | 0 | grep { !$seen{ $_->{name} }++ } @_; | 
|  | 0 |  |  |  |  | 0 |  | 
| 258 |  |  |  |  |  |  | } | 
| 259 |  |  |  |  |  |  |  | 
| 260 |  |  |  |  |  |  | # the constructor: | 
| 261 |  |  |  |  |  |  | sub new { | 
| 262 | 1 |  |  | 1 | 1 | 69 | my $class = shift; | 
| 263 | 1 |  |  |  |  | 9 | my $self  = { | 
| 264 |  |  |  |  |  |  | LastError             => "", | 
| 265 |  |  |  |  |  |  | Uid                   => 1, | 
| 266 |  |  |  |  |  |  | Count                 => 0, | 
| 267 |  |  |  |  |  |  | Clear                 => 2, | 
| 268 |  |  |  |  |  |  | Keepalive             => 0, | 
| 269 |  |  |  |  |  |  | Maxappendstringlength => 1024**2, | 
| 270 |  |  |  |  |  |  | Maxcommandlength      => 1000, | 
| 271 |  |  |  |  |  |  | Maxtemperrors         => undef, | 
| 272 |  |  |  |  |  |  | State                 => Unconnected, | 
| 273 |  |  |  |  |  |  | Authmechanism         => 'LOGIN', | 
| 274 |  |  |  |  |  |  | Timeout               => 600, | 
| 275 |  |  |  |  |  |  | History               => {}, | 
| 276 |  |  |  |  |  |  | }; | 
| 277 | 1 |  |  |  |  | 4 | while (@_) { | 
| 278 | 0 |  |  |  |  | 0 | my $k = ucfirst lc shift; | 
| 279 | 0 |  |  |  |  | 0 | my $v = shift; | 
| 280 | 0 | 0 |  |  |  | 0 | $self->{$k} = $v if defined $v; | 
| 281 |  |  |  |  |  |  | } | 
| 282 | 1 |  | 33 |  |  | 5 | bless $self, ref($class) || $class; | 
| 283 |  |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  | # Fast_io is enabled by default when not given a socket | 
| 285 | 1 | 50 | 33 |  |  | 9 | unless ( exists $self->{Fast_io} || $self->{Socket} || $self->{Rawsocket} ) | 
|  |  |  | 33 |  |  |  |  | 
| 286 |  |  |  |  |  |  | { | 
| 287 | 1 |  |  |  |  | 3 | $self->{Fast_io} = 1; | 
| 288 |  |  |  |  |  |  | } | 
| 289 |  |  |  |  |  |  |  | 
| 290 | 1 | 50 |  |  |  | 3 | if ( my $sup = $self->{Supportedflags} ) {    # unpack into case-less HASH | 
| 291 | 0 | 0 |  |  |  | 0 | my %sup = map { m/^\\?(\S+)/ ? lc $1 : () } @$sup; | 
|  | 0 |  |  |  |  | 0 |  | 
| 292 | 0 |  |  |  |  | 0 | $self->{Supportedflags} = \%sup; | 
| 293 |  |  |  |  |  |  | } | 
| 294 |  |  |  |  |  |  |  | 
| 295 | 1 |  | 50 |  |  | 7 | $self->{Debug_fh} ||= \*STDERR; | 
| 296 | 1 |  |  |  |  | 7 | CORE::select( ( select( $self->{Debug_fh} ), $|++ )[0] ); | 
| 297 |  |  |  |  |  |  |  | 
| 298 | 1 | 50 |  |  |  | 5 | if ( $self->Debug ) { | 
| 299 | 0 |  |  |  |  | 0 | $self->_debug( "Started at " . localtime() ); | 
| 300 | 0 |  |  |  |  | 0 | $self->_debug("Using Mail::IMAPClient version $VERSION on perl $]"); | 
| 301 |  |  |  |  |  |  | } | 
| 302 |  |  |  |  |  |  |  | 
| 303 |  |  |  |  |  |  | # BUG? return undef on Socket() failure? | 
| 304 |  |  |  |  |  |  | $self->Socket( $self->{Socket} ) | 
| 305 | 1 | 50 |  |  |  | 3 | if $self->{Socket}; | 
| 306 |  |  |  |  |  |  |  | 
| 307 | 1 | 50 |  |  |  | 3 | if ( $self->{Rawsocket} ) { | 
| 308 | 0 |  |  |  |  | 0 | my $sock = delete $self->{Rawsocket}; | 
| 309 |  |  |  |  |  |  |  | 
| 310 |  |  |  |  |  |  | # Ignore Rawsocket if Socket is set.  BUG? should we carp/croak? | 
| 311 | 0 | 0 |  |  |  | 0 | $self->RawSocket($sock) unless $self->{Socket}; | 
| 312 |  |  |  |  |  |  | } | 
| 313 |  |  |  |  |  |  |  | 
| 314 | 1 | 50 | 33 |  |  | 5 | if ( !$self->{Socket} && $self->{Server} ) { | 
| 315 | 0 | 0 |  |  |  | 0 | $self->connect or return undef; | 
| 316 |  |  |  |  |  |  | } | 
| 317 | 1 |  |  |  |  | 2 | return $self; | 
| 318 |  |  |  |  |  |  | } | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | sub connect(@) { | 
| 321 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | # BUG? We should restrict which keys can be passed/set here. | 
| 324 | 0 | 0 |  |  |  | 0 | %$self = ( %$self, @_ ) if @_; | 
| 325 |  |  |  |  |  |  |  | 
| 326 | 0 | 0 |  |  |  | 0 | my @sockargs = $self->Timeout ? ( Timeout => $self->Timeout ) : (); | 
| 327 | 0 | 0 |  |  |  | 0 | push( @sockargs, $self->Debug ? ( Debug => $self->Debug ) : () ); | 
| 328 |  |  |  |  |  |  |  | 
| 329 |  |  |  |  |  |  | # give caller control of IO::Socket::... args to new if desired | 
| 330 | 0 | 0 | 0 |  |  | 0 | if ( $self->Socketargs and ref $self->Socketargs eq "ARRAY" ) { | 
| 331 | 0 |  |  |  |  | 0 | push( @sockargs, @{ $self->Socketargs } ); | 
|  | 0 |  |  |  |  | 0 |  | 
| 332 |  |  |  |  |  |  | } | 
| 333 |  |  |  |  |  |  |  | 
| 334 |  |  |  |  |  |  | # if no server, use " " to induce a non-fatal error | 
| 335 | 0 |  | 0 |  |  | 0 | my $server = $self->Server || " "; | 
| 336 | 0 |  | 0 |  |  | 0 | my $port = $self->Port || $self->Port( $self->Ssl ? "993" : "143" ); | 
| 337 | 0 |  |  |  |  | 0 | my ( $ioclass, $sock ); | 
| 338 |  |  |  |  |  |  |  | 
| 339 | 0 | 0 |  |  |  | 0 | if ( File::Spec->file_name_is_absolute($server) ) { | 
| 340 | 0 |  |  |  |  | 0 | $ioclass = $self->_load_module("UNIX"); | 
| 341 | 0 |  |  |  |  | 0 | unshift( @sockargs, Peer => $server ); | 
| 342 |  |  |  |  |  |  | } | 
| 343 |  |  |  |  |  |  | else { | 
| 344 | 0 |  |  |  |  | 0 | unshift( | 
| 345 |  |  |  |  |  |  | @sockargs, | 
| 346 |  |  |  |  |  |  | PeerAddr => $server, | 
| 347 |  |  |  |  |  |  | PeerPort => $port, | 
| 348 |  |  |  |  |  |  | Proto    => "tcp", | 
| 349 |  |  |  |  |  |  | ); | 
| 350 |  |  |  |  |  |  |  | 
| 351 |  |  |  |  |  |  | # pass SSL args if requested; default to IO::Socket::(IP|INET) | 
| 352 | 0 | 0 |  |  |  | 0 | if ( $self->Ssl ) { | 
| 353 | 0 |  |  |  |  | 0 | $ioclass = $self->_load_module("SSL"); | 
| 354 | 0 | 0 |  |  |  | 0 | push( @sockargs, @{ $self->Ssl } ) if ref $self->Ssl eq "ARRAY"; | 
|  | 0 |  |  |  |  | 0 |  | 
| 355 |  |  |  |  |  |  | } | 
| 356 |  |  |  |  |  |  | else { | 
| 357 | 0 |  |  |  |  | 0 | $ioclass = $self->_load_module("IP"); | 
| 358 | 0 | 0 |  |  |  | 0 | $ioclass = $self->_load_module("INET") unless $ioclass; | 
| 359 |  |  |  |  |  |  | } | 
| 360 |  |  |  |  |  |  | } | 
| 361 |  |  |  |  |  |  |  | 
| 362 | 0 | 0 |  |  |  | 0 | if ($ioclass) { | 
| 363 | 0 |  |  |  |  | 0 | $self->_debug("Connecting with $ioclass @sockargs"); | 
| 364 | 0 |  |  |  |  | 0 | $sock = $ioclass->new(@sockargs); | 
| 365 |  |  |  |  |  |  | } | 
| 366 |  |  |  |  |  |  |  | 
| 367 | 0 | 0 |  |  |  | 0 | if ($sock) { | 
| 368 | 0 | 0 |  |  |  | 0 | $self->_debug( "Connected to $server" . ( $! ? " errno($!)" : "" ) ); | 
| 369 | 0 |  |  |  |  | 0 | return $self->Socket($sock); | 
| 370 |  |  |  |  |  |  | } | 
| 371 |  |  |  |  |  |  | else { | 
| 372 | 0 |  |  |  |  | 0 | my $lasterr = $self->LastError; | 
| 373 | 0 | 0 | 0 |  |  | 0 | if ( !$lasterr and $self->Ssl and $ioclass ) { | 
|  |  |  | 0 |  |  |  |  | 
| 374 | 0 |  |  |  |  | 0 | $lasterr = $ioclass->errstr; | 
| 375 |  |  |  |  |  |  | } | 
| 376 | 0 |  | 0 |  |  | 0 | $lasterr ||= ""; | 
| 377 | 0 |  |  |  |  | 0 | $self->LastError("Unable to connect to $server: $lasterr"); | 
| 378 | 0 |  |  |  |  | 0 | return undef; | 
| 379 |  |  |  |  |  |  | } | 
| 380 |  |  |  |  |  |  | } | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | sub RawSocket(;$) { | 
| 383 | 0 |  |  | 0 | 1 | 0 | my ( $self, $sock ) = @_; | 
| 384 |  |  |  |  |  |  | defined $sock | 
| 385 | 0 | 0 |  |  |  | 0 | or return $self->{Socket}; | 
| 386 |  |  |  |  |  |  |  | 
| 387 | 0 |  |  |  |  | 0 | $self->{Socket}  = $sock; | 
| 388 | 0 |  |  |  |  | 0 | $self->{_select} = IO::Select->new($sock); | 
| 389 |  |  |  |  |  |  |  | 
| 390 | 0 |  |  |  |  | 0 | delete $self->{_fcntl}; | 
| 391 | 0 |  |  |  |  | 0 | $self->Fast_io( $self->Fast_io ); | 
| 392 |  |  |  |  |  |  |  | 
| 393 | 0 |  |  |  |  | 0 | return $sock; | 
| 394 |  |  |  |  |  |  | } | 
| 395 |  |  |  |  |  |  |  | 
| 396 |  |  |  |  |  |  | sub Socket($) { | 
| 397 | 0 |  |  | 0 | 1 | 0 | my ( $self, $sock ) = @_; | 
| 398 |  |  |  |  |  |  | defined $sock | 
| 399 | 0 | 0 |  |  |  | 0 | or return $self->{Socket}; | 
| 400 |  |  |  |  |  |  |  | 
| 401 | 0 |  |  |  |  | 0 | $self->RawSocket($sock); | 
| 402 | 0 |  |  |  |  | 0 | $self->State(Connected); | 
| 403 |  |  |  |  |  |  |  | 
| 404 | 0 | 0 |  |  |  | 0 | setsockopt( $sock, SOL_SOCKET, SO_KEEPALIVE, 1 ) if $self->Keepalive; | 
| 405 |  |  |  |  |  |  |  | 
| 406 |  |  |  |  |  |  | # LastError may be set by _read_line via _get_response | 
| 407 |  |  |  |  |  |  | # look for "* (OK|BAD|NO|PREAUTH)" | 
| 408 | 0 | 0 |  |  |  | 0 | my $code = $self->_get_response( '*', 'PREAUTH' ) or return undef; | 
| 409 |  |  |  |  |  |  |  | 
| 410 | 0 | 0 | 0 |  |  | 0 | if ( $code eq 'BYE' || $code eq 'NO' ) { | 
|  |  | 0 |  |  |  |  |  | 
| 411 | 0 |  |  |  |  | 0 | $self->State(Unconnected); | 
| 412 | 0 |  |  |  |  | 0 | return undef; | 
| 413 |  |  |  |  |  |  | } | 
| 414 |  |  |  |  |  |  | elsif ( $code eq 'PREAUTH' ) { | 
| 415 | 0 |  |  |  |  | 0 | $self->State(Authenticated); | 
| 416 | 0 |  |  |  |  | 0 | return $self; | 
| 417 |  |  |  |  |  |  | } | 
| 418 |  |  |  |  |  |  |  | 
| 419 | 0 | 0 |  |  |  | 0 | if ( $self->Starttls ) { | 
| 420 | 0 | 0 |  |  |  | 0 | $self->starttls or return undef; | 
| 421 |  |  |  |  |  |  | } | 
| 422 |  |  |  |  |  |  |  | 
| 423 | 0 | 0 | 0 |  |  | 0 | if ( defined $self->User && defined $self->Password ) { | 
| 424 | 0 | 0 |  |  |  | 0 | $self->login or return undef; | 
| 425 |  |  |  |  |  |  | } | 
| 426 |  |  |  |  |  |  |  | 
| 427 | 0 |  |  |  |  | 0 | return $self->{Socket}; | 
| 428 |  |  |  |  |  |  | } | 
| 429 |  |  |  |  |  |  |  | 
| 430 |  |  |  |  |  |  | # RFC2595 section 3.1 | 
| 431 |  |  |  |  |  |  | sub starttls { | 
| 432 | 0 |  |  | 0 | 1 | 0 | my ($self) = @_; | 
| 433 |  |  |  |  |  |  |  | 
| 434 |  |  |  |  |  |  | # BUG? RFC requirement checks commented out for now... | 
| 435 |  |  |  |  |  |  | #if ( $self->IsUnconnected or $self->IsAuthenticated ) { | 
| 436 |  |  |  |  |  |  | #    $self->LastError("NO must be connected but not authenticated"); | 
| 437 |  |  |  |  |  |  | #    return undef; | 
| 438 |  |  |  |  |  |  | #} | 
| 439 |  |  |  |  |  |  |  | 
| 440 |  |  |  |  |  |  | # BUG? strict check on capability commented out for now... | 
| 441 |  |  |  |  |  |  | #return undef unless $self->has_capability("STARTTLS"); | 
| 442 |  |  |  |  |  |  |  | 
| 443 | 0 | 0 |  |  |  | 0 | $self->_imap_command("STARTTLS") or return undef; | 
| 444 |  |  |  |  |  |  |  | 
| 445 |  |  |  |  |  |  | # MUST discard cached capability info; should re-issue capability command | 
| 446 | 0 |  |  |  |  | 0 | delete $self->{CAPABILITY}; | 
| 447 |  |  |  |  |  |  |  | 
| 448 | 0 | 0 |  |  |  | 0 | my $ioclass  = $self->_load_module("SSL") or return undef; | 
| 449 | 0 |  |  |  |  | 0 | my $sock     = $self->RawSocket; | 
| 450 | 0 |  |  |  |  | 0 | my $blocking = $sock->blocking; | 
| 451 |  |  |  |  |  |  |  | 
| 452 |  |  |  |  |  |  | # BUG: force blocking for now | 
| 453 | 0 |  |  |  |  | 0 | $sock->blocking(1); | 
| 454 |  |  |  |  |  |  |  | 
| 455 |  |  |  |  |  |  | # give caller control of args to start_SSL if desired | 
| 456 |  |  |  |  |  |  | my @sslargs = | 
| 457 |  |  |  |  |  |  | ( $self->Starttls and ref( $self->Starttls ) eq "ARRAY" ) | 
| 458 | 0 | 0 | 0 |  |  | 0 | ? ( @{ $self->Starttls } ) | 
|  | 0 |  |  |  |  | 0 |  | 
| 459 |  |  |  |  |  |  | : ( Timeout => 30 ); | 
| 460 |  |  |  |  |  |  |  | 
| 461 | 0 | 0 |  |  |  | 0 | unless ( $ioclass->start_SSL( $sock, @sslargs ) ) { | 
| 462 | 0 |  |  |  |  | 0 | $self->LastError( "Unable to start TLS: " . $ioclass->errstr ); | 
| 463 | 0 |  |  |  |  | 0 | return undef; | 
| 464 |  |  |  |  |  |  | } | 
| 465 |  |  |  |  |  |  |  | 
| 466 |  |  |  |  |  |  | # return blocking to previous setting | 
| 467 | 0 |  |  |  |  | 0 | $sock->blocking($blocking); | 
| 468 |  |  |  |  |  |  |  | 
| 469 | 0 |  |  |  |  | 0 | return $self; | 
| 470 |  |  |  |  |  |  | } | 
| 471 |  |  |  |  |  |  |  | 
| 472 |  |  |  |  |  |  | # RFC4978 COMPRESS | 
| 473 |  |  |  |  |  |  | sub compress { | 
| 474 | 0 |  |  | 0 | 1 | 0 | my ($self) = @_; | 
| 475 |  |  |  |  |  |  |  | 
| 476 |  |  |  |  |  |  | # BUG? strict check on capability commented out for now... | 
| 477 |  |  |  |  |  |  | #my $can = $self->has_capability("COMPRESS") | 
| 478 |  |  |  |  |  |  | #return undef unless $can and $can eq "DEFLATE"; | 
| 479 |  |  |  |  |  |  |  | 
| 480 | 0 | 0 |  |  |  | 0 | $self->_imap_command("COMPRESS DEFLATE") or return undef; | 
| 481 |  |  |  |  |  |  |  | 
| 482 | 0 | 0 |  |  |  | 0 | my $zcl = $self->_load_module("Compress-Zlib") or return undef; | 
| 483 |  |  |  |  |  |  |  | 
| 484 |  |  |  |  |  |  | # give caller control of args if desired | 
| 485 | 0 | 0 | 0 |  |  | 0 | $self->Compress( | 
| 486 |  |  |  |  |  |  | [ | 
| 487 |  |  |  |  |  |  | -WindowBits => -$zcl->MAX_WBITS(), | 
| 488 |  |  |  |  |  |  | -Level      => $zcl->Z_BEST_SPEED() | 
| 489 |  |  |  |  |  |  | ] | 
| 490 |  |  |  |  |  |  | ) unless ( $self->Compress and ref( $self->Compress ) eq "ARRAY" ); | 
| 491 |  |  |  |  |  |  |  | 
| 492 | 0 |  |  |  |  | 0 | my ( $rc, $do, $io ); | 
| 493 |  |  |  |  |  |  |  | 
| 494 | 0 |  |  |  |  | 0 | ( $do, $rc ) = Compress::Zlib::deflateInit( @{ $self->Compress } ); | 
|  | 0 |  |  |  |  | 0 |  | 
| 495 | 0 | 0 |  |  |  | 0 | unless ( $rc == $zcl->Z_OK ) { | 
| 496 | 0 |  |  |  |  | 0 | $self->LastError("deflateInit failed (rc=$rc)"); | 
| 497 | 0 |  |  |  |  | 0 | return undef; | 
| 498 |  |  |  |  |  |  | } | 
| 499 |  |  |  |  |  |  |  | 
| 500 | 0 |  |  |  |  | 0 | ( $io, $rc ) = | 
| 501 |  |  |  |  |  |  | Compress::Zlib::inflateInit( -WindowBits => -$zcl->MAX_WBITS() ); | 
| 502 | 0 | 0 |  |  |  | 0 | unless ( $rc == $zcl->Z_OK ) { | 
| 503 | 0 |  |  |  |  | 0 | $self->LastError("inflateInit failed (rc=$rc)"); | 
| 504 | 0 |  |  |  |  | 0 | return undef; | 
| 505 |  |  |  |  |  |  | } | 
| 506 |  |  |  |  |  |  |  | 
| 507 |  |  |  |  |  |  | $self->{Prewritemethod} = sub { | 
| 508 | 0 |  |  | 0 |  | 0 | my ( $imap, $string ) = @_; | 
| 509 |  |  |  |  |  |  |  | 
| 510 | 0 |  |  |  |  | 0 | my ( $rc, $out1, $out2 ); | 
| 511 | 0 |  |  |  |  | 0 | ( $out1, $rc ) = $do->deflate($string); | 
| 512 | 0 | 0 |  |  |  | 0 | ( $out2, $rc ) = $do->flush( $zcl->Z_PARTIAL_FLUSH() ) | 
| 513 |  |  |  |  |  |  | unless ( $rc != $zcl->Z_OK ); | 
| 514 |  |  |  |  |  |  |  | 
| 515 | 0 | 0 |  |  |  | 0 | unless ( $rc == $zcl->Z_OK ) { | 
| 516 | 0 |  |  |  |  | 0 | $self->LastError("deflate/flush failed (rc=$rc)"); | 
| 517 | 0 |  |  |  |  | 0 | return undef; | 
| 518 |  |  |  |  |  |  | } | 
| 519 |  |  |  |  |  |  |  | 
| 520 | 0 |  |  |  |  | 0 | return $out1 . $out2; | 
| 521 | 0 |  |  |  |  | 0 | }; | 
| 522 |  |  |  |  |  |  |  | 
| 523 |  |  |  |  |  |  | # need to retain some state for Readmoremethod/Readmethod calls | 
| 524 | 0 |  |  |  |  | 0 | my ( $Zbuf, $Ibuf ) = ( "", "" ); | 
| 525 |  |  |  |  |  |  |  | 
| 526 |  |  |  |  |  |  | $self->{Readmoremethod} = sub { | 
| 527 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 528 | 0 | 0 | 0 |  |  | 0 | return 1 if ( length($Zbuf) || length($Ibuf) ); | 
| 529 | 0 |  |  |  |  | 0 | $self->__read_more(@_); | 
| 530 | 0 |  |  |  |  | 0 | }; | 
| 531 |  |  |  |  |  |  |  | 
| 532 |  |  |  |  |  |  | $self->{Readmethod} = sub { | 
| 533 | 0 |  |  | 0 |  | 0 | my ( $imap, $fh, $buf, $len, $off ) = @_; | 
| 534 |  |  |  |  |  |  |  | 
| 535 |  |  |  |  |  |  | # get more data, but empty $Ibuf first if any data is left | 
| 536 | 0 |  |  |  |  | 0 | my ( $lz, $li ) = ( length $Zbuf, length $Ibuf ); | 
| 537 | 0 | 0 | 0 |  |  | 0 | if ( $lz || !$li ) { | 
| 538 | 0 |  |  |  |  | 0 | my $ret = sysread( $fh, $Zbuf, $len, length $Zbuf ); | 
| 539 | 0 |  |  |  |  | 0 | $lz = length $Zbuf; | 
| 540 | 0 | 0 | 0 |  |  | 0 | return $ret if ( !$ret && !$lz );    # $ret is undef or 0 | 
| 541 |  |  |  |  |  |  | } | 
| 542 |  |  |  |  |  |  |  | 
| 543 |  |  |  |  |  |  | # accumulate inflated data in $Ibuf | 
| 544 | 0 | 0 |  |  |  | 0 | if ($lz) { | 
| 545 | 0 |  |  |  |  | 0 | my ( $tbuf, $rc ) = $io->inflate( \$Zbuf ); | 
| 546 | 0 | 0 |  |  |  | 0 | unless ( $rc == $zcl->Z_OK ) { | 
| 547 | 0 |  |  |  |  | 0 | $self->LastError("inflate failed (rc=$rc)"); | 
| 548 | 0 |  |  |  |  | 0 | return undef; | 
| 549 |  |  |  |  |  |  | } | 
| 550 | 0 |  |  |  |  | 0 | $Ibuf .= $tbuf; | 
| 551 |  |  |  |  |  |  | } | 
| 552 |  |  |  |  |  |  |  | 
| 553 |  |  |  |  |  |  | # pull desired length of data from $Ibuf | 
| 554 | 0 |  |  |  |  | 0 | my $tbuf = substr( $Ibuf, 0, $len ); | 
| 555 | 0 |  |  |  |  | 0 | substr( $Ibuf, 0, $len ) = ""; | 
| 556 | 0 |  |  |  |  | 0 | substr( $$buf, $off ) = $tbuf; | 
| 557 |  |  |  |  |  |  |  | 
| 558 | 0 |  |  |  |  | 0 | return length $tbuf; | 
| 559 | 0 |  |  |  |  | 0 | }; | 
| 560 |  |  |  |  |  |  |  | 
| 561 | 0 |  |  |  |  | 0 | return $self; | 
| 562 |  |  |  |  |  |  | } | 
| 563 |  |  |  |  |  |  |  | 
| 564 |  |  |  |  |  |  | sub login { | 
| 565 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 566 | 0 |  |  |  |  | 0 | my $auth = $self->Authmechanism; | 
| 567 |  |  |  |  |  |  |  | 
| 568 | 0 | 0 | 0 |  |  | 0 | if ( $auth && $auth ne 'LOGIN' ) { | 
| 569 | 0 | 0 |  |  |  | 0 | $self->authenticate( $auth, $self->Authcallback ) | 
| 570 |  |  |  |  |  |  | or return undef; | 
| 571 |  |  |  |  |  |  | } | 
| 572 |  |  |  |  |  |  | else { | 
| 573 | 0 |  |  |  |  | 0 | my $user   = $self->User; | 
| 574 | 0 |  |  |  |  | 0 | my $passwd = $self->Password; | 
| 575 |  |  |  |  |  |  |  | 
| 576 | 0 | 0 | 0 |  |  | 0 | return undef unless ( defined($passwd) and defined($user) ); | 
| 577 |  |  |  |  |  |  |  | 
| 578 |  |  |  |  |  |  | # if user is passed as a literal: | 
| 579 |  |  |  |  |  |  | # 1. send passwd as a literal | 
| 580 |  |  |  |  |  |  | # 2. empty literal passwd are sent as an blank line ($CRLF) | 
| 581 | 0 |  |  |  |  | 0 | $user = $self->Quote($user); | 
| 582 | 0 | 0 |  |  |  | 0 | if ( $user =~ /^{/ ) { | 
| 583 | 0 | 0 |  |  |  | 0 | my $nopasswd = ( $passwd eq "" ) ? 1 : 0; | 
| 584 | 0 |  |  |  |  | 0 | $passwd = $self->Quote( $passwd, 1 );    # force literal | 
| 585 | 0 | 0 |  |  |  | 0 | $passwd .= $CRLF if ($nopasswd);         # blank line | 
| 586 |  |  |  |  |  |  | } | 
| 587 |  |  |  |  |  |  | else { | 
| 588 | 0 |  |  |  |  | 0 | $passwd = $self->Quote($passwd); | 
| 589 |  |  |  |  |  |  | } | 
| 590 |  |  |  |  |  |  |  | 
| 591 | 0 | 0 |  |  |  | 0 | $self->_imap_command("LOGIN $user $passwd") | 
| 592 |  |  |  |  |  |  | or return undef; | 
| 593 |  |  |  |  |  |  | } | 
| 594 |  |  |  |  |  |  |  | 
| 595 | 0 |  |  |  |  | 0 | $self->State(Authenticated); | 
| 596 | 0 | 0 |  |  |  | 0 | if ( $self->Compress ) { | 
| 597 | 0 | 0 |  |  |  | 0 | $self->compress or return undef; | 
| 598 |  |  |  |  |  |  | } | 
| 599 | 0 |  |  |  |  | 0 | return $self; | 
| 600 |  |  |  |  |  |  | } | 
| 601 |  |  |  |  |  |  |  | 
| 602 |  |  |  |  |  |  | sub noop { | 
| 603 | 0 |  |  | 0 | 1 | 0 | my ($self) = @_; | 
| 604 | 0 | 0 |  |  |  | 0 | $self->_imap_command("NOOP") ? $self->Results : undef; | 
| 605 |  |  |  |  |  |  | } | 
| 606 |  |  |  |  |  |  |  | 
| 607 |  |  |  |  |  |  | sub proxyauth { | 
| 608 | 0 |  |  | 0 | 1 | 0 | my ( $self, $user ) = @_; | 
| 609 | 0 |  |  |  |  | 0 | $user = $self->Quote($user); | 
| 610 | 0 | 0 |  |  |  | 0 | $self->_imap_command("PROXYAUTH $user") ? $self->Results : undef; | 
| 611 |  |  |  |  |  |  | } | 
| 612 |  |  |  |  |  |  |  | 
| 613 |  |  |  |  |  |  | sub separator { | 
| 614 | 0 |  |  | 0 | 1 | 0 | my ( $self, $target ) = @_; | 
| 615 | 0 | 0 |  |  |  | 0 | unless ( defined $target ) { | 
| 616 |  |  |  |  |  |  |  | 
| 617 |  |  |  |  |  |  | # separator is namespace's 1st thing's 1st thing's 2nd thing: | 
| 618 | 0 | 0 |  |  |  | 0 | my $ns = $self->namespace or return undef; | 
| 619 | 0 | 0 |  |  |  | 0 | if ($ns) { | 
| 620 | 0 |  |  |  |  | 0 | my $sep = $ns->[0][0][1]; | 
| 621 | 0 | 0 |  |  |  | 0 | return $sep if $sep; | 
| 622 |  |  |  |  |  |  | } | 
| 623 | 0 |  |  |  |  | 0 | $target = ''; | 
| 624 |  |  |  |  |  |  | } | 
| 625 |  |  |  |  |  |  |  | 
| 626 |  |  |  |  |  |  | return $self->{separators}{$target} | 
| 627 | 0 | 0 |  |  |  | 0 | if exists $self->{separators}{$target}; | 
| 628 |  |  |  |  |  |  |  | 
| 629 | 0 | 0 |  |  |  | 0 | my $list = $self->list( undef, $target ) or return undef; | 
| 630 |  |  |  |  |  |  |  | 
| 631 | 0 |  |  |  |  | 0 | foreach my $line (@$list) { | 
| 632 | 0 |  |  |  |  | 0 | my $rec = $self->_list_or_lsub_response_parse($line); | 
| 633 | 0 | 0 |  |  |  | 0 | next unless defined $rec->{name}; | 
| 634 | 0 |  |  |  |  | 0 | $self->{separators}{ $rec->{name} } = $rec->{delim}; | 
| 635 |  |  |  |  |  |  | } | 
| 636 | 0 |  |  |  |  | 0 | return $self->{separators}{$target}; | 
| 637 |  |  |  |  |  |  | } | 
| 638 |  |  |  |  |  |  |  | 
| 639 |  |  |  |  |  |  | # BUG? caller gets empty list even if Error | 
| 640 |  |  |  |  |  |  | # - returning an array with a single undef value seems even worse though | 
| 641 |  |  |  |  |  |  | sub sort { | 
| 642 | 0 |  |  | 0 | 1 | 0 | my ( $self, $crit, @a ) = @_; | 
| 643 |  |  |  |  |  |  |  | 
| 644 | 0 | 0 |  |  |  | 0 | $crit =~ /^\(.*\)$/    # wrap criteria in parens | 
| 645 |  |  |  |  |  |  | or $crit = "($crit)"; | 
| 646 |  |  |  |  |  |  |  | 
| 647 | 0 |  |  |  |  | 0 | my @hits; | 
| 648 | 0 | 0 |  |  |  | 0 | if ( $self->_imap_uid_command( SORT => $crit, @a ) ) { | 
| 649 | 0 |  |  |  |  | 0 | my @results = $self->History; | 
| 650 | 0 |  |  |  |  | 0 | foreach (@results) { | 
| 651 | 0 |  |  |  |  | 0 | chomp; | 
| 652 | 0 |  |  |  |  | 0 | s/$CR$//; | 
| 653 | 0 | 0 |  |  |  | 0 | s/^\*\s+SORT\s+// or next; | 
| 654 | 0 |  |  |  |  | 0 | push @hits, grep /\d/, split; | 
| 655 |  |  |  |  |  |  | } | 
| 656 |  |  |  |  |  |  | } | 
| 657 | 0 | 0 |  |  |  | 0 | return wantarray ? @hits : \@hits; | 
| 658 |  |  |  |  |  |  | } | 
| 659 |  |  |  |  |  |  |  | 
| 660 |  |  |  |  |  |  | sub _list_or_lsub { | 
| 661 | 0 |  |  | 0 |  | 0 | my ( $self, $cmd, $reference, $target ) = @_; | 
| 662 | 0 | 0 |  |  |  | 0 | defined $reference or $reference = ''; | 
| 663 | 0 | 0 |  |  |  | 0 | defined $target    or $target    = '*'; | 
| 664 | 0 | 0 |  |  |  | 0 | length $target     or $target    = '""'; | 
| 665 |  |  |  |  |  |  |  | 
| 666 | 0 | 0 | 0 |  |  | 0 | $target eq '*' || $target eq '""' | 
| 667 |  |  |  |  |  |  | or $target = $self->Quote($target); | 
| 668 |  |  |  |  |  |  |  | 
| 669 | 0 | 0 |  |  |  | 0 | $self->_imap_command(qq($cmd "$reference" $target)) | 
| 670 |  |  |  |  |  |  | or return undef; | 
| 671 |  |  |  |  |  |  |  | 
| 672 | 0 | 0 |  |  |  | 0 | return wantarray ? $self->Escaped_history : $self->Escaped_results; | 
| 673 |  |  |  |  |  |  | } | 
| 674 |  |  |  |  |  |  |  | 
| 675 | 0 |  |  | 0 | 1 | 0 | sub list { shift->_list_or_lsub( "LIST", @_ ) } | 
| 676 | 0 |  |  | 0 | 1 | 0 | sub lsub { shift->_list_or_lsub( "LSUB", @_ ) } | 
| 677 |  |  |  |  |  |  |  | 
| 678 |  |  |  |  |  |  | # deprecated 3.34 | 
| 679 |  |  |  |  |  |  | sub xlist { | 
| 680 | 0 |  |  | 0 | 0 | 0 | my ($self) = @_; | 
| 681 | 0 | 0 |  |  |  | 0 | return undef unless $self->has_capability("XLIST"); | 
| 682 | 0 |  |  |  |  | 0 | shift->_list_or_lsub( "XLIST", @_ ); | 
| 683 |  |  |  |  |  |  | } | 
| 684 |  |  |  |  |  |  |  | 
| 685 |  |  |  |  |  |  | sub _folders_or_subscribed { | 
| 686 | 0 |  |  | 0 |  | 0 | my ( $self, $method, $what ) = @_; | 
| 687 | 0 |  |  |  |  | 0 | my @folders; | 
| 688 |  |  |  |  |  |  |  | 
| 689 |  |  |  |  |  |  | # do BLOCK allowing use of "last if undef/error" and avoiding dup code | 
| 690 | 0 |  |  |  |  | 0 | do { | 
| 691 |  |  |  |  |  |  | { | 
| 692 | 0 |  |  |  |  | 0 | my @list; | 
|  | 0 |  |  |  |  | 0 |  | 
| 693 | 0 | 0 |  |  |  | 0 | if ($what) { | 
| 694 | 0 |  | 0 |  |  | 0 | my $sep = $self->separator($what) || $self->separator(undef); | 
| 695 | 0 | 0 |  |  |  | 0 | last unless defined $sep; | 
| 696 |  |  |  |  |  |  |  | 
| 697 | 0 | 0 |  |  |  | 0 | my $whatsub = $what =~ m/\Q${sep}\E$/ ? "$what*" : "$what$sep*"; | 
| 698 |  |  |  |  |  |  |  | 
| 699 | 0 | 0 |  |  |  | 0 | my $tref = $self->$method( undef, $whatsub ) or last; | 
| 700 | 0 |  |  |  |  | 0 | shift @$tref;    # remove command | 
| 701 | 0 |  |  |  |  | 0 | push @list, @$tref; | 
| 702 |  |  |  |  |  |  |  | 
| 703 |  |  |  |  |  |  | # BUG?: this behavior has been around since 2.x, why? | 
| 704 | 0 |  |  |  |  | 0 | my $cansel = $self->selectable($what); | 
| 705 | 0 | 0 |  |  |  | 0 | last unless defined $cansel; | 
| 706 | 0 | 0 |  |  |  | 0 | if ($cansel) { | 
| 707 | 0 | 0 |  |  |  | 0 | $tref = $self->$method( undef, $what ) or last; | 
| 708 | 0 |  |  |  |  | 0 | shift @$tref;    # remove command | 
| 709 | 0 |  |  |  |  | 0 | push @list, @$tref; | 
| 710 |  |  |  |  |  |  | } | 
| 711 |  |  |  |  |  |  | } | 
| 712 |  |  |  |  |  |  | else { | 
| 713 | 0 | 0 |  |  |  | 0 | my $tref = $self->$method( undef, undef ) or last; | 
| 714 | 0 |  |  |  |  | 0 | shift @$tref;        # remove command | 
| 715 | 0 |  |  |  |  | 0 | push @list, @$tref; | 
| 716 |  |  |  |  |  |  | } | 
| 717 |  |  |  |  |  |  |  | 
| 718 | 0 |  |  |  |  | 0 | foreach my $resp (@list) { | 
| 719 | 0 |  |  |  |  | 0 | my $rec = $self->_list_or_lsub_response_parse($resp); | 
| 720 | 0 | 0 |  |  |  | 0 | next unless defined $rec->{name}; | 
| 721 | 0 | 0 |  | 0 |  | 0 | next if first { lc($_) eq '\noselect' } @{ $rec->{attrs} }; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 722 | 0 |  |  |  |  | 0 | push @folders, $rec; | 
| 723 |  |  |  |  |  |  | } | 
| 724 |  |  |  |  |  |  | } | 
| 725 |  |  |  |  |  |  | }; | 
| 726 |  |  |  |  |  |  |  | 
| 727 | 0 |  |  |  |  | 0 | my @clean = _remove_doubles @folders; | 
| 728 | 0 | 0 |  |  |  | 0 | return wantarray ? @clean : \@clean; | 
| 729 |  |  |  |  |  |  | } | 
| 730 |  |  |  |  |  |  |  | 
| 731 |  |  |  |  |  |  | sub folders { | 
| 732 | 0 |  |  | 0 | 1 | 0 | my ( $self, $what ) = @_; | 
| 733 |  |  |  |  |  |  |  | 
| 734 |  |  |  |  |  |  | my @folders = | 
| 735 | 0 |  |  |  |  | 0 | map( $_->{name}, $self->_folders_or_subscribed( "list", $what ) ); | 
| 736 | 0 | 0 |  |  |  | 0 | return wantarray ? @folders : \@folders; | 
| 737 |  |  |  |  |  |  | } | 
| 738 |  |  |  |  |  |  |  | 
| 739 |  |  |  |  |  |  | sub folders_hash { | 
| 740 | 0 |  |  | 0 | 1 | 0 | my ( $self, $what ) = @_; | 
| 741 |  |  |  |  |  |  |  | 
| 742 | 0 |  |  |  |  | 0 | my @folders_hash = $self->_folders_or_subscribed( "list", $what ); | 
| 743 | 0 | 0 |  |  |  | 0 | return wantarray ? @folders_hash : \@folders_hash; | 
| 744 |  |  |  |  |  |  | } | 
| 745 |  |  |  |  |  |  |  | 
| 746 |  |  |  |  |  |  | # deprecated 3.34 | 
| 747 |  |  |  |  |  |  | sub xlist_folders { | 
| 748 | 0 |  |  | 0 | 1 | 0 | my ($self) = @_; | 
| 749 | 0 |  |  |  |  | 0 | my $xlist = $self->xlist; | 
| 750 | 0 | 0 |  |  |  | 0 | return undef unless defined $xlist; | 
| 751 |  |  |  |  |  |  |  | 
| 752 | 0 |  |  |  |  | 0 | my %xlist; | 
| 753 | 0 |  |  |  |  | 0 | my $xlist_re = qr/\A\\(Inbox|AllMail|Trash|Drafts|Sent|Spam|Starred)\Z/; | 
| 754 |  |  |  |  |  |  |  | 
| 755 | 0 |  |  |  |  | 0 | for my $resp (@$xlist) { | 
| 756 | 0 |  |  |  |  | 0 | my $rec = $self->_list_or_lsub_response_parse($resp); | 
| 757 | 0 | 0 |  |  |  | 0 | next unless defined $rec->{name}; | 
| 758 | 0 |  |  |  |  | 0 | for my $attr ( @{ $rec->{attrs} } ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 759 | 0 | 0 |  |  |  | 0 | $xlist{$1} = $rec->{name} if ( $attr =~ $xlist_re ); | 
| 760 |  |  |  |  |  |  | } | 
| 761 |  |  |  |  |  |  | } | 
| 762 |  |  |  |  |  |  |  | 
| 763 | 0 | 0 |  |  |  | 0 | return wantarray ? %xlist : \%xlist; | 
| 764 |  |  |  |  |  |  | } | 
| 765 |  |  |  |  |  |  |  | 
| 766 |  |  |  |  |  |  | sub subscribed { | 
| 767 | 0 |  |  | 0 | 1 | 0 | my ( $self, $what ) = @_; | 
| 768 |  |  |  |  |  |  | my @folders = | 
| 769 | 0 |  |  |  |  | 0 | map( $_->{name}, $self->_folders_or_subscribed( "lsub", $what ) ); | 
| 770 | 0 | 0 |  |  |  | 0 | return wantarray ? @folders : \@folders; | 
| 771 |  |  |  |  |  |  | } | 
| 772 |  |  |  |  |  |  |  | 
| 773 |  |  |  |  |  |  | sub deleteacl { | 
| 774 | 0 |  |  | 0 | 1 | 0 | my ( $self, $target, $user ) = @_; | 
| 775 | 0 |  |  |  |  | 0 | $target = $self->Quote($target); | 
| 776 | 0 |  |  |  |  | 0 | $user   = $self->Quote($user); | 
| 777 |  |  |  |  |  |  |  | 
| 778 | 0 | 0 |  |  |  | 0 | $self->_imap_command(qq(DELETEACL $target $user)) | 
| 779 |  |  |  |  |  |  | or return undef; | 
| 780 |  |  |  |  |  |  |  | 
| 781 | 0 | 0 |  |  |  | 0 | return wantarray ? $self->History : $self->Results; | 
| 782 |  |  |  |  |  |  | } | 
| 783 |  |  |  |  |  |  |  | 
| 784 |  |  |  |  |  |  | sub setacl { | 
| 785 | 0 |  |  | 0 | 1 | 0 | my ( $self, $target, $user, $acl ) = @_; | 
| 786 | 0 |  | 0 |  |  | 0 | $target ||= $self->Folder; | 
| 787 | 0 |  |  |  |  | 0 | $target = $self->Quote($target); | 
| 788 |  |  |  |  |  |  |  | 
| 789 | 0 |  | 0 |  |  | 0 | $user ||= $self->User; | 
| 790 | 0 |  |  |  |  | 0 | $user = $self->Quote($user); | 
| 791 | 0 |  |  |  |  | 0 | $acl  = $self->Quote($acl); | 
| 792 |  |  |  |  |  |  |  | 
| 793 | 0 | 0 |  |  |  | 0 | $self->_imap_command(qq(SETACL $target $user $acl)) | 
| 794 |  |  |  |  |  |  | or return undef; | 
| 795 |  |  |  |  |  |  |  | 
| 796 | 0 | 0 |  |  |  | 0 | return wantarray ? $self->History : $self->Results; | 
| 797 |  |  |  |  |  |  | } | 
| 798 |  |  |  |  |  |  |  | 
| 799 |  |  |  |  |  |  | sub getacl { | 
| 800 | 0 |  |  | 0 | 1 | 0 | my ( $self, $target ) = @_; | 
| 801 | 0 | 0 |  |  |  | 0 | defined $target or $target = $self->Folder; | 
| 802 | 0 |  |  |  |  | 0 | my $mtarget = $self->Quote($target); | 
| 803 | 0 | 0 |  |  |  | 0 | $self->_imap_command(qq(GETACL $mtarget)) | 
| 804 |  |  |  |  |  |  | or return undef; | 
| 805 |  |  |  |  |  |  |  | 
| 806 | 0 |  |  |  |  | 0 | my @history = $self->History; | 
| 807 | 0 |  |  |  |  | 0 | my $hash; | 
| 808 | 0 |  |  |  |  | 0 | for ( my $x = 0 ; $x < @history ; $x++ ) { | 
| 809 | 0 | 0 |  |  |  | 0 | next if $history[$x] !~ /^\* ACL/; | 
| 810 |  |  |  |  |  |  |  | 
| 811 | 0 | 0 |  |  |  | 0 | my $perm = | 
| 812 |  |  |  |  |  |  | $history[$x] =~ /^\* ACL $/ | 
| 813 |  |  |  |  |  |  | ? $history[ ++$x ] . $history[ ++$x ] | 
| 814 |  |  |  |  |  |  | : $history[$x]; | 
| 815 |  |  |  |  |  |  |  | 
| 816 | 0 |  |  |  |  | 0 | $perm =~ s/\s?$CRLF$//o; | 
| 817 | 0 |  | 0 |  |  | 0 | until ( $perm =~ /\Q$target\E"?$/ || !$perm ) { | 
| 818 | 0 | 0 |  |  |  | 0 | $perm =~ s/\s([^\s]+)\s?$// or last; | 
| 819 | 0 |  |  |  |  | 0 | my $p = $1; | 
| 820 | 0 | 0 |  |  |  | 0 | $perm =~ s/\s([^\s]+)\s?$// or last; | 
| 821 | 0 |  |  |  |  | 0 | my $u = $1; | 
| 822 | 0 |  |  |  |  | 0 | $hash->{$u} = $p; | 
| 823 | 0 |  |  |  |  | 0 | $self->_debug("Permissions: $u => $p"); | 
| 824 |  |  |  |  |  |  | } | 
| 825 |  |  |  |  |  |  | } | 
| 826 | 0 |  |  |  |  | 0 | return $hash; | 
| 827 |  |  |  |  |  |  | } | 
| 828 |  |  |  |  |  |  |  | 
| 829 |  |  |  |  |  |  | sub listrights { | 
| 830 | 0 |  |  | 0 | 1 | 0 | my ( $self, $target, $user ) = @_; | 
| 831 | 0 |  | 0 |  |  | 0 | $target ||= $self->Folder; | 
| 832 | 0 |  |  |  |  | 0 | $target = $self->Quote($target); | 
| 833 |  |  |  |  |  |  |  | 
| 834 | 0 |  | 0 |  |  | 0 | $user ||= $self->User; | 
| 835 | 0 |  |  |  |  | 0 | $user = $self->Quote($user); | 
| 836 |  |  |  |  |  |  |  | 
| 837 | 0 | 0 |  |  |  | 0 | $self->_imap_command(qq(LISTRIGHTS $target $user)) | 
| 838 |  |  |  |  |  |  | or return undef; | 
| 839 |  |  |  |  |  |  |  | 
| 840 | 0 |  |  | 0 |  | 0 | my $resp = first { /^\* LISTRIGHTS/ } $self->History; | 
|  | 0 |  |  |  |  | 0 |  | 
| 841 | 0 |  |  |  |  | 0 | my @rights = split /\s/, $resp; | 
| 842 | 0 |  |  |  |  | 0 | my $rights = join '', @rights[ 4 .. $#rights ]; | 
| 843 | 0 |  |  |  |  | 0 | $rights =~ s/"//g; | 
| 844 | 0 | 0 |  |  |  | 0 | return wantarray ? split( //, $rights ) : $rights; | 
| 845 |  |  |  |  |  |  | } | 
| 846 |  |  |  |  |  |  |  | 
| 847 |  |  |  |  |  |  | sub select { | 
| 848 | 0 |  |  | 0 | 1 | 0 | my ( $self, $target ) = @_; | 
| 849 | 0 | 0 |  |  |  | 0 | defined $target or return undef; | 
| 850 |  |  |  |  |  |  |  | 
| 851 | 0 |  |  |  |  | 0 | my $qqtarget = $self->Quote($target); | 
| 852 | 0 |  |  |  |  | 0 | my $old      = $self->Folder; | 
| 853 |  |  |  |  |  |  |  | 
| 854 | 0 | 0 |  |  |  | 0 | $self->_imap_command("SELECT $qqtarget") | 
| 855 |  |  |  |  |  |  | or return undef; | 
| 856 |  |  |  |  |  |  |  | 
| 857 | 0 |  |  |  |  | 0 | $self->State(Selected); | 
| 858 | 0 |  |  |  |  | 0 | $self->Folder($target); | 
| 859 | 0 |  | 0 |  |  | 0 | return $old || $self;    # ??$self?? | 
| 860 |  |  |  |  |  |  | } | 
| 861 |  |  |  |  |  |  |  | 
| 862 |  |  |  |  |  |  | sub message_string { | 
| 863 | 0 |  |  | 0 | 1 | 0 | my ( $self, $msg ) = @_; | 
| 864 |  |  |  |  |  |  |  | 
| 865 | 0 | 0 |  |  |  | 0 | return undef unless defined $self->imap4rev1; | 
| 866 | 0 | 0 |  |  |  | 0 | my $peek = $self->Peek      ? '.PEEK'        : ''; | 
| 867 | 0 | 0 |  |  |  | 0 | my $cmd  = $self->imap4rev1 ? "BODY$peek\[]" : "RFC822$peek"; | 
| 868 |  |  |  |  |  |  |  | 
| 869 | 0 |  |  |  |  | 0 | my $string; | 
| 870 | 0 |  |  |  |  | 0 | $self->message_to_file( \$string, $msg ); | 
| 871 |  |  |  |  |  |  |  | 
| 872 | 0 | 0 |  |  |  | 0 | unless ( $self->Ignoresizeerrors ) {    # Check size with expected size | 
| 873 | 0 |  |  |  |  | 0 | my $expected_size = $self->size($msg); | 
| 874 | 0 | 0 |  |  |  | 0 | return undef unless defined $expected_size; | 
| 875 |  |  |  |  |  |  |  | 
| 876 |  |  |  |  |  |  | # RFC822.SIZE may be wrong, see RFC2683 3.4.5 "RFC822.SIZE" | 
| 877 | 0 | 0 |  |  |  | 0 | if ( length($string) != $expected_size ) { | 
| 878 | 0 |  |  |  |  | 0 | $self->LastError( "message_string() " | 
| 879 |  |  |  |  |  |  | . "expected $expected_size bytes but received " | 
| 880 |  |  |  |  |  |  | . length($string) | 
| 881 |  |  |  |  |  |  | . " you may need the IgnoreSizeErrors option" ); | 
| 882 | 0 |  |  |  |  | 0 | return undef; | 
| 883 |  |  |  |  |  |  | } | 
| 884 |  |  |  |  |  |  | } | 
| 885 |  |  |  |  |  |  |  | 
| 886 | 0 |  |  |  |  | 0 | return $string; | 
| 887 |  |  |  |  |  |  | } | 
| 888 |  |  |  |  |  |  |  | 
| 889 |  |  |  |  |  |  | sub bodypart_string { | 
| 890 | 0 |  |  | 0 | 1 | 0 | my ( $self, $msg, $partno, $bytes, $offset ) = @_; | 
| 891 |  |  |  |  |  |  |  | 
| 892 | 0 | 0 |  |  |  | 0 | unless ( $self->imap4rev1 ) { | 
| 893 | 0 | 0 |  |  |  | 0 | $self->LastError( "Unable to get body part; server " | 
| 894 |  |  |  |  |  |  | . $self->Server | 
| 895 |  |  |  |  |  |  | . " does not support IMAP4REV1" ) | 
| 896 |  |  |  |  |  |  | unless $self->LastError; | 
| 897 | 0 |  |  |  |  | 0 | return undef; | 
| 898 |  |  |  |  |  |  | } | 
| 899 |  |  |  |  |  |  |  | 
| 900 | 0 |  | 0 |  |  | 0 | $offset ||= 0; | 
| 901 | 0 | 0 |  |  |  | 0 | my $cmd = "BODY" | 
|  |  | 0 |  |  |  |  |  | 
| 902 |  |  |  |  |  |  | . ( $self->Peek ? '.PEEK' : '' ) | 
| 903 |  |  |  |  |  |  | . "[$partno]" | 
| 904 |  |  |  |  |  |  | . ( $bytes ? "<$offset.$bytes>" : '' ); | 
| 905 |  |  |  |  |  |  |  | 
| 906 | 0 | 0 |  |  |  | 0 | $self->fetch( $msg, $cmd ) | 
| 907 |  |  |  |  |  |  | or return undef; | 
| 908 |  |  |  |  |  |  |  | 
| 909 | 0 |  |  |  |  | 0 | $self->_transaction_literals; | 
| 910 |  |  |  |  |  |  | } | 
| 911 |  |  |  |  |  |  |  | 
| 912 |  |  |  |  |  |  | # message_to_file( $self, $file, @msgs ) | 
| 913 |  |  |  |  |  |  | sub message_to_file { | 
| 914 | 0 |  |  | 0 | 1 | 0 | my ( $self, $file, @msgs ) = @_; | 
| 915 |  |  |  |  |  |  |  | 
| 916 |  |  |  |  |  |  | # $file can be a name or a scalar reference (for in memory file) | 
| 917 |  |  |  |  |  |  | # avoid IO::File bug handling scalar refs in perl <= 5.8.8? | 
| 918 |  |  |  |  |  |  | # - buggy: $fh = IO::File->new( $file, 'r' ) | 
| 919 | 0 |  |  |  |  | 0 | my $fh; | 
| 920 | 0 | 0 | 0 |  |  | 0 | if ( ref $file and ref $file ne "SCALAR" ) { | 
| 921 | 0 |  |  |  |  | 0 | $fh = $file; | 
| 922 |  |  |  |  |  |  | } | 
| 923 |  |  |  |  |  |  | else { | 
| 924 | 0 | 0 | 0 |  |  | 0 | $$file = "" if ( ref $file eq "SCALAR" and !defined $$file ); | 
| 925 | 0 |  |  |  |  | 0 | local ($!); | 
| 926 | 0 |  |  |  |  | 0 | open( $fh, ">>", $file ); | 
| 927 | 0 | 0 |  |  |  | 0 | unless ( defined($fh) ) { | 
| 928 | 0 |  |  |  |  | 0 | $self->LastError("Unable to open file '$file': $!"); | 
| 929 | 0 |  |  |  |  | 0 | return undef; | 
| 930 |  |  |  |  |  |  | } | 
| 931 |  |  |  |  |  |  | } | 
| 932 |  |  |  |  |  |  |  | 
| 933 | 0 |  |  |  |  | 0 | binmode($fh); | 
| 934 |  |  |  |  |  |  |  | 
| 935 | 0 | 0 |  |  |  | 0 | unless (@msgs) { | 
| 936 | 0 |  |  |  |  | 0 | $self->LastError("message_to_file: NO messages specified!"); | 
| 937 | 0 |  |  |  |  | 0 | return undef; | 
| 938 |  |  |  |  |  |  | } | 
| 939 |  |  |  |  |  |  |  | 
| 940 | 0 | 0 |  |  |  | 0 | my $peek = $self->Peek ? '.PEEK' : ''; | 
| 941 | 0 | 0 |  |  |  | 0 | $peek = sprintf( $self->imap4rev1 ? "BODY%s\[]" : "RFC822%s", $peek ); | 
| 942 |  |  |  |  |  |  |  | 
| 943 | 0 |  |  |  |  | 0 | my @args = ( join( ",", @msgs ), $peek ); | 
| 944 |  |  |  |  |  |  |  | 
| 945 | 0 | 0 |  |  |  | 0 | return $self->_imap_uid_command( { outref => $fh }, "FETCH" => @args ) | 
| 946 |  |  |  |  |  |  | ? $self | 
| 947 |  |  |  |  |  |  | : undef; | 
| 948 |  |  |  |  |  |  | } | 
| 949 |  |  |  |  |  |  |  | 
| 950 |  |  |  |  |  |  | sub message_uid { | 
| 951 | 0 |  |  | 0 | 1 | 0 | my ( $self, $msg ) = @_; | 
| 952 |  |  |  |  |  |  |  | 
| 953 | 0 | 0 |  |  |  | 0 | my $ref = $self->fetch( $msg, "UID" ) or return undef; | 
| 954 | 0 |  |  |  |  | 0 | foreach (@$ref) { | 
| 955 | 0 | 0 |  |  |  | 0 | return $1 if m/\(UID\s+(\d+)\s*\)$CR?$/o; | 
| 956 |  |  |  |  |  |  | } | 
| 957 | 0 |  |  |  |  | 0 | return undef; | 
| 958 |  |  |  |  |  |  | } | 
| 959 |  |  |  |  |  |  |  | 
| 960 |  |  |  |  |  |  | # cleaned up and simplified but see TODO in code... | 
| 961 |  |  |  |  |  |  | sub migrate { | 
| 962 | 0 |  |  | 0 | 1 | 0 | my ( $self, $peer, $msgs, $folder ) = @_; | 
| 963 |  |  |  |  |  |  |  | 
| 964 | 0 | 0 | 0 |  |  | 0 | unless ( $peer and $peer->IsConnected ) { | 
| 965 | 0 | 0 |  |  |  | 0 | $self->LastError( ( $peer ? "Invalid" : "Unconnected" ) | 
|  |  | 0 |  |  |  |  |  | 
| 966 |  |  |  |  |  |  | . " target " | 
| 967 |  |  |  |  |  |  | . ref($self) | 
| 968 |  |  |  |  |  |  | . " object in migrate()" | 
| 969 |  |  |  |  |  |  | . ( $peer ? ( ": " . $peer->LastError ) : "" ) ); | 
| 970 | 0 |  |  |  |  | 0 | return undef; | 
| 971 |  |  |  |  |  |  | } | 
| 972 |  |  |  |  |  |  |  | 
| 973 |  |  |  |  |  |  | # sanity check to see if $self is same object as $peer | 
| 974 | 0 | 0 |  |  |  | 0 | if ( $self eq $peer ) { | 
| 975 | 0 |  |  |  |  | 0 | $self->LastError("dest must not be the same object as self"); | 
| 976 | 0 |  |  |  |  | 0 | return undef; | 
| 977 |  |  |  |  |  |  | } | 
| 978 |  |  |  |  |  |  |  | 
| 979 | 0 | 0 |  |  |  | 0 | $folder = $self->Folder unless ( defined $folder ); | 
| 980 | 0 | 0 |  |  |  | 0 | unless ($folder) { | 
| 981 | 0 |  |  |  |  | 0 | $self->LastError("No folder selected on source mailbox."); | 
| 982 | 0 |  |  |  |  | 0 | return undef; | 
| 983 |  |  |  |  |  |  | } | 
| 984 |  |  |  |  |  |  |  | 
| 985 | 0 | 0 | 0 |  |  | 0 | unless ( $peer->exists($folder) or $peer->create($folder) ) { | 
| 986 | 0 |  |  |  |  | 0 | $self->LastError( "Create folder '$folder' on target host failed: " | 
| 987 |  |  |  |  |  |  | . $peer->LastError ); | 
| 988 | 0 |  |  |  |  | 0 | return undef; | 
| 989 |  |  |  |  |  |  | } | 
| 990 |  |  |  |  |  |  |  | 
| 991 | 0 | 0 | 0 |  |  | 0 | if ( !defined $msgs or uc($msgs) eq "ALL" ) { | 
| 992 | 0 | 0 |  |  |  | 0 | $msgs = $self->search("ALL") or return undef; | 
| 993 |  |  |  |  |  |  | } | 
| 994 |  |  |  |  |  |  |  | 
| 995 |  |  |  |  |  |  | # message size and (internal) date | 
| 996 | 0 |  |  |  |  | 0 | my @headers = qw(RFC822.SIZE INTERNALDATE FLAGS); | 
| 997 | 0 |  |  |  |  | 0 | my $range   = $self->Range($msgs); | 
| 998 |  |  |  |  |  |  |  | 
| 999 | 0 |  |  |  |  | 0 | $self->_debug("Messages to migrate from '$folder': $range"); | 
| 1000 |  |  |  |  |  |  |  | 
| 1001 | 0 |  |  |  |  | 0 | foreach my $mid ( $range->unfold ) { | 
| 1002 |  |  |  |  |  |  |  | 
| 1003 |  |  |  |  |  |  | # fetch size internaldate and flags of original message | 
| 1004 |  |  |  |  |  |  | # - TODO: add flags here... | 
| 1005 | 0 | 0 |  |  |  | 0 | my $minfo = $self->fetch_hash( $mid, @headers ) | 
| 1006 |  |  |  |  |  |  | or return undef; | 
| 1007 |  |  |  |  |  |  |  | 
| 1008 | 0 |  |  |  |  | 0 | my ( $size, $date ) = @{ $minfo->{$mid} }{@headers}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1009 | 0 | 0 | 0 |  |  | 0 | return undef unless ( defined $size and defined $date ); | 
| 1010 |  |  |  |  |  |  |  | 
| 1011 | 0 |  |  |  |  | 0 | $self->_debug("Copy message $mid (sz=$size,dt=$date) from '$folder'"); | 
| 1012 |  |  |  |  |  |  |  | 
| 1013 | 0 |  |  |  |  | 0 | my @flags = grep !/\\Recent/i, $self->flags($mid); | 
| 1014 | 0 |  |  |  |  | 0 | my $flags = join ' ', $peer->supported_flags(@flags); | 
| 1015 |  |  |  |  |  |  |  | 
| 1016 |  |  |  |  |  |  | # TODO: - use File::Temp tempfile if $msg > bufferSize? | 
| 1017 |  |  |  |  |  |  | # read message to $msg | 
| 1018 | 0 |  |  |  |  | 0 | my $msg; | 
| 1019 | 0 | 0 |  |  |  | 0 | $self->message_to_file( \$msg, $mid ) | 
| 1020 |  |  |  |  |  |  | or return undef; | 
| 1021 |  |  |  |  |  |  |  | 
| 1022 | 0 |  |  |  |  | 0 | my $newid = $peer->append_file( $folder, \$msg, undef, $flags, $date ); | 
| 1023 |  |  |  |  |  |  |  | 
| 1024 | 0 | 0 |  |  |  | 0 | unless ( defined $newid ) { | 
| 1025 | 0 |  |  |  |  | 0 | $self->LastError( | 
| 1026 |  |  |  |  |  |  | "Append to '$folder' on target failed: " . $peer->LastError ); | 
| 1027 | 0 |  |  |  |  | 0 | return undef; | 
| 1028 |  |  |  |  |  |  | } | 
| 1029 |  |  |  |  |  |  |  | 
| 1030 | 0 |  |  |  |  | 0 | $self->_debug("Copied UID $mid in '$folder' to target UID $newid"); | 
| 1031 |  |  |  |  |  |  | } | 
| 1032 |  |  |  |  |  |  |  | 
| 1033 | 0 |  |  |  |  | 0 | return $self; | 
| 1034 |  |  |  |  |  |  | } | 
| 1035 |  |  |  |  |  |  |  | 
| 1036 |  |  |  |  |  |  | # Optimization of wait time between syswrite calls only runs if syscalls | 
| 1037 |  |  |  |  |  |  | # run too fast and fill the buffer causing "EAGAIN: Resource Temp. Unavail" | 
| 1038 |  |  |  |  |  |  | # errors. The premise is that $maxwrite will be approx. the same as the | 
| 1039 |  |  |  |  |  |  | # smallest buffer between the sending and receiving side. Waiting time | 
| 1040 |  |  |  |  |  |  | # between syscalls should ideally be exactly as long as it takes the | 
| 1041 |  |  |  |  |  |  | # receiving side to empty that buffer, minus a little bit to prevent it | 
| 1042 |  |  |  |  |  |  | # from emptying completely and wasting time in the select call. | 
| 1043 |  |  |  |  |  |  |  | 
| 1044 |  |  |  |  |  |  | sub _optimal_sleep($$$) { | 
| 1045 | 0 |  |  | 0 |  | 0 | my ( $self, $maxwrite, $waittime, $last5writes ) = @_; | 
| 1046 |  |  |  |  |  |  |  | 
| 1047 | 0 |  |  |  |  | 0 | push @$last5writes, $waittime; | 
| 1048 | 0 | 0 |  |  |  | 0 | shift @$last5writes if @$last5writes > 5; | 
| 1049 |  |  |  |  |  |  |  | 
| 1050 | 0 |  |  |  |  | 0 | my $bufferavail = ( sum @$last5writes ) / @$last5writes; | 
| 1051 |  |  |  |  |  |  |  | 
| 1052 | 0 | 0 |  |  |  | 0 | if ( $bufferavail < .4 * $maxwrite ) { | 
|  |  | 0 |  |  |  |  |  | 
| 1053 |  |  |  |  |  |  |  | 
| 1054 |  |  |  |  |  |  | # Buffer is staying pretty full; we should increase the wait | 
| 1055 |  |  |  |  |  |  | # period to reduce transmission overhead/number of packets sent | 
| 1056 | 0 |  |  |  |  | 0 | $waittime *= 1.3; | 
| 1057 |  |  |  |  |  |  | } | 
| 1058 |  |  |  |  |  |  | elsif ( $bufferavail > .9 * $maxwrite ) { | 
| 1059 |  |  |  |  |  |  |  | 
| 1060 |  |  |  |  |  |  | # Buffer is nearly or totally empty; we're wasting time in select | 
| 1061 |  |  |  |  |  |  | # call that could be used to send data, so reduce the wait period | 
| 1062 | 0 |  |  |  |  | 0 | $waittime *= .5; | 
| 1063 |  |  |  |  |  |  | } | 
| 1064 |  |  |  |  |  |  |  | 
| 1065 | 0 |  |  |  |  | 0 | CORE::select( undef, undef, undef, $waittime ); | 
| 1066 | 0 |  |  |  |  | 0 | $waittime; | 
| 1067 |  |  |  |  |  |  | } | 
| 1068 |  |  |  |  |  |  |  | 
| 1069 |  |  |  |  |  |  | sub body_string { | 
| 1070 | 2 |  |  | 2 | 1 | 936 | my ( $self, $msg ) = @_; | 
| 1071 | 2 | 50 |  |  |  | 9 | my $ref = | 
|  |  | 50 |  |  |  |  |  | 
| 1072 |  |  |  |  |  |  | $self->fetch( $msg, "BODY" . ( $self->Peek ? ".PEEK" : "" ) . "[TEXT]" ) | 
| 1073 |  |  |  |  |  |  | or return undef; | 
| 1074 |  |  |  |  |  |  |  | 
| 1075 | 0 |  |  |  |  | 0 | my $string = join '', map { $_->[DATA] } | 
| 1076 | 2 |  |  |  |  | 15 | grep { $self->_is_literal($_) } @$ref; | 
|  | 11 |  |  |  |  | 20 |  | 
| 1077 |  |  |  |  |  |  |  | 
| 1078 | 2 | 50 |  |  |  | 5 | return $string | 
| 1079 |  |  |  |  |  |  | if $string; | 
| 1080 |  |  |  |  |  |  |  | 
| 1081 | 2 |  |  |  |  | 3 | my $head; | 
| 1082 | 2 |  |  |  |  | 7 | while ( $head = shift @$ref ) { | 
| 1083 | 4 |  |  |  |  | 16 | $self->_debug("body_string: head = '$head'"); | 
| 1084 |  |  |  |  |  |  |  | 
| 1085 |  |  |  |  |  |  | last | 
| 1086 | 4 | 100 |  |  |  | 29 | if $head =~ | 
| 1087 |  |  |  |  |  |  | /(?:.*FETCH .*\(.*BODY\[TEXT\])|(?:^\d+ BAD )|(?:^\d NO )/i; | 
| 1088 |  |  |  |  |  |  | } | 
| 1089 |  |  |  |  |  |  |  | 
| 1090 | 2 | 50 |  |  |  | 6 | unless (@$ref) { | 
| 1091 | 0 |  |  |  |  | 0 | $self->LastError( | 
| 1092 |  |  |  |  |  |  | "Unable to parse server response from " . $self->LastIMAPCommand ); | 
| 1093 | 0 |  |  |  |  | 0 | return undef; | 
| 1094 |  |  |  |  |  |  | } | 
| 1095 |  |  |  |  |  |  |  | 
| 1096 | 2 |  |  |  |  | 3 | my $popped; | 
| 1097 | 2 |  | 100 |  |  | 50 | $popped = pop @$ref | 
|  |  |  | 66 |  |  |  |  | 
| 1098 |  |  |  |  |  |  | until ( $popped && $popped =~ /^\)$CRLF$/o ) | 
| 1099 |  |  |  |  |  |  | || !grep /^\)$CRLF$/o, @$ref; | 
| 1100 |  |  |  |  |  |  |  | 
| 1101 | 2 | 50 |  |  |  | 10 | if ( $head =~ /BODY\[TEXT\]\s*$/i ) {    # Next line is a literal | 
| 1102 | 2 |  |  |  |  | 9 | $string .= shift @$ref while @$ref; | 
| 1103 | 2 | 50 |  |  |  | 5 | $self->_debug("String is now $string") | 
| 1104 |  |  |  |  |  |  | if $self->Debug; | 
| 1105 |  |  |  |  |  |  | } | 
| 1106 |  |  |  |  |  |  |  | 
| 1107 | 2 |  |  |  |  | 5 | $string; | 
| 1108 |  |  |  |  |  |  | } | 
| 1109 |  |  |  |  |  |  |  | 
| 1110 |  |  |  |  |  |  | sub examine { | 
| 1111 | 0 |  |  | 0 | 1 | 0 | my ( $self, $target ) = @_; | 
| 1112 | 0 | 0 |  |  |  | 0 | defined $target or return undef; | 
| 1113 |  |  |  |  |  |  |  | 
| 1114 | 0 | 0 |  |  |  | 0 | $self->_imap_command( 'EXAMINE ' . $self->Quote($target) ) | 
| 1115 |  |  |  |  |  |  | or return undef; | 
| 1116 |  |  |  |  |  |  |  | 
| 1117 | 0 |  |  |  |  | 0 | my $old = $self->Folder; | 
| 1118 | 0 |  |  |  |  | 0 | $self->Folder($target); | 
| 1119 | 0 |  |  |  |  | 0 | $self->State(Selected); | 
| 1120 | 0 | 0 |  |  |  | 0 | $old || $self; | 
| 1121 |  |  |  |  |  |  | } | 
| 1122 |  |  |  |  |  |  |  | 
| 1123 |  |  |  |  |  |  | sub idle { | 
| 1124 | 0 |  |  | 0 | 1 | 0 | my $self  = shift; | 
| 1125 | 0 |  |  |  |  | 0 | my $good  = '+'; | 
| 1126 | 0 |  |  |  |  | 0 | my $count = $self->Count + 1; | 
| 1127 | 0 | 0 |  |  |  | 0 | $self->_imap_command( "IDLE", $good ) ? $count : undef; | 
| 1128 |  |  |  |  |  |  | } | 
| 1129 |  |  |  |  |  |  |  | 
| 1130 |  |  |  |  |  |  | sub idle_data { | 
| 1131 | 0 |  |  | 0 | 1 | 0 | my $self    = shift; | 
| 1132 | 0 | 0 |  |  |  | 0 | my $timeout = scalar(@_) ? shift : 0; | 
| 1133 | 0 |  |  |  |  | 0 | my $socket  = $self->Socket; | 
| 1134 |  |  |  |  |  |  |  | 
| 1135 |  |  |  |  |  |  | # current index in Results array | 
| 1136 | 0 |  |  |  |  | 0 | my $trans_c1 = $self->_next_index; | 
| 1137 |  |  |  |  |  |  |  | 
| 1138 |  |  |  |  |  |  | # look for all untagged responses | 
| 1139 | 0 |  |  |  |  | 0 | my ( $rc, $ret ); | 
| 1140 |  |  |  |  |  |  |  | 
| 1141 | 0 |  | 0 |  |  | 0 | do { | 
| 1142 | 0 |  |  |  |  | 0 | $ret = | 
| 1143 |  |  |  |  |  |  | $self->_read_more( { error_on_timeout => 0 }, $socket, $timeout ); | 
| 1144 |  |  |  |  |  |  |  | 
| 1145 |  |  |  |  |  |  | # set rc on first pass or on errors | 
| 1146 | 0 | 0 | 0 |  |  | 0 | $rc = $ret if ( !defined($rc) or $ret < 0 ); | 
| 1147 |  |  |  |  |  |  |  | 
| 1148 |  |  |  |  |  |  | # not using /\S+/ because that can match 0 in "* 0 RECENT" | 
| 1149 |  |  |  |  |  |  | # leading the library to act as if things failed | 
| 1150 | 0 | 0 |  |  |  | 0 | if ( $ret > 0 ) { | 
| 1151 | 0 | 0 |  |  |  | 0 | $self->_get_response( '*', qr/(?!BAD|BYE|NO)(?:\d+\s+\w+|\S+)/ ) | 
| 1152 |  |  |  |  |  |  | or return undef; | 
| 1153 | 0 |  |  |  |  | 0 | $timeout = 0;    # check for more data without blocking! | 
| 1154 |  |  |  |  |  |  | } | 
| 1155 |  |  |  |  |  |  | } while $ret > 0 and $self->IsConnected; | 
| 1156 |  |  |  |  |  |  |  | 
| 1157 |  |  |  |  |  |  | # select returns -1 on errors | 
| 1158 | 0 | 0 |  |  |  | 0 | return undef if $rc < 0; | 
| 1159 |  |  |  |  |  |  |  | 
| 1160 | 0 |  |  |  |  | 0 | my $trans_c2 = $self->_next_index; | 
| 1161 |  |  |  |  |  |  |  | 
| 1162 |  |  |  |  |  |  | # if current index in Results array has changed return data | 
| 1163 | 0 |  |  |  |  | 0 | my @res; | 
| 1164 | 0 | 0 |  |  |  | 0 | if ( $trans_c1 < $trans_c2 ) { | 
| 1165 | 0 |  |  |  |  | 0 | @res = $self->Results; | 
| 1166 | 0 |  |  |  |  | 0 | @res = @res[ $trans_c1 .. ( $trans_c2 - 1 ) ]; | 
| 1167 |  |  |  |  |  |  | } | 
| 1168 | 0 | 0 |  |  |  | 0 | return wantarray ? @res : \@res; | 
| 1169 |  |  |  |  |  |  | } | 
| 1170 |  |  |  |  |  |  |  | 
| 1171 |  |  |  |  |  |  | sub done { | 
| 1172 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 1173 | 0 |  | 0 |  |  | 0 | my $count = shift || $self->Count; | 
| 1174 |  |  |  |  |  |  |  | 
| 1175 |  |  |  |  |  |  | # DONE looks like a tag when sent and not already in IDLE | 
| 1176 | 0 | 0 |  |  |  | 0 | $self->_imap_command( | 
| 1177 |  |  |  |  |  |  | { addtag => 0, tag => qr/(?:$count|DONE)/, doretry => 0 }, "DONE" ) | 
| 1178 |  |  |  |  |  |  | or return undef; | 
| 1179 | 0 |  |  |  |  | 0 | return $self->Results; | 
| 1180 |  |  |  |  |  |  | } | 
| 1181 |  |  |  |  |  |  |  | 
| 1182 |  |  |  |  |  |  | # tag_and_run( $self, $string, $good ) | 
| 1183 |  |  |  |  |  |  | sub tag_and_run { | 
| 1184 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 1185 | 0 | 0 |  |  |  | 0 | $self->_imap_command(@_) or return undef; | 
| 1186 | 0 |  |  |  |  | 0 | return $self->Results; | 
| 1187 |  |  |  |  |  |  | } | 
| 1188 |  |  |  |  |  |  |  | 
| 1189 |  |  |  |  |  |  | sub reconnect { | 
| 1190 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 1191 |  |  |  |  |  |  |  | 
| 1192 | 0 | 0 |  |  |  | 0 | if ( $self->IsAuthenticated ) { | 
| 1193 | 0 |  |  |  |  | 0 | $self->_debug("reconnect called but already authenticated"); | 
| 1194 | 0 |  |  |  |  | 0 | return 1; | 
| 1195 |  |  |  |  |  |  | } | 
| 1196 |  |  |  |  |  |  |  | 
| 1197 |  |  |  |  |  |  | # safeguard from deep recursion via connect | 
| 1198 | 0 | 0 |  |  |  | 0 | if ( $self->{_doing_reconnect} ) { | 
| 1199 | 0 |  |  |  |  | 0 | $self->_debug("recursive call to reconnect, returning 0\n"); | 
| 1200 | 0 | 0 |  |  |  | 0 | $self->LastError("unexpected reconnect recursion") | 
| 1201 |  |  |  |  |  |  | unless $self->LastError; | 
| 1202 | 0 |  |  |  |  | 0 | return 0; | 
| 1203 |  |  |  |  |  |  | } | 
| 1204 |  |  |  |  |  |  |  | 
| 1205 | 0 |  | 0 |  |  | 0 | my $einfo = $self->LastError || ""; | 
| 1206 | 0 |  |  |  |  | 0 | $self->_debug( "reconnecting to ", $self->Server, ", last error: $einfo" ); | 
| 1207 | 0 |  |  |  |  | 0 | $self->{_doing_reconnect} = 1; | 
| 1208 |  |  |  |  |  |  |  | 
| 1209 |  |  |  |  |  |  | # reconnect and select appropriate folder | 
| 1210 | 0 |  |  |  |  | 0 | my $ret; | 
| 1211 | 0 | 0 |  |  |  | 0 | if ( $self->connect ) { | 
| 1212 | 0 |  |  |  |  | 0 | $ret = 1; | 
| 1213 | 0 | 0 |  |  |  | 0 | if ( defined $self->Folder ) { | 
| 1214 | 0 | 0 |  |  |  | 0 | $ret = defined( $self->select( $self->Folder ) ) ? 1 : undef; | 
| 1215 |  |  |  |  |  |  | } | 
| 1216 |  |  |  |  |  |  | } | 
| 1217 |  |  |  |  |  |  |  | 
| 1218 | 0 |  |  |  |  | 0 | delete $self->{_doing_reconnect}; | 
| 1219 | 0 | 0 |  |  |  | 0 | return $ret ? 1 : $ret; | 
| 1220 |  |  |  |  |  |  | } | 
| 1221 |  |  |  |  |  |  |  | 
| 1222 |  |  |  |  |  |  | # wrapper for _imap_command_do to enable retrying on lost connections | 
| 1223 |  |  |  |  |  |  | # options: | 
| 1224 |  |  |  |  |  |  | #   doretry => 0|1 - suppress|allow retry after reconnect | 
| 1225 |  |  |  |  |  |  | sub _imap_command { | 
| 1226 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 1227 | 0 | 0 |  |  |  | 0 | my $opt = ref( $_[0] ) eq "HASH" ? $_[0] : {}; | 
| 1228 |  |  |  |  |  |  |  | 
| 1229 | 0 |  |  |  |  | 0 | my $tries = 0; | 
| 1230 | 0 |  | 0 |  |  | 0 | my $retry = $self->Reconnectretry || 0; | 
| 1231 | 0 |  |  |  |  | 0 | my ( $rc, @err ); | 
| 1232 |  |  |  |  |  |  |  | 
| 1233 |  |  |  |  |  |  | # LastError (if set) will be overwritten masking any earlier errors | 
| 1234 | 0 |  |  |  |  | 0 | while ( $tries++ <= $retry ) { | 
| 1235 |  |  |  |  |  |  |  | 
| 1236 |  |  |  |  |  |  | # do command on the first try or if Connected (reconnect ongoing) | 
| 1237 | 0 | 0 | 0 |  |  | 0 | if ( $tries == 1 or $self->IsConnected ) { | 
| 1238 | 0 |  |  |  |  | 0 | $rc = $self->_imap_command_do(@_); | 
| 1239 | 0 | 0 |  |  |  | 0 | push( @err, $self->LastError ) if $self->LastError; | 
| 1240 |  |  |  |  |  |  | } | 
| 1241 |  |  |  |  |  |  |  | 
| 1242 | 0 | 0 | 0 |  |  | 0 | if ( !defined($rc) and $retry and $self->IsUnconnected ) { | 
|  |  |  | 0 |  |  |  |  | 
| 1243 |  |  |  |  |  |  | last | 
| 1244 |  |  |  |  |  |  | unless ( | 
| 1245 | 0 | 0 | 0 |  |  | 0 | $! == EPIPE | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 1246 |  |  |  |  |  |  | or $! == ECONNRESET | 
| 1247 |  |  |  |  |  |  | or $self->LastError =~ /(?:error\(.*?\)|timeout) waiting\b/ | 
| 1248 |  |  |  |  |  |  | or $self->LastError =~ /(?:socket closed|\* BYE)\b/ | 
| 1249 |  |  |  |  |  |  |  | 
| 1250 |  |  |  |  |  |  | # BUG? reconnect if caller ignored/missed earlier errors? | 
| 1251 |  |  |  |  |  |  | # or $self->LastError =~ /NO not connected/ | 
| 1252 |  |  |  |  |  |  | ); | 
| 1253 | 0 |  |  |  |  | 0 | my $ret = $self->reconnect; | 
| 1254 | 0 | 0 | 0 |  |  | 0 | if ($ret) { | 
|  |  | 0 |  |  |  |  |  | 
| 1255 | 0 |  |  |  |  | 0 | $self->_debug("reconnect success($ret) on try #$tries/$retry"); | 
| 1256 | 0 | 0 | 0 |  |  | 0 | last if exists $opt->{doretry} and !$opt->{doretry}; | 
| 1257 |  |  |  |  |  |  | } | 
| 1258 |  |  |  |  |  |  | elsif ( defined $ret and $ret == 0 ) {    # escaping recursion | 
| 1259 | 0 |  |  |  |  | 0 | return undef; | 
| 1260 |  |  |  |  |  |  | } | 
| 1261 |  |  |  |  |  |  | else { | 
| 1262 | 0 |  |  |  |  | 0 | $self->_debug("reconnect failure on try #$tries/$retry"); | 
| 1263 | 0 | 0 |  |  |  | 0 | push( @err, $self->LastError ) if $self->LastError; | 
| 1264 |  |  |  |  |  |  | } | 
| 1265 |  |  |  |  |  |  | } | 
| 1266 |  |  |  |  |  |  | else { | 
| 1267 | 0 |  |  |  |  | 0 | last; | 
| 1268 |  |  |  |  |  |  | } | 
| 1269 |  |  |  |  |  |  | } | 
| 1270 |  |  |  |  |  |  |  | 
| 1271 | 0 | 0 |  |  |  | 0 | unless ($rc) { | 
| 1272 | 0 |  |  |  |  | 0 | my ( %seen, @keep, @info ); | 
| 1273 |  |  |  |  |  |  |  | 
| 1274 | 0 |  |  |  |  | 0 | foreach my $str (@err) { | 
| 1275 | 0 |  |  |  |  | 0 | my ( $sz, $len ) = ( 96, length($str) ); | 
| 1276 | 0 |  |  |  |  | 0 | $str =~ s/$CR?$LF$/\\n/omg; | 
| 1277 | 0 | 0 | 0 |  |  | 0 | if ( !$self->Debug and $len > $sz * 2 ) { | 
| 1278 | 0 |  |  |  |  | 0 | my $beg = substr( $str, 0,    $sz ); | 
| 1279 | 0 |  |  |  |  | 0 | my $end = substr( $str, -$sz, $sz ); | 
| 1280 | 0 |  |  |  |  | 0 | $str = $beg . "..." . $end; | 
| 1281 |  |  |  |  |  |  | } | 
| 1282 | 0 | 0 |  |  |  | 0 | next if $seen{$str}++; | 
| 1283 | 0 |  |  |  |  | 0 | push( @keep, $str ); | 
| 1284 |  |  |  |  |  |  | } | 
| 1285 | 0 |  |  |  |  | 0 | foreach my $msg (@keep) { | 
| 1286 | 0 | 0 |  |  |  | 0 | push( @info, $msg . ( $seen{$msg} > 1 ? " ($seen{$msg}x)" : "" ) ); | 
| 1287 |  |  |  |  |  |  | } | 
| 1288 | 0 |  |  |  |  | 0 | $self->LastError( join( "; ", @info ) ); | 
| 1289 |  |  |  |  |  |  | } | 
| 1290 |  |  |  |  |  |  |  | 
| 1291 | 0 |  |  |  |  | 0 | return $rc; | 
| 1292 |  |  |  |  |  |  | } | 
| 1293 |  |  |  |  |  |  |  | 
| 1294 |  |  |  |  |  |  | # _imap_command_do runs a command, inserting a tag and CRLF as requested | 
| 1295 |  |  |  |  |  |  | # options: | 
| 1296 |  |  |  |  |  |  | #   addcrlf => 0|1  - suppress adding CRLF to $string | 
| 1297 |  |  |  |  |  |  | #   addtag  => 0|1  - suppress adding $tag to $string | 
| 1298 |  |  |  |  |  |  | #   tag     => $tag - use this $tag instead of incrementing $self->Count | 
| 1299 |  |  |  |  |  |  | #   outref  => ...  - see _get_response() | 
| 1300 |  |  |  |  |  |  | sub _imap_command_do { | 
| 1301 | 0 |  |  | 0 |  | 0 | my $self   = shift; | 
| 1302 | 0 | 0 |  |  |  | 0 | my $opt    = ref( $_[0] ) eq "HASH" ? shift : {}; | 
| 1303 | 0 | 0 |  |  |  | 0 | my $string = shift or return undef; | 
| 1304 | 0 |  |  |  |  | 0 | my $good   = shift; | 
| 1305 |  |  |  |  |  |  |  | 
| 1306 | 0 | 0 |  |  |  | 0 | my @gropt = ( $opt->{outref} ? { outref => $opt->{outref} } : () ); | 
| 1307 |  |  |  |  |  |  |  | 
| 1308 | 0 | 0 |  |  |  | 0 | $opt->{addcrlf} = 1 unless exists $opt->{addcrlf}; | 
| 1309 | 0 | 0 |  |  |  | 0 | $opt->{addtag}  = 1 unless exists $opt->{addtag}; | 
| 1310 |  |  |  |  |  |  |  | 
| 1311 |  |  |  |  |  |  | # reset error in case the last error was non-fatal but never cleared | 
| 1312 | 0 | 0 |  |  |  | 0 | if ( $self->LastError ) { | 
| 1313 |  |  |  |  |  |  |  | 
| 1314 |  |  |  |  |  |  | #DEBUG $self->_debug( "Reset LastError: " . $self->LastError ); | 
| 1315 | 0 |  |  |  |  | 0 | $self->LastError(undef); | 
| 1316 |  |  |  |  |  |  | } | 
| 1317 |  |  |  |  |  |  |  | 
| 1318 | 0 |  |  |  |  | 0 | my $clear = $self->Clear; | 
| 1319 | 0 | 0 | 0 |  |  | 0 | $self->Clear($clear) | 
| 1320 |  |  |  |  |  |  | if $self->Count >= $clear && $clear > 0; | 
| 1321 |  |  |  |  |  |  |  | 
| 1322 | 0 |  |  |  |  | 0 | my $count = $self->Count( $self->Count + 1 ); | 
| 1323 | 0 |  | 0 |  |  | 0 | my $tag = $opt->{tag} || $count; | 
| 1324 | 0 | 0 |  |  |  | 0 | $string = "$tag $string" if $opt->{addtag}; | 
| 1325 |  |  |  |  |  |  |  | 
| 1326 |  |  |  |  |  |  | # for APPEND (append_string) only log first line of command | 
| 1327 | 0 | 0 |  |  |  | 0 | my $logstr = ( $string =~ /^($tag\s+APPEND\s+.*?)$CR?$LF/ ) ? $1 : $string; | 
| 1328 |  |  |  |  |  |  |  | 
| 1329 |  |  |  |  |  |  | # BUG? use $self->_next_index($tag) ? or 0 ??? | 
| 1330 |  |  |  |  |  |  | # $self->_record($tag, [$self->_next_index($tag), "INPUT", $logstr] ); | 
| 1331 | 0 |  |  |  |  | 0 | $self->_record( $count, [ 0, "INPUT", $logstr ] ); | 
| 1332 |  |  |  |  |  |  |  | 
| 1333 |  |  |  |  |  |  | # $suppress (adding CRLF) set to 0 if $opt->{addcrlf} is TRUE | 
| 1334 | 0 | 0 |  |  |  | 0 | unless ( $self->_send_line( $string, $opt->{addcrlf} ? 0 : 1 ) ) { | 
|  |  | 0 |  |  |  |  |  | 
| 1335 | 0 |  |  |  |  | 0 | $self->LastError( "Error sending '$logstr': " . $self->LastError ); | 
| 1336 | 0 |  |  |  |  | 0 | return undef; | 
| 1337 |  |  |  |  |  |  | } | 
| 1338 |  |  |  |  |  |  |  | 
| 1339 |  |  |  |  |  |  | # look for " (OK|BAD|NO|$good)" (or "+..." if $good is '+') | 
| 1340 | 0 | 0 |  |  |  | 0 | my $code = $self->_get_response( @gropt, $tag, $good ) or return undef; | 
| 1341 |  |  |  |  |  |  |  | 
| 1342 | 0 | 0 | 0 |  |  | 0 | if ( $code eq 'OK' ) { | 
|  |  | 0 |  |  |  |  |  | 
| 1343 | 0 |  |  |  |  | 0 | return $self; | 
| 1344 |  |  |  |  |  |  | } | 
| 1345 |  |  |  |  |  |  | elsif ( $good and $code eq $good ) { | 
| 1346 | 0 |  |  |  |  | 0 | return $self; | 
| 1347 |  |  |  |  |  |  | } | 
| 1348 |  |  |  |  |  |  | else { | 
| 1349 | 0 |  |  |  |  | 0 | return undef; | 
| 1350 |  |  |  |  |  |  | } | 
| 1351 |  |  |  |  |  |  | } | 
| 1352 |  |  |  |  |  |  |  | 
| 1353 |  |  |  |  |  |  | sub _response_code_sub { | 
| 1354 | 0 |  |  | 0 |  | 0 | my ( $self, $tag, $good ) = @_; | 
| 1355 |  |  |  |  |  |  |  | 
| 1356 |  |  |  |  |  |  | # tag/good can be a ref (compiled regex) otherwise quote it | 
| 1357 | 0 | 0 |  |  |  | 0 | my $qtag  = ref($tag)  ? $tag  : defined($tag)  ? quotemeta($tag)  : undef; | 
|  |  | 0 |  |  |  |  |  | 
| 1358 | 0 | 0 |  |  |  | 0 | my $qgood = ref($good) ? $good : defined($good) ? quotemeta($good) : undef; | 
|  |  | 0 |  |  |  |  |  | 
| 1359 |  |  |  |  |  |  |  | 
| 1360 |  |  |  |  |  |  | # using closure, a variable alias, and sub returns on first match | 
| 1361 |  |  |  |  |  |  | # - $_[0] is $o->[DATA] | 
| 1362 |  |  |  |  |  |  | # - returns list ( $code, $byemsg ) | 
| 1363 |  |  |  |  |  |  | my $getcodesub = sub { | 
| 1364 | 0 | 0 |  | 0 |  | 0 | if ( defined $qgood ) { | 
| 1365 | 0 | 0 | 0 |  |  | 0 | if ( $good eq '+' and $_[0] =~ /^$qgood/ ) { | 
| 1366 | 0 |  |  |  |  | 0 | return ($good); | 
| 1367 |  |  |  |  |  |  | } | 
| 1368 | 0 | 0 | 0 |  |  | 0 | if ( defined $qtag and $_[0] =~ /^$qtag\s+($qgood)/i ) { | 
| 1369 | 0 | 0 |  |  |  | 0 | return ( ref($qgood) ? $1 : uc($1) ); | 
| 1370 |  |  |  |  |  |  | } | 
| 1371 |  |  |  |  |  |  | } | 
| 1372 | 0 | 0 |  |  |  | 0 | if ( defined $qtag ) { | 
| 1373 | 0 | 0 | 0 |  |  | 0 | if ( $tag eq '+' and $_[0] =~ /^$qtag/ ) { | 
| 1374 | 0 |  |  |  |  | 0 | return ($tag); | 
| 1375 |  |  |  |  |  |  | } | 
| 1376 | 0 | 0 |  |  |  | 0 | if ( $_[0] =~ /^$qtag\s+(OK|BAD|NO)\b/i ) { | 
| 1377 | 0 |  |  |  |  | 0 | my $code = uc($1); | 
| 1378 | 0 | 0 |  |  |  | 0 | $self->LastError( $_[0] ) unless ( $code eq 'OK' ); | 
| 1379 | 0 |  |  |  |  | 0 | return ($code); | 
| 1380 |  |  |  |  |  |  | } | 
| 1381 |  |  |  |  |  |  | } | 
| 1382 | 0 | 0 |  |  |  | 0 | if ( $_[0] =~ /^\*\s+(BYE)\b/i ) { | 
| 1383 | 0 |  |  |  |  | 0 | return ( uc($1), $_[0] );    # ( 'BYE', $byemsg ) | 
| 1384 |  |  |  |  |  |  | } | 
| 1385 | 0 |  |  |  |  | 0 | return (undef); | 
| 1386 | 0 |  |  |  |  | 0 | }; | 
| 1387 |  |  |  |  |  |  |  | 
| 1388 | 0 |  |  |  |  | 0 | return $getcodesub; | 
| 1389 |  |  |  |  |  |  | } | 
| 1390 |  |  |  |  |  |  |  | 
| 1391 |  |  |  |  |  |  | # _get_response get IMAP response optionally send data somewhere | 
| 1392 |  |  |  |  |  |  | # options: | 
| 1393 |  |  |  |  |  |  | #   outref => GLOB|CODE - reference to send output to (see _read_line) | 
| 1394 |  |  |  |  |  |  | sub _get_response { | 
| 1395 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 1396 | 0 | 0 |  |  |  | 0 | my $opt  = ref( $_[0] ) eq "HASH" ? shift : {}; | 
| 1397 | 0 |  |  |  |  | 0 | my $tag  = shift; | 
| 1398 | 0 |  |  |  |  | 0 | my $good = shift; | 
| 1399 |  |  |  |  |  |  |  | 
| 1400 | 0 |  |  |  |  | 0 | my $outref  = $opt->{outref}; | 
| 1401 | 0 | 0 |  |  |  | 0 | my @readopt = defined($outref) ? ($outref) : (); | 
| 1402 | 0 |  |  |  |  | 0 | my $getcode = $self->_response_code_sub( $tag, $good ); | 
| 1403 |  |  |  |  |  |  |  | 
| 1404 | 0 |  |  |  |  | 0 | my ( $count, $out, $code, $byemsg ) = ( $self->Count, [], undef, undef ); | 
| 1405 | 0 |  |  |  |  | 0 | until ( defined $code ) { | 
| 1406 | 0 | 0 |  |  |  | 0 | my $output = $self->_read_line(@readopt) or return undef; | 
| 1407 | 0 |  |  |  |  | 0 | $out = $output;    # keep last response just in case | 
| 1408 |  |  |  |  |  |  |  | 
| 1409 |  |  |  |  |  |  | # not using last on first match? paranoia or right thing? | 
| 1410 |  |  |  |  |  |  | # only uc() when match is not on case where $tag|$good is a ref() | 
| 1411 | 0 |  |  |  |  | 0 | foreach my $o (@$output) { | 
| 1412 | 0 |  |  |  |  | 0 | $self->_record( $count, $o ); | 
| 1413 | 0 | 0 |  |  |  | 0 | $self->_is_output($o) or next; | 
| 1414 | 0 |  |  |  |  | 0 | my ( $tcode, $tbyemsg ) = $getcode->( $o->[DATA] ); | 
| 1415 | 0 | 0 |  |  |  | 0 | $code   = $tcode   if ( defined $tcode ); | 
| 1416 | 0 | 0 |  |  |  | 0 | $byemsg = $tbyemsg if ( defined $tbyemsg ); | 
| 1417 |  |  |  |  |  |  | } | 
| 1418 |  |  |  |  |  |  | } | 
| 1419 |  |  |  |  |  |  |  | 
| 1420 | 0 | 0 |  |  |  | 0 | if ( defined $code ) { | 
|  |  | 0 |  |  |  |  |  | 
| 1421 | 0 |  |  |  |  | 0 | $code =~ s/$CR?$LF?$//o; | 
| 1422 | 0 | 0 | 0 |  |  | 0 | $code = uc($code) unless ( $good and $code eq $good ); | 
| 1423 |  |  |  |  |  |  |  | 
| 1424 |  |  |  |  |  |  | # RFC 3501 7.1.5: $code on successful LOGOUT is OK not BYE | 
| 1425 |  |  |  |  |  |  | # sometimes we may fail to wait long enough to read a tagged | 
| 1426 |  |  |  |  |  |  | # OK so don't be strict about setting an error on LOGOUT! | 
| 1427 | 0 | 0 |  |  |  | 0 | if ( $code eq 'BYE' ) { | 
| 1428 | 0 |  |  |  |  | 0 | $self->State(Unconnected); | 
| 1429 | 0 | 0 |  |  |  | 0 | if ($byemsg) { | 
| 1430 | 0 | 0 | 0 |  |  | 0 | $self->LastError($byemsg) | 
| 1431 |  |  |  |  |  |  | unless ( $good and $code eq $good ); | 
| 1432 |  |  |  |  |  |  | } | 
| 1433 |  |  |  |  |  |  | } | 
| 1434 |  |  |  |  |  |  | } | 
| 1435 |  |  |  |  |  |  | elsif ( !$self->LastError ) { | 
| 1436 | 0 |  |  |  |  | 0 | my $info = "unexpected response: " . join( " ", @$out ); | 
| 1437 | 0 |  |  |  |  | 0 | $self->LastError($info); | 
| 1438 |  |  |  |  |  |  | } | 
| 1439 |  |  |  |  |  |  |  | 
| 1440 | 0 |  |  |  |  | 0 | return $code; | 
| 1441 |  |  |  |  |  |  | } | 
| 1442 |  |  |  |  |  |  |  | 
| 1443 |  |  |  |  |  |  | sub _imap_uid_command { | 
| 1444 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 1445 | 0 | 0 |  |  |  | 0 | my @opt  = ref( $_[0] ) eq "HASH" ? (shift) : (); | 
| 1446 | 0 |  |  |  |  | 0 | my $cmd  = shift; | 
| 1447 |  |  |  |  |  |  |  | 
| 1448 | 0 | 0 |  |  |  | 0 | my $args = @_ ? join( " ", '', @_ ) : ''; | 
| 1449 | 0 | 0 |  |  |  | 0 | my $uid = $self->Uid ? 'UID ' : ''; | 
| 1450 | 0 |  |  |  |  | 0 | $self->_imap_command( @opt, "$uid$cmd$args" ); | 
| 1451 |  |  |  |  |  |  | } | 
| 1452 |  |  |  |  |  |  |  | 
| 1453 |  |  |  |  |  |  | sub run { | 
| 1454 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 1455 | 0 | 0 |  |  |  | 0 | my $string = shift or return undef; | 
| 1456 |  |  |  |  |  |  |  | 
| 1457 | 0 | 0 |  |  |  | 0 | my $tag = $string =~ /^(\S+) / ? $1 : undef; | 
| 1458 | 0 | 0 |  |  |  | 0 | unless ($tag) { | 
| 1459 | 0 |  |  |  |  | 0 | $self->LastError("No tag found in string passed to run(): $string"); | 
| 1460 | 0 |  |  |  |  | 0 | return undef; | 
| 1461 |  |  |  |  |  |  | } | 
| 1462 |  |  |  |  |  |  |  | 
| 1463 | 0 | 0 |  |  |  | 0 | $self->_imap_command( { addtag => 0, addcrlf => 0, tag => $tag }, $string ) | 
| 1464 |  |  |  |  |  |  | or return undef; | 
| 1465 |  |  |  |  |  |  |  | 
| 1466 | 0 | 0 |  |  |  | 0 | $self->{History}{$tag} = $self->{History}{ $self->Count } | 
| 1467 |  |  |  |  |  |  | unless $tag eq $self->Count; | 
| 1468 |  |  |  |  |  |  |  | 
| 1469 | 0 |  |  |  |  | 0 | return $self->Results; | 
| 1470 |  |  |  |  |  |  | } | 
| 1471 |  |  |  |  |  |  |  | 
| 1472 |  |  |  |  |  |  | # _record saves the conversation into the History structure: | 
| 1473 |  |  |  |  |  |  | sub _record { | 
| 1474 | 0 |  |  | 0 |  | 0 | my ( $self, $count, $array ) = @_; | 
| 1475 | 0 | 0 | 0 |  |  | 0 | if ( $array->[DATA] =~ /^\d+ LOGIN/i && !$self->Showcredentials ) { | 
| 1476 | 0 |  |  |  |  | 0 | $array->[DATA] =~ s/LOGIN.*/LOGIN XXXXXXXX XXXXXXXX/i; | 
| 1477 |  |  |  |  |  |  | } | 
| 1478 |  |  |  |  |  |  |  | 
| 1479 | 0 |  |  |  |  | 0 | push @{ $self->{History}{$count} }, $array; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1480 |  |  |  |  |  |  | } | 
| 1481 |  |  |  |  |  |  |  | 
| 1482 |  |  |  |  |  |  | # try to avoid exposing auth info via debug unless Showcredentials is true | 
| 1483 |  |  |  |  |  |  | sub _redact_line { | 
| 1484 | 0 |  |  | 0 |  | 0 | my ( $self, $string ) = @_; | 
| 1485 | 0 | 0 |  |  |  | 0 | $self->Showcredentials and return undef; | 
| 1486 |  |  |  |  |  |  |  | 
| 1487 | 0 |  |  |  |  | 0 | my ( $tag, $cmd ) = ( $self->Count, undef ); | 
| 1488 | 0 |  |  |  |  | 0 | my $retext = "[Redact: Count=$tag Showcredentials=OFF]"; | 
| 1489 | 0 |  |  |  |  | 0 | my $show   = $retext; | 
| 1490 |  |  |  |  |  |  |  | 
| 1491 |  |  |  |  |  |  | # tagged command? | 
| 1492 | 0 | 0 |  |  |  | 0 | if ( $string =~ s/^($tag\s+(\S+)\s+)// ) { | 
| 1493 | 0 |  |  |  |  | 0 | ( $show, $cmd ) = ( $1, $2 ); | 
| 1494 |  |  |  |  |  |  |  | 
| 1495 |  |  |  |  |  |  | # login | 
| 1496 | 0 | 0 |  |  |  | 0 | if ( $cmd =~ /login/i ) { | 
|  |  | 0 |  |  |  |  |  | 
| 1497 |  |  |  |  |  |  |  | 
| 1498 |  |  |  |  |  |  | # username as literal | 
| 1499 | 0 | 0 |  |  |  | 0 | if ( $string =~ /^{/ ) { | 
|  |  | 0 |  |  |  |  |  | 
| 1500 | 0 |  |  |  |  | 0 | $show .= $string; | 
| 1501 |  |  |  |  |  |  | } | 
| 1502 |  |  |  |  |  |  |  | 
| 1503 |  |  |  |  |  |  | # username (possibly quoted) string, then literal? password | 
| 1504 |  |  |  |  |  |  | elsif ( $string =~ s/^((?:"(?>(?:(?>[^"\\]+)|\\.)*)"|\S+)\s*)// ) { | 
| 1505 | 0 |  |  |  |  | 0 | $show .= $1; | 
| 1506 | 0 | 0 |  |  |  | 0 | $show .= ( $string =~ /^{/ ) ? $string : $retext; | 
| 1507 |  |  |  |  |  |  | } | 
| 1508 |  |  |  |  |  |  | } | 
| 1509 |  |  |  |  |  |  | elsif ( $cmd =~ /^auth/i ) { | 
| 1510 | 0 |  |  |  |  | 0 | $show .= $string; | 
| 1511 |  |  |  |  |  |  | } | 
| 1512 |  |  |  |  |  |  | else { | 
| 1513 | 0 |  |  |  |  | 0 | return undef;    # show it all | 
| 1514 |  |  |  |  |  |  | } | 
| 1515 |  |  |  |  |  |  | } | 
| 1516 |  |  |  |  |  |  |  | 
| 1517 | 0 |  |  |  |  | 0 | return $show; | 
| 1518 |  |  |  |  |  |  | } | 
| 1519 |  |  |  |  |  |  |  | 
| 1520 |  |  |  |  |  |  | # _send_line handles literal data and supports the Prewritemethod | 
| 1521 |  |  |  |  |  |  | sub _send_line { | 
| 1522 | 0 |  |  | 0 |  | 0 | my ( $self, $string, $suppress ) = @_; | 
| 1523 |  |  |  |  |  |  |  | 
| 1524 | 0 | 0 |  |  |  | 0 | $string =~ s/$CR?$LF?$/$CRLF/o | 
| 1525 |  |  |  |  |  |  | unless $suppress; | 
| 1526 |  |  |  |  |  |  |  | 
| 1527 |  |  |  |  |  |  | # handle case where string contains a literal | 
| 1528 | 0 | 0 |  |  |  | 0 | if ( $string =~ s/^([^$LF\{]*\{\d+\}$CRLF)(?=.)//o ) { | 
| 1529 | 0 |  |  |  |  | 0 | my $first = $1; | 
| 1530 | 0 | 0 |  |  |  | 0 | if ( $self->Debug ) { | 
| 1531 | 0 | 0 | 0 |  |  | 0 | my $dat = | 
| 1532 |  |  |  |  |  |  | ( $self->IsConnected and !$self->IsAuthenticated ) | 
| 1533 |  |  |  |  |  |  | ? $self->_redact_line($string) | 
| 1534 |  |  |  |  |  |  | : undef; | 
| 1535 | 0 |  | 0 |  |  | 0 | $self->_debug( "Sending literal: $first\tthen: ", $dat || $string ); | 
| 1536 |  |  |  |  |  |  | } | 
| 1537 | 0 | 0 |  |  |  | 0 | $self->_send_line($first) or return undef; | 
| 1538 |  |  |  |  |  |  |  | 
| 1539 |  |  |  |  |  |  | # look for "$tag NO" or "+ ..." | 
| 1540 | 0 | 0 |  |  |  | 0 | my $code = $self->_get_response( $self->Count, '+' ) or return undef; | 
| 1541 | 0 | 0 |  |  |  | 0 | return undef unless $code eq '+'; | 
| 1542 |  |  |  |  |  |  | } | 
| 1543 |  |  |  |  |  |  |  | 
| 1544 |  |  |  |  |  |  | # non-literal part continues... | 
| 1545 | 0 | 0 |  |  |  | 0 | if ( my $prew = $self->Prewritemethod ) { | 
| 1546 | 0 |  |  |  |  | 0 | $string = $prew->( $self, $string ); | 
| 1547 |  |  |  |  |  |  | } | 
| 1548 |  |  |  |  |  |  |  | 
| 1549 | 0 | 0 |  |  |  | 0 | if ( $self->Debug ) { | 
| 1550 | 0 | 0 | 0 |  |  | 0 | my $dat = | 
| 1551 |  |  |  |  |  |  | ( $self->IsConnected and !$self->IsAuthenticated ) | 
| 1552 |  |  |  |  |  |  | ? $self->_redact_line($string) | 
| 1553 |  |  |  |  |  |  | : undef; | 
| 1554 | 0 |  | 0 |  |  | 0 | $self->_debug( "Sending: ", $dat || $string ); | 
| 1555 |  |  |  |  |  |  | } | 
| 1556 |  |  |  |  |  |  |  | 
| 1557 | 0 | 0 |  |  |  | 0 | unless ( $self->IsConnected ) { | 
| 1558 | 0 |  |  |  |  | 0 | $self->LastError("NO not connected"); | 
| 1559 | 0 |  |  |  |  | 0 | return undef; | 
| 1560 |  |  |  |  |  |  | } | 
| 1561 |  |  |  |  |  |  |  | 
| 1562 | 0 |  |  |  |  | 0 | $self->_send_bytes( \$string ); | 
| 1563 |  |  |  |  |  |  | } | 
| 1564 |  |  |  |  |  |  |  | 
| 1565 |  |  |  |  |  |  | sub _send_bytes($) { | 
| 1566 | 0 |  |  | 0 |  | 0 | my ( $self, $byteref ) = @_; | 
| 1567 | 0 |  |  |  |  | 0 | my ( $total, $temperrs, $maxwrite ) = ( 0, 0, 0 ); | 
| 1568 | 0 |  |  |  |  | 0 | my $waittime = .02; | 
| 1569 | 0 |  |  |  |  | 0 | my @previous_writes; | 
| 1570 |  |  |  |  |  |  |  | 
| 1571 | 0 |  |  |  |  | 0 | my $maxagain = $self->Maxtemperrors; | 
| 1572 | 0 | 0 | 0 |  |  | 0 | undef $maxagain if $maxagain and lc($maxagain) eq 'unlimited'; | 
| 1573 |  |  |  |  |  |  |  | 
| 1574 | 0 |  |  |  |  | 0 | local $SIG{PIPE} = 'IGNORE';    # handle SIGPIPE as normal error | 
| 1575 |  |  |  |  |  |  |  | 
| 1576 | 0 |  |  |  |  | 0 | my $socket = $self->Socket; | 
| 1577 | 0 |  |  |  |  | 0 | while ( $total < length $$byteref ) { | 
| 1578 | 0 |  |  |  |  | 0 | my $written = | 
| 1579 |  |  |  |  |  |  | syswrite( $socket, $$byteref, length($$byteref) - $total, $total ); | 
| 1580 |  |  |  |  |  |  |  | 
| 1581 | 0 | 0 |  |  |  | 0 | if ( defined $written ) { | 
| 1582 | 0 |  |  |  |  | 0 | $temperrs = 0; | 
| 1583 | 0 |  |  |  |  | 0 | $total += $written; | 
| 1584 | 0 |  |  |  |  | 0 | next; | 
| 1585 |  |  |  |  |  |  | } | 
| 1586 |  |  |  |  |  |  |  | 
| 1587 | 0 | 0 |  |  |  | 0 | if ( $! == EAGAIN ) { | 
| 1588 | 0 | 0 | 0 |  |  | 0 | if ( defined $maxagain && $temperrs++ > $maxagain ) { | 
| 1589 | 0 |  |  |  |  | 0 | $self->LastError("Persistent error '$!'"); | 
| 1590 | 0 |  |  |  |  | 0 | return undef; | 
| 1591 |  |  |  |  |  |  | } | 
| 1592 |  |  |  |  |  |  |  | 
| 1593 |  |  |  |  |  |  | $waittime = | 
| 1594 | 0 |  |  |  |  | 0 | $self->_optimal_sleep( $maxwrite, $waittime, \@previous_writes ); | 
| 1595 | 0 |  |  |  |  | 0 | next; | 
| 1596 |  |  |  |  |  |  | } | 
| 1597 |  |  |  |  |  |  |  | 
| 1598 |  |  |  |  |  |  | # Unconnected might be apropos for more than just these? | 
| 1599 | 0 | 0 |  |  |  | 0 | my $emsg = $! ? "$!" : "no error caught"; | 
| 1600 | 0 | 0 | 0 |  |  | 0 | $self->State(Unconnected) | 
|  |  |  | 0 |  |  |  |  | 
| 1601 |  |  |  |  |  |  | if ( $! == EPIPE or $! == ECONNRESET or $! == EBADF ); | 
| 1602 | 0 |  |  |  |  | 0 | $self->LastError("Write failed '$emsg'"); | 
| 1603 |  |  |  |  |  |  |  | 
| 1604 | 0 |  |  |  |  | 0 | return undef;    # no luck | 
| 1605 |  |  |  |  |  |  | } | 
| 1606 |  |  |  |  |  |  |  | 
| 1607 | 0 |  |  |  |  | 0 | $self->_debug("Sent $total bytes"); | 
| 1608 | 0 |  |  |  |  | 0 | return $total; | 
| 1609 |  |  |  |  |  |  | } | 
| 1610 |  |  |  |  |  |  |  | 
| 1611 |  |  |  |  |  |  | # _read_line: read one line from the socket | 
| 1612 |  |  |  |  |  |  | # | 
| 1613 |  |  |  |  |  |  | # $output = $self->_read_line($literal_callback) | 
| 1614 |  |  |  |  |  |  | #    literal_callback is optional, but if supplied it must be either | 
| 1615 |  |  |  |  |  |  | #    be a filehandle, coderef, or undef. | 
| 1616 |  |  |  |  |  |  | # | 
| 1617 |  |  |  |  |  |  | #    Returns a reference to an array of arrays, i.e.: | 
| 1618 |  |  |  |  |  |  | #    $output = [ | 
| 1619 |  |  |  |  |  |  | #        [ $index, 'OUTPUT|LITERAL', $output_line ], | 
| 1620 |  |  |  |  |  |  | #        [ $index, 'OUTPUT|LITERAL', $output_line ], | 
| 1621 |  |  |  |  |  |  | #        ... | 
| 1622 |  |  |  |  |  |  | #    \]; | 
| 1623 |  |  |  |  |  |  |  | 
| 1624 |  |  |  |  |  |  | # BUG?: make memory more efficient | 
| 1625 |  |  |  |  |  |  | sub _read_line { | 
| 1626 | 0 |  |  | 0 |  | 0 | my ( $self, $literal_callback ) = @_; | 
| 1627 |  |  |  |  |  |  |  | 
| 1628 | 0 |  |  |  |  | 0 | my $socket = $self->Socket; | 
| 1629 | 0 | 0 | 0 |  |  | 0 | unless ( $self->IsConnected && $socket ) { | 
| 1630 | 0 |  |  |  |  | 0 | $self->LastError("NO not connected"); | 
| 1631 | 0 |  |  |  |  | 0 | return undef; | 
| 1632 |  |  |  |  |  |  | } | 
| 1633 |  |  |  |  |  |  |  | 
| 1634 | 0 |  |  |  |  | 0 | my $iBuffer = ""; | 
| 1635 | 0 |  |  |  |  | 0 | my $oBuffer = []; | 
| 1636 | 0 |  |  |  |  | 0 | my $index   = $self->_next_index; | 
| 1637 | 0 |  |  |  |  | 0 | my $timeout = $self->Timeout; | 
| 1638 | 0 |  | 0 |  |  | 0 | my $readlen = $self->Buffer || 4096; | 
| 1639 | 0 |  |  |  |  | 0 | my $transno = $self->Transaction; | 
| 1640 |  |  |  |  |  |  |  | 
| 1641 | 0 |  |  |  |  | 0 | my $literal_cbtype = ""; | 
| 1642 | 0 | 0 |  |  |  | 0 | if ($literal_callback) { | 
| 1643 | 0 | 0 |  |  |  | 0 | if ( UNIVERSAL::isa( $literal_callback, "GLOB" ) ) { | 
|  |  | 0 |  |  |  |  |  | 
| 1644 | 0 |  |  |  |  | 0 | $literal_cbtype = "GLOB"; | 
| 1645 |  |  |  |  |  |  | } | 
| 1646 |  |  |  |  |  |  | elsif ( UNIVERSAL::isa( $literal_callback, "CODE" ) ) { | 
| 1647 | 0 |  |  |  |  | 0 | $literal_cbtype = "CODE"; | 
| 1648 |  |  |  |  |  |  | } | 
| 1649 |  |  |  |  |  |  | else { | 
| 1650 | 0 |  |  |  |  | 0 | $self->LastError( "'$literal_callback' is an " | 
| 1651 |  |  |  |  |  |  | . "invalid callback; must be a filehandle or CODE" ); | 
| 1652 | 0 |  |  |  |  | 0 | return undef; | 
| 1653 |  |  |  |  |  |  | } | 
| 1654 |  |  |  |  |  |  | } | 
| 1655 |  |  |  |  |  |  |  | 
| 1656 | 0 |  |  |  |  | 0 | my $temperrs = 0; | 
| 1657 | 0 |  |  |  |  | 0 | my $maxagain = $self->Maxtemperrors; | 
| 1658 | 0 | 0 | 0 |  |  | 0 | undef $maxagain if $maxagain and lc($maxagain) eq 'unlimited'; | 
| 1659 |  |  |  |  |  |  |  | 
| 1660 | 0 |  | 0 |  |  | 0 | until ( | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 1661 |  |  |  |  |  |  | @$oBuffer    # there's stuff in output buffer: | 
| 1662 |  |  |  |  |  |  | && $oBuffer->[-1][TYPE] eq 'OUTPUT'    # that thing is an output line: | 
| 1663 |  |  |  |  |  |  | && $oBuffer->[-1][DATA] =~ | 
| 1664 |  |  |  |  |  |  | /$CR?$LF$/o            # the last thing there has cr-lf: | 
| 1665 |  |  |  |  |  |  | && !length $iBuffer    # and the input buffer has been MT'ed: | 
| 1666 |  |  |  |  |  |  | ) | 
| 1667 |  |  |  |  |  |  | { | 
| 1668 |  |  |  |  |  |  |  | 
| 1669 | 0 | 0 |  |  |  | 0 | if ($timeout) { | 
| 1670 | 0 |  |  |  |  | 0 | my $rc = $self->_read_more( $socket, $timeout ); | 
| 1671 | 0 | 0 |  |  |  | 0 | return undef unless ( $rc > 0 ); | 
| 1672 |  |  |  |  |  |  | } | 
| 1673 |  |  |  |  |  |  |  | 
| 1674 | 0 |  |  |  |  | 0 | my $emsg; | 
| 1675 | 0 |  |  |  |  | 0 | my $ret = | 
| 1676 |  |  |  |  |  |  | $self->_sysread( $socket, \$iBuffer, $readlen, length $iBuffer ); | 
| 1677 |  |  |  |  |  |  |  | 
| 1678 | 0 | 0 |  |  |  | 0 | if ($timeout) { | 
| 1679 | 0 | 0 |  |  |  | 0 | if ( defined $ret ) { | 
| 1680 | 0 |  |  |  |  | 0 | $temperrs = 0; | 
| 1681 |  |  |  |  |  |  | } | 
| 1682 |  |  |  |  |  |  | else { | 
| 1683 | 0 |  |  |  |  | 0 | $emsg = "error while reading data from server: $!"; | 
| 1684 | 0 | 0 |  |  |  | 0 | if ( $! == ECONNRESET ) { | 
|  |  | 0 |  |  |  |  |  | 
| 1685 | 0 |  |  |  |  | 0 | $self->State(Unconnected); | 
| 1686 |  |  |  |  |  |  | } | 
| 1687 |  |  |  |  |  |  | elsif ( $! == EAGAIN ) { | 
| 1688 | 0 | 0 | 0 |  |  | 0 | if ( defined $maxagain && $temperrs++ >= $maxagain ) { | 
| 1689 | 0 |  |  |  |  | 0 | $emsg .= " ($temperrs)"; | 
| 1690 |  |  |  |  |  |  | } | 
| 1691 |  |  |  |  |  |  | else { | 
| 1692 | 0 |  |  |  |  | 0 | next;    # try again | 
| 1693 |  |  |  |  |  |  | } | 
| 1694 |  |  |  |  |  |  | } | 
| 1695 |  |  |  |  |  |  | } | 
| 1696 |  |  |  |  |  |  | } | 
| 1697 |  |  |  |  |  |  |  | 
| 1698 | 0 | 0 | 0 |  |  | 0 | if ( defined $ret && $ret == 0 ) {    # Caught EOF... | 
| 1699 | 0 |  |  |  |  | 0 | $emsg = "socket closed while reading data from server"; | 
| 1700 | 0 |  |  |  |  | 0 | $self->State(Unconnected); | 
| 1701 |  |  |  |  |  |  | } | 
| 1702 |  |  |  |  |  |  |  | 
| 1703 |  |  |  |  |  |  | # save errors and return | 
| 1704 | 0 | 0 |  |  |  | 0 | if ($emsg) { | 
| 1705 | 0 |  |  |  |  | 0 | $self->LastError($emsg); | 
| 1706 | 0 |  |  |  |  | 0 | $self->_record( | 
| 1707 |  |  |  |  |  |  | $transno, | 
| 1708 |  |  |  |  |  |  | [ | 
| 1709 |  |  |  |  |  |  | $self->_next_index($transno), "ERROR", "$transno * NO $emsg" | 
| 1710 |  |  |  |  |  |  | ] | 
| 1711 |  |  |  |  |  |  | ); | 
| 1712 | 0 |  |  |  |  | 0 | return undef; | 
| 1713 |  |  |  |  |  |  | } | 
| 1714 |  |  |  |  |  |  |  | 
| 1715 | 0 |  |  |  |  | 0 | while ( $iBuffer =~ s/^(.*?$CR?$LF)//o )    # consume line | 
| 1716 |  |  |  |  |  |  | { | 
| 1717 | 0 |  |  |  |  | 0 | my $current_line = $1; | 
| 1718 | 0 | 0 |  |  |  | 0 | if ( $current_line !~ s/\{(\d+)\}$CR?$LF$//o ) { | 
| 1719 | 0 |  |  |  |  | 0 | push @$oBuffer, [ $index++, 'OUTPUT', $current_line ]; | 
| 1720 | 0 |  |  |  |  | 0 | next; | 
| 1721 |  |  |  |  |  |  | } | 
| 1722 |  |  |  |  |  |  |  | 
| 1723 | 0 |  |  |  |  | 0 | push @$oBuffer, [ $index++, 'OUTPUT', $current_line ]; | 
| 1724 |  |  |  |  |  |  |  | 
| 1725 |  |  |  |  |  |  | ## handle LITERAL | 
| 1726 |  |  |  |  |  |  | # BLAH BLAH {nnn}$CRLF | 
| 1727 |  |  |  |  |  |  | # [nnn bytes of literally transmitted stuff] | 
| 1728 |  |  |  |  |  |  | # [part of line that follows literal data]$CRLF | 
| 1729 |  |  |  |  |  |  |  | 
| 1730 | 0 |  |  |  |  | 0 | my $expected_size = $1; | 
| 1731 |  |  |  |  |  |  |  | 
| 1732 | 0 |  |  |  |  | 0 | $self->_debug( "LITERAL: received literal in line " | 
| 1733 |  |  |  |  |  |  | . "$current_line of length $expected_size; attempting to " | 
| 1734 |  |  |  |  |  |  | . "retrieve from the " | 
| 1735 |  |  |  |  |  |  | . length($iBuffer) | 
| 1736 |  |  |  |  |  |  | . " bytes in: $iBuffer" ); | 
| 1737 |  |  |  |  |  |  |  | 
| 1738 | 0 |  |  |  |  | 0 | my $litstring; | 
| 1739 | 0 | 0 |  |  |  | 0 | if ( length $iBuffer >= $expected_size ) { | 
| 1740 |  |  |  |  |  |  |  | 
| 1741 |  |  |  |  |  |  | # already received all data | 
| 1742 | 0 |  |  |  |  | 0 | $litstring = substr $iBuffer, 0, $expected_size, ''; | 
| 1743 |  |  |  |  |  |  | } | 
| 1744 |  |  |  |  |  |  | else {    # literal data still to arrive | 
| 1745 | 0 |  |  |  |  | 0 | $litstring = $iBuffer; | 
| 1746 | 0 |  |  |  |  | 0 | $iBuffer   = ''; | 
| 1747 |  |  |  |  |  |  |  | 
| 1748 | 0 |  |  |  |  | 0 | my $litreadb = length($litstring); | 
| 1749 | 0 |  |  |  |  | 0 | my $temperrs = 0; | 
| 1750 | 0 |  |  |  |  | 0 | my $maxagain = $self->Maxtemperrors; | 
| 1751 | 0 | 0 | 0 |  |  | 0 | undef $maxagain if $maxagain and lc($maxagain) eq 'unlimited'; | 
| 1752 |  |  |  |  |  |  |  | 
| 1753 | 0 |  |  |  |  | 0 | while ( $expected_size > $litreadb ) { | 
| 1754 | 0 | 0 |  |  |  | 0 | if ($timeout) { | 
| 1755 | 0 |  |  |  |  | 0 | my $rc = $self->_read_more( $socket, $timeout ); | 
| 1756 | 0 | 0 |  |  |  | 0 | return undef unless ( $rc > 0 ); | 
| 1757 |  |  |  |  |  |  | } | 
| 1758 |  |  |  |  |  |  | else {    # 25 ms before retry | 
| 1759 | 0 |  |  |  |  | 0 | CORE::select( undef, undef, undef, 0.025 ); | 
| 1760 |  |  |  |  |  |  | } | 
| 1761 |  |  |  |  |  |  |  | 
| 1762 |  |  |  |  |  |  | # $litstring is emptied when $literal_cbtype is GLOB | 
| 1763 | 0 |  |  |  |  | 0 | my $ret = | 
| 1764 |  |  |  |  |  |  | $self->_sysread( $socket, \$litstring, | 
| 1765 |  |  |  |  |  |  | $expected_size - $litreadb, | 
| 1766 |  |  |  |  |  |  | length($litstring) ); | 
| 1767 |  |  |  |  |  |  |  | 
| 1768 | 0 | 0 |  |  |  | 0 | if ($timeout) { | 
| 1769 | 0 | 0 |  |  |  | 0 | if ( defined $ret ) { | 
| 1770 | 0 |  |  |  |  | 0 | $temperrs = 0; | 
| 1771 |  |  |  |  |  |  | } | 
| 1772 |  |  |  |  |  |  | else { | 
| 1773 | 0 |  |  |  |  | 0 | $emsg = "error while reading data from server: $!"; | 
| 1774 | 0 | 0 |  |  |  | 0 | if ( $! == ECONNRESET ) { | 
|  |  | 0 |  |  |  |  |  | 
| 1775 | 0 |  |  |  |  | 0 | $self->State(Unconnected); | 
| 1776 |  |  |  |  |  |  | } | 
| 1777 |  |  |  |  |  |  | elsif ( $! == EAGAIN ) { | 
| 1778 | 0 | 0 | 0 |  |  | 0 | if ( defined $maxagain | 
| 1779 |  |  |  |  |  |  | && $temperrs++ >= $maxagain ) | 
| 1780 |  |  |  |  |  |  | { | 
| 1781 | 0 |  |  |  |  | 0 | $emsg .= " ($temperrs)"; | 
| 1782 |  |  |  |  |  |  | } | 
| 1783 |  |  |  |  |  |  | else { | 
| 1784 | 0 |  |  |  |  | 0 | undef $emsg; | 
| 1785 | 0 |  |  |  |  | 0 | next;    # try again | 
| 1786 |  |  |  |  |  |  | } | 
| 1787 |  |  |  |  |  |  | } | 
| 1788 |  |  |  |  |  |  | } | 
| 1789 |  |  |  |  |  |  | } | 
| 1790 |  |  |  |  |  |  |  | 
| 1791 |  |  |  |  |  |  | # EOF: note IO::Socket::SSL does not support eof() | 
| 1792 | 0 | 0 | 0 |  |  | 0 | if ( defined $ret and $ret == 0 ) { | 
|  |  | 0 | 0 |  |  |  |  | 
| 1793 | 0 |  |  |  |  | 0 | $emsg = "socket closed while reading data from server"; | 
| 1794 | 0 |  |  |  |  | 0 | $self->State(Unconnected); | 
| 1795 |  |  |  |  |  |  | } | 
| 1796 |  |  |  |  |  |  | elsif ( defined $ret and $ret > 0 ) { | 
| 1797 | 0 |  |  |  |  | 0 | $litreadb += $ret; | 
| 1798 |  |  |  |  |  |  |  | 
| 1799 |  |  |  |  |  |  | # conserve memory when using literal_callback GLOB | 
| 1800 | 0 | 0 |  |  |  | 0 | if ( $literal_cbtype eq "GLOB" ) { | 
| 1801 | 0 |  |  |  |  | 0 | print $literal_callback $litstring; | 
| 1802 | 0 | 0 |  |  |  | 0 | $litstring = "" unless ($emsg); | 
| 1803 |  |  |  |  |  |  | } | 
| 1804 |  |  |  |  |  |  | } | 
| 1805 |  |  |  |  |  |  |  | 
| 1806 | 0 | 0 |  |  |  | 0 | $self->_debug( "Received ret=" | 
| 1807 |  |  |  |  |  |  | . ( defined($ret) ? $ret : "" ) | 
| 1808 |  |  |  |  |  |  | . " $litreadb of $expected_size" ); | 
| 1809 |  |  |  |  |  |  |  | 
| 1810 |  |  |  |  |  |  | # save errors and return | 
| 1811 | 0 | 0 |  |  |  | 0 | if ($emsg) { | 
| 1812 | 0 |  |  |  |  | 0 | $self->LastError($emsg); | 
| 1813 | 0 |  |  |  |  | 0 | $self->_record( | 
| 1814 |  |  |  |  |  |  | $transno, | 
| 1815 |  |  |  |  |  |  | [ | 
| 1816 |  |  |  |  |  |  | $self->_next_index($transno), "ERROR", | 
| 1817 |  |  |  |  |  |  | "$transno * NO $emsg" | 
| 1818 |  |  |  |  |  |  | ] | 
| 1819 |  |  |  |  |  |  | ); | 
| 1820 | 0 | 0 |  |  |  | 0 | $litstring = "" unless defined $litstring; | 
| 1821 | 0 |  |  |  |  | 0 | $self->_debug( "ERROR while processing LITERAL, " | 
| 1822 |  |  |  |  |  |  | . " buffer=\n" | 
| 1823 |  |  |  |  |  |  | . $litstring | 
| 1824 |  |  |  |  |  |  | . "\n" ); | 
| 1825 | 0 |  |  |  |  | 0 | return undef; | 
| 1826 |  |  |  |  |  |  | } | 
| 1827 |  |  |  |  |  |  | } | 
| 1828 |  |  |  |  |  |  | } | 
| 1829 |  |  |  |  |  |  |  | 
| 1830 | 0 | 0 |  |  |  | 0 | if ( defined $litstring ) { | 
| 1831 | 0 | 0 |  |  |  | 0 | if ( $literal_cbtype eq "GLOB" ) { | 
|  |  | 0 |  |  |  |  |  | 
| 1832 | 0 |  |  |  |  | 0 | print $literal_callback $litstring; | 
| 1833 |  |  |  |  |  |  | } | 
| 1834 |  |  |  |  |  |  | elsif ( $literal_cbtype eq "CODE" ) { | 
| 1835 | 0 |  |  |  |  | 0 | $literal_callback->($litstring); | 
| 1836 |  |  |  |  |  |  | } | 
| 1837 |  |  |  |  |  |  | } | 
| 1838 |  |  |  |  |  |  |  | 
| 1839 | 0 | 0 |  |  |  | 0 | push @$oBuffer, [ $index++, 'LITERAL', $litstring ] | 
| 1840 |  |  |  |  |  |  | if ( $literal_cbtype ne "GLOB" ); | 
| 1841 |  |  |  |  |  |  | } | 
| 1842 |  |  |  |  |  |  | } | 
| 1843 |  |  |  |  |  |  |  | 
| 1844 | 0 | 0 |  |  |  | 0 | $self->_debug( "Read: " . join "", map { "\t" . $_->[DATA] } @$oBuffer ) | 
|  | 0 |  |  |  |  | 0 |  | 
| 1845 |  |  |  |  |  |  | if ( $self->Debug ); | 
| 1846 |  |  |  |  |  |  |  | 
| 1847 | 0 | 0 |  |  |  | 0 | @$oBuffer ? $oBuffer : undef; | 
| 1848 |  |  |  |  |  |  | } | 
| 1849 |  |  |  |  |  |  |  | 
| 1850 |  |  |  |  |  |  | sub _sysread { | 
| 1851 | 0 |  |  | 0 |  | 0 | my ( $self, $fh, $buf, $len, $off ) = @_; | 
| 1852 | 0 |  |  |  |  | 0 | my $rm = $self->Readmethod; | 
| 1853 | 0 | 0 |  |  |  | 0 | $rm ? $rm->(@_) : sysread( $fh, $$buf, $len, $off ); | 
| 1854 |  |  |  |  |  |  | } | 
| 1855 |  |  |  |  |  |  |  | 
| 1856 |  |  |  |  |  |  | sub _read_more { | 
| 1857 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 1858 | 0 |  |  |  |  | 0 | my $rm   = $self->Readmoremethod; | 
| 1859 | 0 | 0 |  |  |  | 0 | $rm ? $rm->( $self, @_ ) : $self->__read_more(@_); | 
| 1860 |  |  |  |  |  |  | } | 
| 1861 |  |  |  |  |  |  |  | 
| 1862 |  |  |  |  |  |  | sub __read_more { | 
| 1863 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 1864 | 0 | 0 |  |  |  | 0 | my $opt = ref( $_[0] ) eq "HASH" ? shift : {}; | 
| 1865 | 0 |  |  |  |  | 0 | my ( $socket, $timeout ) = @_; | 
| 1866 |  |  |  |  |  |  |  | 
| 1867 |  |  |  |  |  |  | # IO::Socket::SSL buffers some data internally, so there might be some | 
| 1868 |  |  |  |  |  |  | # data available from the previous sysread of which the file-handle | 
| 1869 |  |  |  |  |  |  | # (used by select()) doesn't know of. | 
| 1870 | 0 | 0 | 0 |  |  | 0 | return 1 if $socket->isa("IO::Socket::SSL") && $socket->pending; | 
| 1871 |  |  |  |  |  |  |  | 
| 1872 | 0 |  |  |  |  | 0 | my $rvec = ''; | 
| 1873 | 0 |  |  |  |  | 0 | vec( $rvec, fileno($socket), 1 ) = 1; | 
| 1874 |  |  |  |  |  |  |  | 
| 1875 | 0 |  |  |  |  | 0 | my $rc = CORE::select( $rvec, undef, $rvec, $timeout ); | 
| 1876 |  |  |  |  |  |  |  | 
| 1877 |  |  |  |  |  |  | # fast track success | 
| 1878 | 0 | 0 |  |  |  | 0 | return $rc if $rc > 0; | 
| 1879 |  |  |  |  |  |  |  | 
| 1880 |  |  |  |  |  |  | # by default set an error on timeout | 
| 1881 |  |  |  |  |  |  | my $err_on_timeout = | 
| 1882 | 0 | 0 |  |  |  | 0 | exists $opt->{error_on_timeout} ? $opt->{error_on_timeout} : 1; | 
| 1883 |  |  |  |  |  |  |  | 
| 1884 |  |  |  |  |  |  | # $rc is 0 then we timed out | 
| 1885 | 0 | 0 | 0 |  |  | 0 | return $rc if !$rc and !$err_on_timeout; | 
| 1886 |  |  |  |  |  |  |  | 
| 1887 |  |  |  |  |  |  | # set the appropriate error and return | 
| 1888 | 0 |  |  |  |  | 0 | my $transno = $self->Transaction; | 
| 1889 | 0 | 0 |  |  |  | 0 | my $msg = | 
|  |  | 0 |  |  |  |  |  | 
| 1890 |  |  |  |  |  |  | ( $rc ? "error($rc)" : "timeout" ) | 
| 1891 |  |  |  |  |  |  | . " waiting ${timeout}s for data from server" | 
| 1892 |  |  |  |  |  |  | . ( $! ? ": $!" : "" ); | 
| 1893 | 0 |  |  |  |  | 0 | $self->LastError($msg); | 
| 1894 | 0 |  |  |  |  | 0 | $self->_record( $transno, | 
| 1895 |  |  |  |  |  |  | [ $self->_next_index($transno), "ERROR", "$transno * NO $msg" ] ); | 
| 1896 | 0 |  |  |  |  | 0 | $self->_disconnect;    # BUG: can not handle timeouts gracefully | 
| 1897 | 0 |  |  |  |  | 0 | return $rc; | 
| 1898 |  |  |  |  |  |  | } | 
| 1899 |  |  |  |  |  |  |  | 
| 1900 |  |  |  |  |  |  | sub _trans_index() { | 
| 1901 | 0 |  |  | 0 |  | 0 | sort { $a <=> $b } keys %{ $_[0]->{History} }; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 1902 |  |  |  |  |  |  | } | 
| 1903 |  |  |  |  |  |  |  | 
| 1904 |  |  |  |  |  |  | # all default to last transaction | 
| 1905 |  |  |  |  |  |  | sub _transaction(;$) { | 
| 1906 | 0 | 0 | 0 | 0 |  | 0 | @{ $_[0]->{History}{ $_[1] || $_[0]->Transaction } || [] }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1907 |  |  |  |  |  |  | } | 
| 1908 |  |  |  |  |  |  |  | 
| 1909 |  |  |  |  |  |  | sub _trans_data(;$) { | 
| 1910 | 0 |  |  | 0 |  | 0 | map { $_->[DATA] } $_[0]->_transaction( $_[1] ); | 
|  | 0 |  |  |  |  | 0 |  | 
| 1911 |  |  |  |  |  |  | } | 
| 1912 |  |  |  |  |  |  |  | 
| 1913 |  |  |  |  |  |  | sub _escaped_trans_data(;$) { | 
| 1914 | 0 |  |  | 0 |  | 0 | my ( $self, $trans ) = @_; | 
| 1915 | 0 |  |  |  |  | 0 | my @a; | 
| 1916 | 0 |  |  |  |  | 0 | my $prevwasliteral = 0; | 
| 1917 | 0 |  |  |  |  | 0 | foreach my $line ( $self->_transaction($trans) ) { | 
| 1918 | 0 | 0 |  |  |  | 0 | next unless defined $line; | 
| 1919 |  |  |  |  |  |  |  | 
| 1920 | 0 |  |  |  |  | 0 | my $data = $line->[DATA]; | 
| 1921 |  |  |  |  |  |  |  | 
| 1922 |  |  |  |  |  |  | # literal is appended to previous data | 
| 1923 | 0 | 0 |  |  |  | 0 | if ( $self->_is_literal($line) ) { | 
| 1924 | 0 |  |  |  |  | 0 | $data = $self->Escape($data); | 
| 1925 | 0 |  |  |  |  | 0 | $a[-1] .= qq("$data"); | 
| 1926 | 0 |  |  |  |  | 0 | $prevwasliteral = 1; | 
| 1927 |  |  |  |  |  |  | } | 
| 1928 |  |  |  |  |  |  | else { | 
| 1929 | 0 | 0 |  |  |  | 0 | if ($prevwasliteral) { | 
| 1930 | 0 |  |  |  |  | 0 | $a[-1] .= $data; | 
| 1931 |  |  |  |  |  |  | } | 
| 1932 |  |  |  |  |  |  | else { | 
| 1933 | 0 |  |  |  |  | 0 | push( @a, $data ); | 
| 1934 |  |  |  |  |  |  | } | 
| 1935 | 0 |  |  |  |  | 0 | $prevwasliteral = 0; | 
| 1936 |  |  |  |  |  |  | } | 
| 1937 |  |  |  |  |  |  | } | 
| 1938 |  |  |  |  |  |  |  | 
| 1939 | 0 | 0 |  |  |  | 0 | return wantarray ? @a : \@a; | 
| 1940 |  |  |  |  |  |  | } | 
| 1941 |  |  |  |  |  |  |  | 
| 1942 |  |  |  |  |  |  | sub Report { | 
| 1943 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 1944 | 0 |  |  |  |  | 0 | map { $self->_trans_data($_) } $self->_trans_index; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1945 |  |  |  |  |  |  | } | 
| 1946 |  |  |  |  |  |  |  | 
| 1947 |  |  |  |  |  |  | sub LastIMAPCommand(;$) { | 
| 1948 | 0 |  |  | 0 | 1 | 0 | my ( $self, $trans ) = @_; | 
| 1949 | 0 |  |  |  |  | 0 | my $msg = ( $self->_transaction($trans) )[0]; | 
| 1950 | 0 | 0 |  |  |  | 0 | $msg ? $msg->[DATA] : undef; | 
| 1951 |  |  |  |  |  |  | } | 
| 1952 |  |  |  |  |  |  |  | 
| 1953 |  |  |  |  |  |  | sub History(;$) { | 
| 1954 | 0 |  |  | 0 | 1 | 0 | my ( $self, $trans ) = @_; | 
| 1955 | 0 |  |  |  |  | 0 | my ( $cmd,  @a )     = $self->_trans_data($trans); | 
| 1956 | 0 | 0 |  |  |  | 0 | return wantarray ? @a : \@a; | 
| 1957 |  |  |  |  |  |  | } | 
| 1958 |  |  |  |  |  |  |  | 
| 1959 |  |  |  |  |  |  | sub Results(;$) { | 
| 1960 | 0 |  |  | 0 | 1 | 0 | my ( $self, $trans ) = @_; | 
| 1961 | 0 |  |  |  |  | 0 | my @a = $self->_trans_data($trans); | 
| 1962 | 0 | 0 |  |  |  | 0 | return wantarray ? @a : \@a; | 
| 1963 |  |  |  |  |  |  | } | 
| 1964 |  |  |  |  |  |  |  | 
| 1965 |  |  |  |  |  |  | sub _transaction_literals() { | 
| 1966 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 1967 | 0 |  |  |  |  | 0 | join '', map { $_->[DATA] } | 
| 1968 | 0 |  |  |  |  | 0 | grep { $self->_is_literal($_) } $self->_transaction; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1969 |  |  |  |  |  |  | } | 
| 1970 |  |  |  |  |  |  |  | 
| 1971 |  |  |  |  |  |  | sub Escaped_history { | 
| 1972 | 0 |  |  | 0 | 1 | 0 | my ( $self, $trans ) = @_; | 
| 1973 | 0 |  |  |  |  | 0 | my ( $cmd,  @a )     = $self->_escaped_trans_data($trans); | 
| 1974 | 0 | 0 |  |  |  | 0 | return wantarray ? @a : \@a; | 
| 1975 |  |  |  |  |  |  | } | 
| 1976 |  |  |  |  |  |  |  | 
| 1977 |  |  |  |  |  |  | sub Escaped_results { | 
| 1978 | 0 |  |  | 0 | 1 | 0 | my ( $self, $trans ) = @_; | 
| 1979 | 0 |  |  |  |  | 0 | my @a = $self->_escaped_trans_data($trans); | 
| 1980 | 0 | 0 |  |  |  | 0 | return wantarray ? @a : \@a; | 
| 1981 |  |  |  |  |  |  | } | 
| 1982 |  |  |  |  |  |  |  | 
| 1983 |  |  |  |  |  |  | sub Escape { | 
| 1984 | 1 |  |  | 1 | 0 | 3 | my $data = $_[1]; | 
| 1985 | 1 |  |  |  |  | 9 | $data =~ s/([\\\"])/\\$1/og; | 
| 1986 | 1 |  |  |  |  | 3 | return $data; | 
| 1987 |  |  |  |  |  |  | } | 
| 1988 |  |  |  |  |  |  |  | 
| 1989 |  |  |  |  |  |  | sub Unescape { | 
| 1990 | 0 |  |  | 0 | 0 | 0 | my $data = $_[1]; | 
| 1991 | 0 |  |  |  |  | 0 | $data =~ s/\\([\\\"])/$1/og; | 
| 1992 | 0 |  |  |  |  | 0 | return $data; | 
| 1993 |  |  |  |  |  |  | } | 
| 1994 |  |  |  |  |  |  |  | 
| 1995 |  |  |  |  |  |  | sub logout { | 
| 1996 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 1997 | 0 |  |  |  |  | 0 | my $rc = $self->_imap_command( "LOGOUT", "BYE" ); | 
| 1998 | 0 |  |  |  |  | 0 | $self->_disconnect; | 
| 1999 | 0 |  |  |  |  | 0 | return $rc; | 
| 2000 |  |  |  |  |  |  | } | 
| 2001 |  |  |  |  |  |  |  | 
| 2002 |  |  |  |  |  |  | sub _disconnect { | 
| 2003 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 2004 |  |  |  |  |  |  |  | 
| 2005 | 0 |  |  |  |  | 0 | delete $self->{CAPABILITY}; | 
| 2006 | 0 |  |  |  |  | 0 | delete $self->{_IMAP4REV1}; | 
| 2007 | 0 |  |  |  |  | 0 | $self->State(Unconnected); | 
| 2008 | 0 | 0 |  |  |  | 0 | if ( my $sock = delete $self->{Socket} ) { | 
| 2009 | 0 |  |  |  |  | 0 | local ($@); | 
| 2010 | 0 |  |  |  |  | 0 | eval { $sock->close }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2011 |  |  |  |  |  |  | } | 
| 2012 | 0 |  |  |  |  | 0 | return $self; | 
| 2013 |  |  |  |  |  |  | } | 
| 2014 |  |  |  |  |  |  |  | 
| 2015 |  |  |  |  |  |  | # LIST/XLIST/LSUB Response | 
| 2016 |  |  |  |  |  |  | #   Contents: name attributes, hierarchy delimiter, name | 
| 2017 |  |  |  |  |  |  | #   Example: * LIST (\Noselect) "/" ~/Mail/foo | 
| 2018 |  |  |  |  |  |  | # NOTE: liberal matching as folder name data may be Escape()d | 
| 2019 |  |  |  |  |  |  | sub _list_or_lsub_response_parse { | 
| 2020 | 0 |  |  | 0 |  | 0 | my ( $self, $resp ) = @_; | 
| 2021 |  |  |  |  |  |  |  | 
| 2022 | 0 | 0 |  |  |  | 0 | return undef unless defined $resp; | 
| 2023 | 0 |  |  |  |  | 0 | my %info; | 
| 2024 |  |  |  |  |  |  |  | 
| 2025 | 0 |  |  |  |  | 0 | $resp =~ s/\015?\012$//; | 
| 2026 | 0 | 0 |  |  |  | 0 | if ( | 
| 2027 |  |  |  |  |  |  | $resp =~ / ^\* \s+ (?:LIST|XLIST|LSUB) \s+ # * LIST|XLIST|LSUB | 
| 2028 |  |  |  |  |  |  | \( ([^\)]*) \)                \s+ # (attrs) | 
| 2029 |  |  |  |  |  |  | (?:   \" ([^"]*)  \" | NIL  )       \s  # "delimiter" or NIL | 
| 2030 |  |  |  |  |  |  | (?:\s*\" (.*)     \" | (.*) )           # "name" or name | 
| 2031 |  |  |  |  |  |  | /ix | 
| 2032 |  |  |  |  |  |  | ) | 
| 2033 |  |  |  |  |  |  | { | 
| 2034 | 0 | 0 |  |  |  | 0 | @info{qw(attrs delim name)} = | 
| 2035 |  |  |  |  |  |  | ( [ split( / /, $1 ) ], $2, defined($3) ? $self->Unescape($3) : $4 ); | 
| 2036 |  |  |  |  |  |  | } | 
| 2037 | 0 | 0 |  |  |  | 0 | return wantarray ? %info : \%info; | 
| 2038 |  |  |  |  |  |  | } | 
| 2039 |  |  |  |  |  |  |  | 
| 2040 |  |  |  |  |  |  | sub exists { | 
| 2041 | 0 |  |  | 0 | 1 | 0 | my ( $self, $folder ) = @_; | 
| 2042 | 0 | 0 |  |  |  | 0 | $self->status($folder) ? $self : undef; | 
| 2043 |  |  |  |  |  |  | } | 
| 2044 |  |  |  |  |  |  |  | 
| 2045 |  |  |  |  |  |  | # Updated to handle embedded literal strings | 
| 2046 |  |  |  |  |  |  | sub get_bodystructure { | 
| 2047 | 0 |  |  | 0 | 1 | 0 | my ( $self, $msg ) = @_; | 
| 2048 |  |  |  |  |  |  |  | 
| 2049 | 0 | 0 |  |  |  | 0 | my $class = $self->_load_module("BodyStructure") or return undef; | 
| 2050 |  |  |  |  |  |  |  | 
| 2051 | 0 | 0 |  |  |  | 0 | my $out = $self->fetch( $msg, "BODYSTRUCTURE" ) or return undef; | 
| 2052 |  |  |  |  |  |  |  | 
| 2053 | 0 |  |  |  |  | 0 | my $bs = ""; | 
| 2054 | 0 |  |  | 0 |  | 0 | my $output = first { /BODYSTRUCTURE\s+\(/i } @$out; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2055 |  |  |  |  |  |  |  | 
| 2056 | 0 | 0 |  |  |  | 0 | unless ( $output =~ /$CRLF$/o ) { | 
| 2057 | 0 |  |  |  |  | 0 | $output = ''; | 
| 2058 | 0 |  |  |  |  | 0 | $self->_debug("get_bodystructure: reassembling original response"); | 
| 2059 | 0 |  |  |  |  | 0 | my $started = 0; | 
| 2060 | 0 |  |  |  |  | 0 | foreach my $o ( $self->_transaction ) { | 
| 2061 | 0 | 0 |  |  |  | 0 | next unless $self->_is_output_or_literal($o); | 
| 2062 | 0 | 0 |  |  |  | 0 | $started++ if $o->[DATA] =~ /BODYSTRUCTURE \(/i; | 
| 2063 | 0 | 0 |  |  |  | 0 | $started or next; | 
| 2064 |  |  |  |  |  |  |  | 
| 2065 | 0 | 0 | 0 |  |  | 0 | if ( length($output) && $self->_is_literal($o) ) { | 
| 2066 | 0 |  |  |  |  | 0 | my $data = $o->[DATA]; | 
| 2067 | 0 |  |  |  |  | 0 | $data =~ s/"/\\"/g; | 
| 2068 | 0 |  |  |  |  | 0 | $data =~ s/\(/\\\(/g; | 
| 2069 | 0 |  |  |  |  | 0 | $data =~ s/\)/\\\)/g; | 
| 2070 | 0 |  |  |  |  | 0 | $output .= qq("$data"); | 
| 2071 |  |  |  |  |  |  | } | 
| 2072 |  |  |  |  |  |  | else { | 
| 2073 | 0 |  |  |  |  | 0 | $output .= $o->[DATA]; | 
| 2074 |  |  |  |  |  |  | } | 
| 2075 |  |  |  |  |  |  | } | 
| 2076 | 0 |  |  |  |  | 0 | $self->_debug("get_bodystructure: reassembled output=$output"); | 
| 2077 |  |  |  |  |  |  | } | 
| 2078 |  |  |  |  |  |  |  | 
| 2079 |  |  |  |  |  |  | { | 
| 2080 | 0 |  |  |  |  | 0 | local ($@); | 
|  | 0 |  |  |  |  | 0 |  | 
| 2081 | 0 |  |  |  |  | 0 | $bs = eval { $class->new($output) }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2082 |  |  |  |  |  |  | } | 
| 2083 |  |  |  |  |  |  |  | 
| 2084 |  |  |  |  |  |  | $self->_debug( | 
| 2085 | 0 |  | 0 |  |  | 0 | "get_bodystructure: msg $msg returns: " . ( $bs || "UNDEF" ) ); | 
| 2086 | 0 |  |  |  |  | 0 | $bs; | 
| 2087 |  |  |  |  |  |  | } | 
| 2088 |  |  |  |  |  |  |  | 
| 2089 |  |  |  |  |  |  | # Updated to handle embedded literal strings | 
| 2090 |  |  |  |  |  |  | sub get_envelope { | 
| 2091 | 0 |  |  | 0 | 1 | 0 | my ( $self, $msg ) = @_; | 
| 2092 |  |  |  |  |  |  |  | 
| 2093 |  |  |  |  |  |  | # Envelope class is defined within BodyStructure | 
| 2094 | 0 | 0 |  |  |  | 0 | my $class = $self->_load_module("BodyStructure") or return undef; | 
| 2095 | 0 |  |  |  |  | 0 | $class .= "::Envelope"; | 
| 2096 |  |  |  |  |  |  |  | 
| 2097 | 0 | 0 |  |  |  | 0 | my $out = $self->fetch( $msg, 'ENVELOPE' ) or return undef; | 
| 2098 |  |  |  |  |  |  |  | 
| 2099 | 0 |  |  |  |  | 0 | my $bs = ""; | 
| 2100 | 0 |  |  | 0 |  | 0 | my $output = first { /ENVELOPE \(/i } @$out; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2101 |  |  |  |  |  |  |  | 
| 2102 | 0 | 0 |  |  |  | 0 | unless ( $output =~ /$CRLF$/o ) { | 
| 2103 | 0 |  |  |  |  | 0 | $output = ''; | 
| 2104 | 0 |  |  |  |  | 0 | $self->_debug("get_envelope: reassembling original response"); | 
| 2105 | 0 |  |  |  |  | 0 | my $started = 0; | 
| 2106 | 0 |  |  |  |  | 0 | foreach my $o ( $self->_transaction ) { | 
| 2107 | 0 | 0 |  |  |  | 0 | next unless $self->_is_output_or_literal($o); | 
| 2108 | 0 | 0 |  |  |  | 0 | $started++ if $o->[DATA] =~ /ENVELOPE \(/i; | 
| 2109 | 0 | 0 |  |  |  | 0 | $started or next; | 
| 2110 |  |  |  |  |  |  |  | 
| 2111 | 0 | 0 | 0 |  |  | 0 | if ( length($output) && $self->_is_literal($o) ) { | 
| 2112 | 0 |  |  |  |  | 0 | my $data = $o->[DATA]; | 
| 2113 | 0 |  |  |  |  | 0 | $data =~ s/"/\\"/g; | 
| 2114 | 0 |  |  |  |  | 0 | $data =~ s/\(/\\\(/g; | 
| 2115 | 0 |  |  |  |  | 0 | $data =~ s/\)/\\\)/g; | 
| 2116 | 0 |  |  |  |  | 0 | $output .= qq("$data"); | 
| 2117 |  |  |  |  |  |  | } | 
| 2118 |  |  |  |  |  |  | else { | 
| 2119 | 0 |  |  |  |  | 0 | $output .= $o->[DATA]; | 
| 2120 |  |  |  |  |  |  | } | 
| 2121 |  |  |  |  |  |  | } | 
| 2122 | 0 |  |  |  |  | 0 | $self->_debug("get_envelope: reassembled output=$output"); | 
| 2123 |  |  |  |  |  |  | } | 
| 2124 |  |  |  |  |  |  |  | 
| 2125 |  |  |  |  |  |  | { | 
| 2126 | 0 |  |  |  |  | 0 | local ($@); | 
|  | 0 |  |  |  |  | 0 |  | 
| 2127 | 0 |  |  |  |  | 0 | $bs = eval { $class->new($output) }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2128 |  |  |  |  |  |  | } | 
| 2129 |  |  |  |  |  |  |  | 
| 2130 | 0 |  | 0 |  |  | 0 | $self->_debug( "get_envelope: msg $msg returns: " . ( $bs || "UNDEF" ) ); | 
| 2131 | 0 |  |  |  |  | 0 | $bs; | 
| 2132 |  |  |  |  |  |  | } | 
| 2133 |  |  |  |  |  |  |  | 
| 2134 |  |  |  |  |  |  | # fetch( [{option},] [$seq_set|ALL], @msg_data_items ) | 
| 2135 |  |  |  |  |  |  | # options: | 
| 2136 |  |  |  |  |  |  | #   escaped => 0|1  # return Results or Escaped_results | 
| 2137 |  |  |  |  |  |  | sub fetch { | 
| 2138 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 2139 | 0 | 0 |  |  |  | 0 | my $opt  = ref( $_[0] ) eq "HASH" ? shift : {}; | 
| 2140 | 0 |  | 0 |  |  | 0 | my $what = shift || "ALL"; | 
| 2141 |  |  |  |  |  |  |  | 
| 2142 | 0 |  |  |  |  | 0 | my $take = $what; | 
| 2143 | 0 | 0 | 0 |  |  | 0 | if ( $what eq 'ALL' ) { | 
|  |  | 0 |  |  |  |  |  | 
| 2144 | 0 | 0 |  |  |  | 0 | my $msgs = $self->messages or return undef; | 
| 2145 | 0 |  |  |  |  | 0 | $take = $self->Range($msgs); | 
| 2146 |  |  |  |  |  |  | } | 
| 2147 |  |  |  |  |  |  | elsif ( ref $what || $what =~ /^[,:\d]+\w*$/ ) { | 
| 2148 | 0 |  |  |  |  | 0 | $take = $self->Range($what); | 
| 2149 |  |  |  |  |  |  | } | 
| 2150 |  |  |  |  |  |  |  | 
| 2151 | 0 |  |  |  |  | 0 | my ( @data, $cmd ); | 
| 2152 | 0 |  |  |  |  | 0 | my ( $seq_set, @fetch_att ) = $self->_split_sequence( $take, "FETCH", @_ ); | 
| 2153 |  |  |  |  |  |  |  | 
| 2154 | 0 |  |  |  |  | 0 | for ( my $x = 0 ; $x <= $#$seq_set ; $x++ ) { | 
| 2155 | 0 |  |  |  |  | 0 | my $seq = $seq_set->[$x]; | 
| 2156 | 0 | 0 |  |  |  | 0 | $self->_imap_uid_command( FETCH => $seq, @fetch_att, @_ ) | 
| 2157 |  |  |  |  |  |  | or return undef; | 
| 2158 | 0 | 0 |  |  |  | 0 | my $res = $opt->{escaped} ? $self->Escaped_results : $self->Results; | 
| 2159 |  |  |  |  |  |  |  | 
| 2160 |  |  |  |  |  |  | # only keep last command and last response (* OK ...) | 
| 2161 | 0 |  |  |  |  | 0 | $cmd = shift(@$res); | 
| 2162 | 0 | 0 |  |  |  | 0 | pop(@$res) if ( $x != $#{$seq_set} ); | 
|  | 0 |  |  |  |  | 0 |  | 
| 2163 | 0 |  |  |  |  | 0 | push( @data, @$res ); | 
| 2164 |  |  |  |  |  |  | } | 
| 2165 |  |  |  |  |  |  |  | 
| 2166 | 0 | 0 | 0 |  |  | 0 | if ( $cmd and !wantarray ) { | 
| 2167 | 0 |  |  |  |  | 0 | $cmd =~ s/^(\d+\s+.*?FETCH\s+)\S+(\s*)/$1$take$2/; | 
| 2168 | 0 |  |  |  |  | 0 | unshift( @data, $cmd ); | 
| 2169 |  |  |  |  |  |  | } | 
| 2170 |  |  |  |  |  |  |  | 
| 2171 |  |  |  |  |  |  | #wantarray ? $self->History : $self->Results; | 
| 2172 | 0 | 0 |  |  |  | 0 | return wantarray ? @data : \@data; | 
| 2173 |  |  |  |  |  |  | } | 
| 2174 |  |  |  |  |  |  |  | 
| 2175 |  |  |  |  |  |  | # Some servers have a maximum command length.  If Maxcommandlength is | 
| 2176 |  |  |  |  |  |  | # set, split a sequence to fit within the length restriction. | 
| 2177 |  |  |  |  |  |  | sub _split_sequence { | 
| 2178 | 0 |  |  | 0 |  | 0 | my ( $self, $take, @args ) = @_; | 
| 2179 |  |  |  |  |  |  |  | 
| 2180 |  |  |  |  |  |  | # split take => sequence-set and (optional) fetch-att | 
| 2181 | 0 |  |  |  |  | 0 | my ( $seq, @att ) = split( / /, $take, 2 ); | 
| 2182 |  |  |  |  |  |  |  | 
| 2183 |  |  |  |  |  |  | # use the entire sequence unless Maxcommandlength is set | 
| 2184 | 0 |  |  |  |  | 0 | my @seqs; | 
| 2185 | 0 |  |  |  |  | 0 | my $maxl = $self->Maxcommandlength; | 
| 2186 | 0 | 0 |  |  |  | 0 | if ($maxl) { | 
| 2187 |  |  |  |  |  |  |  | 
| 2188 |  |  |  |  |  |  | # estimate command length, the sum of the lengths of: | 
| 2189 |  |  |  |  |  |  | #   tag, command, fetch-att + $CRLF | 
| 2190 | 0 | 0 |  |  |  | 0 | push @args, $self->Transaction, $self->Uid ? "UID" : (), "\015\012"; | 
| 2191 |  |  |  |  |  |  |  | 
| 2192 |  |  |  |  |  |  | # do not split on anything smaller than 64 chars | 
| 2193 | 0 |  |  |  |  | 0 | my $clen = length join( " ", @att, @args ); | 
| 2194 | 0 |  |  |  |  | 0 | my $diff = $maxl - $clen; | 
| 2195 | 0 | 0 |  |  |  | 0 | my $most = $diff > 64 ? $diff : 64; | 
| 2196 |  |  |  |  |  |  |  | 
| 2197 | 0 | 0 |  |  |  | 0 | @seqs = ( $seq =~ m/(.{1,$most})(?:,|$)/g ) if defined $seq; | 
| 2198 | 0 | 0 |  |  |  | 0 | $self->_debug( "split_sequence: length($maxl-$clen) parts: ", | 
| 2199 |  |  |  |  |  |  | $#seqs + 1 ) | 
| 2200 |  |  |  |  |  |  | if ( $#seqs != 0 ); | 
| 2201 |  |  |  |  |  |  | } | 
| 2202 |  |  |  |  |  |  | else { | 
| 2203 | 0 | 0 |  |  |  | 0 | push( @seqs, $seq ) if defined $seq; | 
| 2204 |  |  |  |  |  |  | } | 
| 2205 | 0 |  |  |  |  | 0 | return \@seqs, @att; | 
| 2206 |  |  |  |  |  |  | } | 
| 2207 |  |  |  |  |  |  |  | 
| 2208 |  |  |  |  |  |  | # fetch_hash( [$seq_set|ALL], @msg_data_items, [\%msg_by_ids] ) | 
| 2209 |  |  |  |  |  |  | # - TODO: make more efficient use of memory on large fetch results | 
| 2210 |  |  |  |  |  |  | sub fetch_hash { | 
| 2211 | 27 |  |  | 27 | 1 | 14114 | my $self  = shift; | 
| 2212 | 27 | 50 |  |  |  | 57 | my $uids  = ref $_[-1] ? pop @_ : {}; | 
| 2213 | 27 |  |  |  |  | 49 | my @words = @_; | 
| 2214 |  |  |  |  |  |  |  | 
| 2215 |  |  |  |  |  |  | # take an optional leading list of messages argument or default to | 
| 2216 |  |  |  |  |  |  | # ALL let fetch turn that list of messages into a msgref as needed | 
| 2217 |  |  |  |  |  |  | # fetch has similar logic for dealing with message list | 
| 2218 | 27 |  |  |  |  | 35 | my $msgs = 'ALL'; | 
| 2219 | 27 | 50 |  |  |  | 52 | if ( defined $words[0] ) { | 
| 2220 | 27 | 50 |  |  |  | 72 | if ( ref $words[0] ) { | 
| 2221 | 27 |  |  |  |  | 42 | $msgs = shift @words; | 
| 2222 |  |  |  |  |  |  | } | 
| 2223 |  |  |  |  |  |  | else { | 
| 2224 | 0 | 0 |  |  |  | 0 | if ( $words[0] eq 'ALL' ) { | 
|  |  | 0 |  |  |  |  |  | 
| 2225 | 0 |  |  |  |  | 0 | $msgs = shift @words; | 
| 2226 |  |  |  |  |  |  | } | 
| 2227 |  |  |  |  |  |  | elsif ( $words[0] =~ s/^([*,:\d]+)\s*// ) { | 
| 2228 | 0 |  |  |  |  | 0 | $msgs = $1; | 
| 2229 | 0 | 0 |  |  |  | 0 | shift @words if $words[0] eq ""; | 
| 2230 |  |  |  |  |  |  | } | 
| 2231 |  |  |  |  |  |  | } | 
| 2232 |  |  |  |  |  |  | } | 
| 2233 |  |  |  |  |  |  |  | 
| 2234 |  |  |  |  |  |  | # message list (if any) is now removed from @words | 
| 2235 | 27 | 100 | 100 |  |  | 136 | my $what = ( @words > 1 or $words[0] =~ /\s/ ) ? "(@words)" : "@words"; | 
| 2236 |  |  |  |  |  |  |  | 
| 2237 |  |  |  |  |  |  | # RFC 3501: | 
| 2238 |  |  |  |  |  |  | #   fetch = "FETCH" SP sequence-set SP ("ALL" / "FULL" / "FAST" / | 
| 2239 |  |  |  |  |  |  | #           fetch-att / "(" fetch-att *(SP fetch-att) ")") | 
| 2240 | 27 | 50 |  |  |  | 68 | my $output = $self->fetch( $msgs, $what ) | 
| 2241 |  |  |  |  |  |  | or return undef; | 
| 2242 |  |  |  |  |  |  |  | 
| 2243 | 27 |  |  |  |  | 176 | my $asked_for_uid = $what =~ /[\s(]UID[)\s]/i; | 
| 2244 |  |  |  |  |  |  |  | 
| 2245 | 27 |  |  |  |  | 53 | while ( my $l = shift @$output ) { | 
| 2246 | 27 | 50 |  |  |  | 121 | next if $l !~ m/^\*\s(\d+)\sFETCH\s\(/g; | 
| 2247 | 27 |  |  |  |  | 72 | my ( $mid, $entry ) = ( $1, {} ); | 
| 2248 | 27 |  |  |  |  | 35 | my ( $key, $value ); | 
| 2249 |  |  |  |  |  |  | ATTR: | 
| 2250 | 27 |  | 100 |  |  | 109 | while ( $l and $l !~ m/\G\s*\)\s*$/gc ) { | 
| 2251 | 42 | 50 |  |  |  | 129 | if ( $l =~ m/\G\s*([^\s\[]+(?:\[[^\]]*\])?(?:<[^>]*>)?)\s*/gc ) { | 
|  |  | 0 |  |  |  |  |  | 
| 2252 | 42 |  |  |  |  | 78 | $key = uc($1); | 
| 2253 |  |  |  |  |  |  |  | 
| 2254 |  |  |  |  |  |  | # strip quotes around header names - seen w/outlook.com | 
| 2255 | 42 | 100 |  |  |  | 78 | if ( $key =~ /^BODY\[HEADER\.FIELDS \("[^"]+".*?\)\]$/ ) { | 
| 2256 | 1 |  |  |  |  | 6 | $key =~ s/"//g; | 
| 2257 |  |  |  |  |  |  | } | 
| 2258 |  |  |  |  |  |  | } | 
| 2259 |  |  |  |  |  |  | elsif ( !defined $key ) { | 
| 2260 |  |  |  |  |  |  |  | 
| 2261 |  |  |  |  |  |  | # some kind of malformed response | 
| 2262 | 0 |  |  |  |  | 0 | $self->LastError("Invalid item name in FETCH response: $l"); | 
| 2263 | 0 |  |  |  |  | 0 | return undef; | 
| 2264 |  |  |  |  |  |  | } | 
| 2265 | 42 | 100 |  |  |  | 134 | if ( $l =~ m/\G\s*$/gc ) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 2266 | 8 |  |  |  |  | 11 | $value         = shift @$output; | 
| 2267 | 8 |  |  |  |  | 15 | $entry->{$key} = $value; | 
| 2268 | 8 |  |  |  |  | 14 | $l             = shift @$output; | 
| 2269 | 8 |  |  |  |  | 29 | next ATTR; | 
| 2270 |  |  |  |  |  |  | } | 
| 2271 |  |  |  |  |  |  | elsif ( | 
| 2272 |  |  |  |  |  |  | $l =~ m/\G(?:"((?>(?:(?>[^"\\]+)|\\.)*))"|([^()\s]+))\s*/gc ) | 
| 2273 |  |  |  |  |  |  | { | 
| 2274 | 18 | 100 |  |  |  | 45 | $value = defined $1 ? $1 : $2; | 
| 2275 | 18 |  |  |  |  | 32 | $entry->{$key} = $value; | 
| 2276 | 18 |  |  |  |  | 68 | next ATTR; | 
| 2277 |  |  |  |  |  |  | } | 
| 2278 |  |  |  |  |  |  | elsif ( $l =~ m/\G\(/gc ) { | 
| 2279 | 16 |  |  |  |  | 21 | my $depth = 1; | 
| 2280 | 16 |  |  |  |  | 19 | $value = ""; | 
| 2281 | 16 |  |  |  |  | 40 | while ( $l =~ | 
| 2282 |  |  |  |  |  |  | m/\G("((?>(?:(?>[^"\\]+)|\\.)*))"\s*|[()]|[^()"]+)/gc ) | 
| 2283 |  |  |  |  |  |  | { | 
| 2284 | 135 |  |  |  |  | 191 | my $stuff = $1; | 
| 2285 | 135 | 100 |  |  |  | 195 | if ( $stuff eq "(" ) { | 
|  |  | 100 |  |  |  |  |  | 
| 2286 | 23 |  |  |  |  | 24 | $depth++; | 
| 2287 | 23 |  |  |  |  | 23 | $value .= "("; | 
| 2288 |  |  |  |  |  |  | } | 
| 2289 |  |  |  |  |  |  | elsif ( $stuff eq ")" ) { | 
| 2290 | 39 |  |  |  |  | 41 | $depth--; | 
| 2291 | 39 | 100 |  |  |  | 58 | if ( $depth == 0 ) { | 
| 2292 | 16 |  |  |  |  | 31 | $entry->{$key} = $value; | 
| 2293 | 16 |  |  |  |  | 69 | next ATTR; | 
| 2294 |  |  |  |  |  |  | } | 
| 2295 | 23 |  |  |  |  | 24 | $value .= ")"; | 
| 2296 |  |  |  |  |  |  | } | 
| 2297 |  |  |  |  |  |  | else { | 
| 2298 | 73 |  |  |  |  | 91 | $value .= $stuff; | 
| 2299 |  |  |  |  |  |  | } | 
| 2300 |  |  |  |  |  |  |  | 
| 2301 |  |  |  |  |  |  | # consume literal data if any | 
| 2302 | 119 | 50 | 100 |  |  | 339 | if ( $l =~ m/\G\s*$/gc and scalar(@$output) ) { | 
| 2303 | 1 |  |  |  |  | 10 | my $elit = $self->Escape( shift @$output ); | 
| 2304 | 1 |  |  |  |  | 2 | $l = shift @$output; | 
| 2305 | 1 | 50 |  |  |  | 7 | $value .= ( length($value) ? " " : "" ) . qq{"$elit"}; | 
| 2306 |  |  |  |  |  |  | } | 
| 2307 |  |  |  |  |  |  | } | 
| 2308 | 0 |  |  |  |  | 0 | $l =~ m/\G\s*/gc; | 
| 2309 |  |  |  |  |  |  | } | 
| 2310 |  |  |  |  |  |  | else { | 
| 2311 | 0 |  |  |  |  | 0 | $self->LastError("Invalid item value in FETCH response: $l"); | 
| 2312 | 0 |  |  |  |  | 0 | return undef; | 
| 2313 |  |  |  |  |  |  | } | 
| 2314 |  |  |  |  |  |  | } | 
| 2315 |  |  |  |  |  |  |  | 
| 2316 |  |  |  |  |  |  | # NOTE: old code tried to remove any "unrequested" data in $entry | 
| 2317 |  |  |  |  |  |  | # - UID is sometimes not explicitly requested, are there others? | 
| 2318 |  |  |  |  |  |  | # - rt#115726: Uid and $entry->{UID} not set, ignore unsolicited data | 
| 2319 | 27 | 100 |  |  |  | 58 | if ( $self->Uid ) { | 
| 2320 | 4 | 50 |  |  |  | 8 | if ( $entry->{UID} ) { | 
| 2321 | 4 |  |  |  |  | 7 | $uids->{ $entry->{UID} } = $entry; | 
| 2322 | 4 | 100 |  |  |  | 18 | delete $entry->{UID} unless $asked_for_uid; | 
| 2323 |  |  |  |  |  |  | } | 
| 2324 |  |  |  |  |  |  | else { | 
| 2325 | 0 |  |  |  |  | 0 | $self->_debug("ignoring unsolicited response: $l"); | 
| 2326 |  |  |  |  |  |  | } | 
| 2327 |  |  |  |  |  |  | } | 
| 2328 |  |  |  |  |  |  | else { | 
| 2329 | 23 |  |  |  |  | 73 | $uids->{$mid} = $entry; | 
| 2330 |  |  |  |  |  |  | } | 
| 2331 |  |  |  |  |  |  | } | 
| 2332 |  |  |  |  |  |  |  | 
| 2333 | 27 | 50 |  |  |  | 68 | return wantarray ? %$uids : $uids; | 
| 2334 |  |  |  |  |  |  | } | 
| 2335 |  |  |  |  |  |  |  | 
| 2336 |  |  |  |  |  |  | sub store { | 
| 2337 | 0 |  |  | 0 | 1 | 0 | my ( $self, @a ) = @_; | 
| 2338 | 0 | 0 |  |  |  | 0 | $self->_imap_uid_command( STORE => @a ) | 
| 2339 |  |  |  |  |  |  | or return undef; | 
| 2340 | 0 | 0 |  |  |  | 0 | return wantarray ? $self->History : $self->Results; | 
| 2341 |  |  |  |  |  |  | } | 
| 2342 |  |  |  |  |  |  |  | 
| 2343 |  |  |  |  |  |  | sub _imap_folder_command($$@) { | 
| 2344 | 0 |  |  | 0 |  | 0 | my ( $self, $command ) = ( shift, shift ); | 
| 2345 | 0 |  |  |  |  | 0 | my $folder = $self->Quote(shift); | 
| 2346 |  |  |  |  |  |  |  | 
| 2347 | 0 | 0 |  |  |  | 0 | $self->_imap_command( join ' ', $command, $folder, @_ ) | 
| 2348 |  |  |  |  |  |  | or return undef; | 
| 2349 |  |  |  |  |  |  |  | 
| 2350 | 0 | 0 |  |  |  | 0 | return wantarray ? $self->History : $self->Results; | 
| 2351 |  |  |  |  |  |  | } | 
| 2352 |  |  |  |  |  |  |  | 
| 2353 | 0 |  |  | 0 | 1 | 0 | sub subscribe($)   { shift->_imap_folder_command( SUBSCRIBE   => @_ ) } | 
| 2354 | 0 |  |  | 0 | 0 | 0 | sub unsubscribe($) { shift->_imap_folder_command( UNSUBSCRIBE => @_ ) } | 
| 2355 | 0 |  |  | 0 | 1 | 0 | sub create($)      { shift->_imap_folder_command( CREATE      => @_ ) } | 
| 2356 |  |  |  |  |  |  |  | 
| 2357 |  |  |  |  |  |  | sub delete($) { | 
| 2358 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 2359 | 0 | 0 |  |  |  | 0 | $self->_imap_folder_command( DELETE => @_ ) or return undef; | 
| 2360 | 0 |  |  |  |  | 0 | $self->Folder(undef); | 
| 2361 | 0 | 0 |  |  |  | 0 | return wantarray ? $self->History : $self->Results; | 
| 2362 |  |  |  |  |  |  | } | 
| 2363 |  |  |  |  |  |  |  | 
| 2364 |  |  |  |  |  |  | # rfc2086 | 
| 2365 | 0 |  |  | 0 | 0 | 0 | sub myrights($) { $_[0]->_imap_folder_command( MYRIGHTS => $_[1] ) } | 
| 2366 |  |  |  |  |  |  |  | 
| 2367 |  |  |  |  |  |  | sub close { | 
| 2368 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 2369 | 0 | 0 |  |  |  | 0 | $self->_imap_command('CLOSE') | 
| 2370 |  |  |  |  |  |  | or return undef; | 
| 2371 | 0 | 0 |  |  |  | 0 | return wantarray ? $self->History : $self->Results; | 
| 2372 |  |  |  |  |  |  | } | 
| 2373 |  |  |  |  |  |  |  | 
| 2374 |  |  |  |  |  |  | sub expunge { | 
| 2375 | 0 |  |  | 0 | 1 | 0 | my ( $self, $folder ) = @_; | 
| 2376 |  |  |  |  |  |  |  | 
| 2377 | 0 | 0 | 0 |  |  | 0 | return undef unless ( defined $folder or defined $self->Folder ); | 
| 2378 |  |  |  |  |  |  |  | 
| 2379 | 0 | 0 |  |  |  | 0 | my $old = defined $self->Folder ? $self->Folder : ''; | 
| 2380 |  |  |  |  |  |  |  | 
| 2381 | 0 | 0 | 0 |  |  | 0 | if ( !defined($folder) || $folder eq $old ) { | 
| 2382 | 0 | 0 |  |  |  | 0 | $self->_imap_command('EXPUNGE') | 
| 2383 |  |  |  |  |  |  | or return undef; | 
| 2384 |  |  |  |  |  |  | } | 
| 2385 |  |  |  |  |  |  | else { | 
| 2386 | 0 | 0 |  |  |  | 0 | $self->select($folder) or return undef; | 
| 2387 | 0 |  |  |  |  | 0 | my $succ = $self->_imap_command('EXPUNGE'); | 
| 2388 |  |  |  |  |  |  |  | 
| 2389 |  |  |  |  |  |  | # if $old eq '' IMAP4 select should close $folder without EXPUNGE | 
| 2390 | 0 | 0 | 0 |  |  | 0 | return undef unless ( $self->select($old) and $succ ); | 
| 2391 |  |  |  |  |  |  | } | 
| 2392 |  |  |  |  |  |  |  | 
| 2393 | 0 | 0 |  |  |  | 0 | return wantarray ? $self->History : $self->Results; | 
| 2394 |  |  |  |  |  |  | } | 
| 2395 |  |  |  |  |  |  |  | 
| 2396 |  |  |  |  |  |  | sub uidexpunge { | 
| 2397 | 0 |  |  | 0 | 1 | 0 | my ( $self, $msgspec ) = ( shift, shift ); | 
| 2398 |  |  |  |  |  |  |  | 
| 2399 | 0 | 0 |  |  |  | 0 | return undef unless $self->has_capability("UIDPLUS"); | 
| 2400 | 0 | 0 |  |  |  | 0 | unless ( $self->Uid ) { | 
| 2401 | 0 |  |  |  |  | 0 | $self->LastError("Uid must be enabled for uidexpunge"); | 
| 2402 | 0 |  |  |  |  | 0 | return undef; | 
| 2403 |  |  |  |  |  |  | } | 
| 2404 |  |  |  |  |  |  |  | 
| 2405 | 0 | 0 |  |  |  | 0 | my $msg = | 
| 2406 |  |  |  |  |  |  | UNIVERSAL::isa( $msgspec, 'Mail::IMAPClient::MessageSet' ) | 
| 2407 |  |  |  |  |  |  | ? $msgspec | 
| 2408 |  |  |  |  |  |  | : $self->Range($msgspec); | 
| 2409 |  |  |  |  |  |  |  | 
| 2410 | 0 | 0 |  |  |  | 0 | $msg->cat(@_) if @_; | 
| 2411 |  |  |  |  |  |  |  | 
| 2412 | 0 |  |  |  |  | 0 | my ( @data, $cmd ); | 
| 2413 | 0 |  |  |  |  | 0 | my ($seq_set) = $self->_split_sequence( $msg, "UID EXPUNGE" ); | 
| 2414 |  |  |  |  |  |  |  | 
| 2415 | 0 |  |  |  |  | 0 | for ( my $x = 0 ; $x <= $#$seq_set ; $x++ ) { | 
| 2416 | 0 |  |  |  |  | 0 | my $seq = $seq_set->[$x]; | 
| 2417 | 0 | 0 |  |  |  | 0 | $self->_imap_uid_command( "EXPUNGE" => $seq ) | 
| 2418 |  |  |  |  |  |  | or return undef; | 
| 2419 | 0 |  |  |  |  | 0 | my $res = $self->Results; | 
| 2420 |  |  |  |  |  |  |  | 
| 2421 |  |  |  |  |  |  | # only keep last command and last response (* OK ...) | 
| 2422 | 0 |  |  |  |  | 0 | $cmd = shift(@$res); | 
| 2423 | 0 | 0 |  |  |  | 0 | pop(@$res) if ( $x != $#{$seq_set} ); | 
|  | 0 |  |  |  |  | 0 |  | 
| 2424 | 0 |  |  |  |  | 0 | push( @data, @$res ); | 
| 2425 |  |  |  |  |  |  | } | 
| 2426 |  |  |  |  |  |  |  | 
| 2427 | 0 | 0 | 0 |  |  | 0 | if ( $cmd and !wantarray ) { | 
| 2428 | 0 |  |  |  |  | 0 | $cmd =~ s/^(\d+\s+.*?EXPUNGE\s+)\S+(\s*)/$1$msg$2/; | 
| 2429 | 0 |  |  |  |  | 0 | unshift( @data, $cmd ); | 
| 2430 |  |  |  |  |  |  | } | 
| 2431 |  |  |  |  |  |  |  | 
| 2432 |  |  |  |  |  |  | #wantarray ? $self->History : $self->Results; | 
| 2433 | 0 | 0 |  |  |  | 0 | return wantarray ? @data : \@data; | 
| 2434 |  |  |  |  |  |  | } | 
| 2435 |  |  |  |  |  |  |  | 
| 2436 |  |  |  |  |  |  | sub rename { | 
| 2437 | 0 |  |  | 0 | 1 | 0 | my ( $self, $from, $to ) = @_; | 
| 2438 |  |  |  |  |  |  |  | 
| 2439 | 0 |  |  |  |  | 0 | $from = $self->Quote($from); | 
| 2440 | 0 |  |  |  |  | 0 | $to   = $self->Quote($to); | 
| 2441 |  |  |  |  |  |  |  | 
| 2442 | 0 | 0 |  |  |  | 0 | $self->_imap_command(qq(RENAME $from $to)) ? $self : undef; | 
| 2443 |  |  |  |  |  |  | } | 
| 2444 |  |  |  |  |  |  |  | 
| 2445 |  |  |  |  |  |  | sub status { | 
| 2446 | 0 |  |  | 0 | 1 | 0 | my ( $self, $folder ) = ( shift, shift ); | 
| 2447 | 0 | 0 |  |  |  | 0 | defined $folder or return undef; | 
| 2448 |  |  |  |  |  |  |  | 
| 2449 | 0 | 0 |  |  |  | 0 | my $which = @_ ? join( " ", @_ ) : 'MESSAGES'; | 
| 2450 |  |  |  |  |  |  |  | 
| 2451 | 0 |  |  |  |  | 0 | my $box = $self->Quote($folder); | 
| 2452 | 0 | 0 |  |  |  | 0 | $self->_imap_command("STATUS $box ($which)") | 
| 2453 |  |  |  |  |  |  | or return undef; | 
| 2454 |  |  |  |  |  |  |  | 
| 2455 | 0 | 0 |  |  |  | 0 | return wantarray ? $self->History : $self->Results; | 
| 2456 |  |  |  |  |  |  | } | 
| 2457 |  |  |  |  |  |  |  | 
| 2458 |  |  |  |  |  |  | sub flags { | 
| 2459 | 0 |  |  | 0 | 1 | 0 | my ( $self, $msgspec ) = ( shift, shift ); | 
| 2460 | 0 | 0 |  |  |  | 0 | my $msg = | 
| 2461 |  |  |  |  |  |  | UNIVERSAL::isa( $msgspec, 'Mail::IMAPClient::MessageSet' ) | 
| 2462 |  |  |  |  |  |  | ? $msgspec | 
| 2463 |  |  |  |  |  |  | : $self->Range($msgspec); | 
| 2464 |  |  |  |  |  |  |  | 
| 2465 | 0 | 0 |  |  |  | 0 | $msg->cat(@_) if @_; | 
| 2466 |  |  |  |  |  |  |  | 
| 2467 |  |  |  |  |  |  | # Send command | 
| 2468 | 0 | 0 |  |  |  | 0 | my $ref = $self->fetch( $msg, "FLAGS" ) or return undef; | 
| 2469 |  |  |  |  |  |  |  | 
| 2470 | 0 |  |  |  |  | 0 | my $u_f     = $self->Uid; | 
| 2471 | 0 |  |  |  |  | 0 | my $flagset = {}; | 
| 2472 |  |  |  |  |  |  |  | 
| 2473 |  |  |  |  |  |  | # Parse results, setting entry in result hash for each line | 
| 2474 | 0 |  |  |  |  | 0 | foreach my $line (@$ref) { | 
| 2475 | 0 |  |  |  |  | 0 | $self->_debug("flags: line = '$line'"); | 
| 2476 | 0 | 0 |  |  |  | 0 | if ( | 
| 2477 |  |  |  |  |  |  | $line =~ /\* \s+ (\d+) \s+ FETCH \s+    # * nnn FETCH | 
| 2478 |  |  |  |  |  |  | \( | 
| 2479 |  |  |  |  |  |  | (?:\s* UID \s+ (\d+) \s* )? # optional: UID nnn | 
| 2480 |  |  |  |  |  |  | FLAGS \s* \( (.*?) \) \s*   # FLAGS (\Flag1 \Flag2) | 
| 2481 |  |  |  |  |  |  | (?:\s* UID \s+ (\d+) \s* )? # optional: UID nnn | 
| 2482 |  |  |  |  |  |  | \) | 
| 2483 |  |  |  |  |  |  | /x | 
| 2484 |  |  |  |  |  |  | ) | 
| 2485 |  |  |  |  |  |  | { | 
| 2486 | 0 | 0 | 0 |  |  | 0 | my $mailid = $u_f ? ( $2 || $4 ) : $1; | 
| 2487 | 0 |  |  |  |  | 0 | $flagset->{$mailid} = [ split " ", $3 ]; | 
| 2488 |  |  |  |  |  |  | } | 
| 2489 |  |  |  |  |  |  | } | 
| 2490 |  |  |  |  |  |  |  | 
| 2491 |  |  |  |  |  |  | # Return a hash from msgid to flag array? | 
| 2492 | 0 | 0 |  |  |  | 0 | return $flagset | 
| 2493 |  |  |  |  |  |  | if ref $msgspec; | 
| 2494 |  |  |  |  |  |  |  | 
| 2495 |  |  |  |  |  |  | # Or, just one response? Return it if so | 
| 2496 | 0 |  |  |  |  | 0 | my $flagsref = $flagset->{$msgspec}; | 
| 2497 | 0 | 0 |  |  |  | 0 | return wantarray ? @{ $flagsref || [] } : $flagsref; | 
|  | 0 | 0 |  |  |  | 0 |  | 
| 2498 |  |  |  |  |  |  | } | 
| 2499 |  |  |  |  |  |  |  | 
| 2500 |  |  |  |  |  |  | # reduce a list, stripping undeclared flags. Flags with or without | 
| 2501 |  |  |  |  |  |  | # leading backslash. | 
| 2502 |  |  |  |  |  |  | sub supported_flags(@) { | 
| 2503 | 0 |  |  | 0 | 0 | 0 | my $self = shift; | 
| 2504 | 0 | 0 |  |  |  | 0 | my $sup  = $self->Supportedflags | 
| 2505 |  |  |  |  |  |  | or return @_; | 
| 2506 |  |  |  |  |  |  |  | 
| 2507 | 0 | 0 |  |  |  | 0 | return map { $sup->($_) } @_ | 
|  | 0 |  |  |  |  | 0 |  | 
| 2508 |  |  |  |  |  |  | if ref $sup eq 'CODE'; | 
| 2509 |  |  |  |  |  |  |  | 
| 2510 | 0 | 0 |  |  |  | 0 | grep { $sup->{ /^\\(\S+)/ ? lc $1 : () } } @_; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2511 |  |  |  |  |  |  | } | 
| 2512 |  |  |  |  |  |  |  | 
| 2513 |  |  |  |  |  |  | sub parse_headers { | 
| 2514 | 0 |  |  | 0 | 1 | 0 | my ( $self, $msgspec, @fields ) = @_; | 
| 2515 | 0 |  |  |  |  | 0 | my $fields = join ' ', @fields; | 
| 2516 | 0 | 0 |  |  |  | 0 | my $msg = ref $msgspec eq 'ARRAY' ? $self->Range($msgspec) : $msgspec; | 
| 2517 | 0 | 0 | 0 |  |  | 0 | my $peek = !defined $self->Peek || $self->Peek ? '.PEEK' : ''; | 
| 2518 |  |  |  |  |  |  |  | 
| 2519 | 0 | 0 |  |  |  | 0 | my $string = "$msg BODY$peek" | 
| 2520 |  |  |  |  |  |  | . ( $fields eq 'ALL' ? '[HEADER]' : "[HEADER.FIELDS ($fields)]" ); | 
| 2521 |  |  |  |  |  |  |  | 
| 2522 | 0 | 0 |  |  |  | 0 | my $raw = $self->fetch($string) or return undef; | 
| 2523 | 0 |  |  |  |  | 0 | my $cmd = shift @$raw; | 
| 2524 |  |  |  |  |  |  |  | 
| 2525 | 0 |  |  |  |  | 0 | my %headers;    # message ids to headers | 
| 2526 |  |  |  |  |  |  | my $h;          # fields for current msgid | 
| 2527 | 0 |  |  |  |  | 0 | my $field;      # previous field name, for unfolding | 
| 2528 | 0 |  |  |  |  | 0 | my %fieldmap = map { ( lc($_) => $_ ) } @fields; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2529 | 0 |  |  |  |  | 0 | my $msgid; | 
| 2530 |  |  |  |  |  |  |  | 
| 2531 |  |  |  |  |  |  | # BUG: parsing this way is prone to be buggy but works most of the time | 
| 2532 |  |  |  |  |  |  | # some example responses: | 
| 2533 |  |  |  |  |  |  | # * OK Message 1 no longer exists | 
| 2534 |  |  |  |  |  |  | # * 1 FETCH (UID 26535 BODY[HEADER] "") | 
| 2535 |  |  |  |  |  |  | # * 5 FETCH (UID 30699 BODY[HEADER] {1711} | 
| 2536 |  |  |  |  |  |  | # header: value... | 
| 2537 | 0 |  |  |  |  | 0 | foreach my $header ( map { split /$CR?$LF/o } @$raw ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 2538 |  |  |  |  |  |  |  | 
| 2539 |  |  |  |  |  |  | # Windows2003/Maillennium/others? have UID after headers | 
| 2540 | 0 | 0 |  |  |  | 0 | if ( | 
| 2541 |  |  |  |  |  |  | $header =~ s/^\* \s+ (\d+) \s+ FETCH \s+ | 
| 2542 |  |  |  |  |  |  | \( (.*?) BODY\[HEADER (?:\.FIELDS)? .*? \]\s*//ix | 
| 2543 |  |  |  |  |  |  | ) | 
| 2544 |  |  |  |  |  |  | {    # start new message header | 
| 2545 | 0 |  |  |  |  | 0 | ( $msgid, my $msgattrs ) = ( $1, $2 ); | 
| 2546 | 0 |  |  |  |  | 0 | $h = {}; | 
| 2547 | 0 | 0 |  |  |  | 0 | if ( $self->Uid ) {    # undef when win2003 | 
| 2548 | 0 | 0 |  |  |  | 0 | $msgid = $msgattrs =~ m/\b UID \s+ (\d+)/x ? $1 : undef; | 
| 2549 |  |  |  |  |  |  | } | 
| 2550 | 0 | 0 |  |  |  | 0 | $headers{$msgid} = $h if $msgid; | 
| 2551 |  |  |  |  |  |  | } | 
| 2552 | 0 | 0 |  |  |  | 0 | $header =~ /\S/ or next;    # skip empty lines. | 
| 2553 |  |  |  |  |  |  |  | 
| 2554 |  |  |  |  |  |  | # ( for vi | 
| 2555 | 0 | 0 | 0 |  |  | 0 | if ( $header =~ /^\)/ ) {    # end of this message | 
|  |  | 0 |  |  |  |  |  | 
| 2556 | 0 |  |  |  |  | 0 | undef $h;                # inbetween headers | 
| 2557 | 0 |  |  |  |  | 0 | next; | 
| 2558 |  |  |  |  |  |  | } | 
| 2559 |  |  |  |  |  |  | elsif ( !$msgid && $header =~ /^\s*UID\s+(\d+).*\)$/ ) { | 
| 2560 | 0 |  |  |  |  | 0 | $headers{$1} = $h;       # found UID win2003/Maillennium | 
| 2561 |  |  |  |  |  |  |  | 
| 2562 | 0 |  |  |  |  | 0 | undef $h; | 
| 2563 | 0 |  |  |  |  | 0 | next; | 
| 2564 |  |  |  |  |  |  | } | 
| 2565 |  |  |  |  |  |  |  | 
| 2566 | 0 | 0 |  |  |  | 0 | unless ( defined $h ) { | 
| 2567 | 0 |  |  |  |  | 0 | $self->_debug("found data between fetch headers: $header"); | 
| 2568 | 0 |  |  |  |  | 0 | next; | 
| 2569 |  |  |  |  |  |  | } | 
| 2570 |  |  |  |  |  |  |  | 
| 2571 | 0 | 0 | 0 |  |  | 0 | if ( $header and $header =~ s/^(\S+)\:\s*// ) { | 
|  |  | 0 | 0 |  |  |  |  | 
| 2572 | 0 |  | 0 |  |  | 0 | $field = $fieldmap{ lc $1 } || $1; | 
| 2573 | 0 |  |  |  |  | 0 | push @{ $h->{$field} }, $header; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2574 |  |  |  |  |  |  | } | 
| 2575 |  |  |  |  |  |  | elsif ( $field and ref $h->{$field} eq 'ARRAY' ) {    # folded header | 
| 2576 | 0 |  |  |  |  | 0 | $h->{$field}[-1] .= $header; | 
| 2577 |  |  |  |  |  |  | } | 
| 2578 |  |  |  |  |  |  | else { | 
| 2579 |  |  |  |  |  |  |  | 
| 2580 |  |  |  |  |  |  | # show data if it is not like  '"")' or '{123}' | 
| 2581 | 0 | 0 |  |  |  | 0 | $self->_debug("non-header data between fetch headers: $header") | 
| 2582 |  |  |  |  |  |  | if ( $header !~ /^(?:\s*\"\"\)|\{\d+\})$CR?$LF$/o ); | 
| 2583 |  |  |  |  |  |  | } | 
| 2584 |  |  |  |  |  |  | } | 
| 2585 |  |  |  |  |  |  |  | 
| 2586 |  |  |  |  |  |  | # if we asked for one message, just return its hash, | 
| 2587 |  |  |  |  |  |  | # otherwise, return hash of numbers => header hash | 
| 2588 | 0 | 0 |  |  |  | 0 | ref $msgspec eq 'ARRAY' ? \%headers : $headers{$msgspec}; | 
| 2589 |  |  |  |  |  |  | } | 
| 2590 |  |  |  |  |  |  |  | 
| 2591 | 0 |  |  | 0 | 1 | 0 | sub subject { $_[0]->get_header( $_[1], "Subject" ) } | 
| 2592 | 0 |  |  | 0 | 1 | 0 | sub date    { $_[0]->get_header( $_[1], "Date" ) } | 
| 2593 | 0 |  |  | 0 | 0 | 0 | sub rfc822_header { shift->get_header(@_) } | 
| 2594 |  |  |  |  |  |  |  | 
| 2595 |  |  |  |  |  |  | sub get_header { | 
| 2596 | 0 |  |  | 0 | 1 | 0 | my ( $self, $msg, $field ) = @_; | 
| 2597 | 0 |  |  |  |  | 0 | my $headers = $self->parse_headers( $msg, $field ); | 
| 2598 | 0 | 0 |  |  |  | 0 | $headers ? $headers->{$field}[0] : undef; | 
| 2599 |  |  |  |  |  |  | } | 
| 2600 |  |  |  |  |  |  |  | 
| 2601 |  |  |  |  |  |  | sub recent_count { | 
| 2602 | 0 |  |  | 0 | 1 | 0 | my ( $self, $folder ) = ( shift, shift ); | 
| 2603 |  |  |  |  |  |  |  | 
| 2604 | 0 | 0 |  |  |  | 0 | $self->status( $folder, 'RECENT' ) | 
| 2605 |  |  |  |  |  |  | or return undef; | 
| 2606 |  |  |  |  |  |  |  | 
| 2607 |  |  |  |  |  |  | my $r = | 
| 2608 | 0 |  |  | 0 |  | 0 | first { s/\*\s+STATUS\s+.*\(RECENT\s+(\d+)\s*\)/$1/ } $self->History; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2609 | 0 |  |  |  |  | 0 | chomp $r; | 
| 2610 | 0 |  |  |  |  | 0 | $r; | 
| 2611 |  |  |  |  |  |  | } | 
| 2612 |  |  |  |  |  |  |  | 
| 2613 |  |  |  |  |  |  | sub message_count { | 
| 2614 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 2615 | 0 |  | 0 |  |  | 0 | my $folder = shift || $self->Folder; | 
| 2616 |  |  |  |  |  |  |  | 
| 2617 | 0 | 0 |  |  |  | 0 | $self->status( $folder, 'MESSAGES' ) | 
| 2618 |  |  |  |  |  |  | or return undef; | 
| 2619 |  |  |  |  |  |  |  | 
| 2620 | 0 |  |  |  |  | 0 | foreach my $result ( $self->Results ) { | 
| 2621 | 0 | 0 |  |  |  | 0 | return $1 if $result =~ /\(MESSAGES\s+(\d+)\s*\)/i; | 
| 2622 |  |  |  |  |  |  | } | 
| 2623 |  |  |  |  |  |  |  | 
| 2624 | 0 |  |  |  |  | 0 | undef; | 
| 2625 |  |  |  |  |  |  | } | 
| 2626 |  |  |  |  |  |  |  | 
| 2627 | 0 |  |  | 0 | 1 | 0 | sub recent()   { shift->search('recent') } | 
| 2628 | 0 |  |  | 0 | 1 | 0 | sub seen()     { shift->search('seen') } | 
| 2629 | 0 |  |  | 0 | 1 | 0 | sub unseen()   { shift->search('unseen') } | 
| 2630 | 0 |  |  | 0 | 1 | 0 | sub messages() { shift->search('ALL') } | 
| 2631 |  |  |  |  |  |  |  | 
| 2632 | 0 |  |  | 0 | 1 | 0 | sub sentbefore($$) { shift->_search_date( sentbefore => @_ ) } | 
| 2633 | 0 |  |  | 0 | 1 | 0 | sub sentsince($$)  { shift->_search_date( sentsince  => @_ ) } | 
| 2634 | 0 |  |  | 0 | 1 | 0 | sub senton($$)     { shift->_search_date( senton     => @_ ) } | 
| 2635 | 0 |  |  | 0 | 1 | 0 | sub since($$)      { shift->_search_date( since      => @_ ) } | 
| 2636 | 0 |  |  | 0 | 1 | 0 | sub before($$)     { shift->_search_date( before     => @_ ) } | 
| 2637 | 0 |  |  | 0 | 1 | 0 | sub on($$)         { shift->_search_date( on         => @_ ) } | 
| 2638 |  |  |  |  |  |  |  | 
| 2639 |  |  |  |  |  |  | sub _search_date($$$) { | 
| 2640 | 0 |  |  | 0 |  | 0 | my ( $self, $how, $time ) = @_; | 
| 2641 | 0 |  |  |  |  | 0 | my $imapdate; | 
| 2642 |  |  |  |  |  |  |  | 
| 2643 | 0 | 0 |  |  |  | 0 | if ( $time =~ /\d\d-\D\D\D-\d\d\d\d/ ) { | 
|  |  | 0 |  |  |  |  |  | 
| 2644 | 0 |  |  |  |  | 0 | $imapdate = $time; | 
| 2645 |  |  |  |  |  |  | } | 
| 2646 |  |  |  |  |  |  | elsif ( $time =~ /^\d+$/ ) { | 
| 2647 | 0 |  |  |  |  | 0 | my @ltime = localtime $time; | 
| 2648 | 0 |  |  |  |  | 0 | $imapdate = sprintf( "%2.2d-%s-%4.4d", | 
| 2649 |  |  |  |  |  |  | $ltime[3], | 
| 2650 |  |  |  |  |  |  | $mnt[ $ltime[4] ], | 
| 2651 |  |  |  |  |  |  | $ltime[5] + 1900 ); | 
| 2652 |  |  |  |  |  |  | } | 
| 2653 |  |  |  |  |  |  | else { | 
| 2654 | 0 |  |  |  |  | 0 | $self->LastError("Invalid date format supplied for '$how': $time"); | 
| 2655 | 0 |  |  |  |  | 0 | return undef; | 
| 2656 |  |  |  |  |  |  | } | 
| 2657 |  |  |  |  |  |  |  | 
| 2658 | 0 | 0 |  |  |  | 0 | $self->_imap_uid_command( SEARCH => $how, $imapdate ) | 
| 2659 |  |  |  |  |  |  | or return undef; | 
| 2660 |  |  |  |  |  |  |  | 
| 2661 | 0 |  |  |  |  | 0 | my @hits; | 
| 2662 | 0 |  |  |  |  | 0 | foreach ( $self->History ) { | 
| 2663 | 0 |  |  |  |  | 0 | chomp; | 
| 2664 | 0 |  |  |  |  | 0 | s/$CR?$LF$//o; | 
| 2665 | 0 | 0 |  |  |  | 0 | s/^\*\s+SEARCH\s+//i or next; | 
| 2666 | 0 |  |  |  |  | 0 | push @hits, grep /\d/, split; | 
| 2667 |  |  |  |  |  |  | } | 
| 2668 | 0 |  |  |  |  | 0 | $self->_debug("Hits are: @hits"); | 
| 2669 | 0 | 0 |  |  |  | 0 | return wantarray ? @hits : \@hits; | 
| 2670 |  |  |  |  |  |  | } | 
| 2671 |  |  |  |  |  |  |  | 
| 2672 |  |  |  |  |  |  | sub or { | 
| 2673 | 0 |  |  | 0 | 1 | 0 | my ( $self, @what ) = @_; | 
| 2674 | 0 | 0 |  |  |  | 0 | if ( @what < 2 ) { | 
| 2675 | 0 |  |  |  |  | 0 | $self->LastError("Invalid number of arguments passed to or()"); | 
| 2676 | 0 |  |  |  |  | 0 | return undef; | 
| 2677 |  |  |  |  |  |  | } | 
| 2678 |  |  |  |  |  |  |  | 
| 2679 | 0 |  |  |  |  | 0 | my $or = | 
| 2680 |  |  |  |  |  |  | "OR " . $self->Quote( shift @what ) . " " . $self->Quote( shift @what ); | 
| 2681 |  |  |  |  |  |  |  | 
| 2682 | 0 |  |  |  |  | 0 | $or = "OR $or " . $self->Quote($_) for @what; | 
| 2683 |  |  |  |  |  |  |  | 
| 2684 | 0 | 0 |  |  |  | 0 | $self->_imap_uid_command( SEARCH => $or ) | 
| 2685 |  |  |  |  |  |  | or return undef; | 
| 2686 |  |  |  |  |  |  |  | 
| 2687 | 0 |  |  |  |  | 0 | my @hits; | 
| 2688 | 0 |  |  |  |  | 0 | foreach ( $self->History ) { | 
| 2689 | 0 |  |  |  |  | 0 | chomp; | 
| 2690 | 0 |  |  |  |  | 0 | s/$CR?$LF$//o; | 
| 2691 | 0 | 0 |  |  |  | 0 | s/^\*\s+SEARCH\s+//i or next; | 
| 2692 | 0 |  |  |  |  | 0 | push @hits, grep /\d/, split; | 
| 2693 |  |  |  |  |  |  | } | 
| 2694 | 0 |  |  |  |  | 0 | $self->_debug("Hits are now: @hits"); | 
| 2695 |  |  |  |  |  |  |  | 
| 2696 | 0 | 0 |  |  |  | 0 | return wantarray ? @hits : \@hits; | 
| 2697 |  |  |  |  |  |  | } | 
| 2698 |  |  |  |  |  |  |  | 
| 2699 | 0 |  |  | 0 | 1 | 0 | sub disconnect { shift->logout } | 
| 2700 |  |  |  |  |  |  |  | 
| 2701 |  |  |  |  |  |  | sub _quote_search { | 
| 2702 | 0 |  |  | 0 |  | 0 | my ( $self, @args ) = @_; | 
| 2703 | 0 |  |  |  |  | 0 | my @ret; | 
| 2704 | 0 |  |  |  |  | 0 | foreach my $v (@args) { | 
| 2705 | 0 | 0 |  |  |  | 0 | if ( ref($v) eq "SCALAR" ) { | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 2706 | 0 |  |  |  |  | 0 | push( @ret, $$v ); | 
| 2707 |  |  |  |  |  |  | } | 
| 2708 |  |  |  |  |  |  | elsif ( exists $SEARCH_KEYS{ uc($v) } ) { | 
| 2709 | 0 |  |  |  |  | 0 | push( @ret, $v ); | 
| 2710 |  |  |  |  |  |  | } | 
| 2711 |  |  |  |  |  |  | elsif ( @args == 1 ) { | 
| 2712 | 0 |  |  |  |  | 0 | push( @ret, $v );    # <3.17 compat: caller responsible for quoting | 
| 2713 |  |  |  |  |  |  | } | 
| 2714 |  |  |  |  |  |  | else { | 
| 2715 | 0 |  |  |  |  | 0 | push( @ret, $self->Quote($v) ); | 
| 2716 |  |  |  |  |  |  | } | 
| 2717 |  |  |  |  |  |  | } | 
| 2718 | 0 |  |  |  |  | 0 | return @ret; | 
| 2719 |  |  |  |  |  |  | } | 
| 2720 |  |  |  |  |  |  |  | 
| 2721 |  |  |  |  |  |  | sub search { | 
| 2722 | 0 |  |  | 0 | 1 | 0 | my ( $self, @args ) = @_; | 
| 2723 |  |  |  |  |  |  |  | 
| 2724 | 0 |  |  |  |  | 0 | @args = $self->_quote_search(@args); | 
| 2725 |  |  |  |  |  |  |  | 
| 2726 | 0 | 0 |  |  |  | 0 | $self->_imap_uid_command( SEARCH => @args ) | 
| 2727 |  |  |  |  |  |  | or return undef; | 
| 2728 |  |  |  |  |  |  |  | 
| 2729 | 0 |  |  |  |  | 0 | my @hits; | 
| 2730 | 0 |  |  |  |  | 0 | foreach ( $self->History ) { | 
| 2731 | 0 |  |  |  |  | 0 | chomp; | 
| 2732 | 0 |  |  |  |  | 0 | s/$CR?$LF$//o; | 
| 2733 | 0 | 0 |  |  |  | 0 | s/^\*\s+SEARCH\s+(?=.*?\d)// or next; | 
| 2734 | 0 |  |  |  |  | 0 | push @hits, grep /^\d+$/, split; | 
| 2735 |  |  |  |  |  |  | } | 
| 2736 |  |  |  |  |  |  |  | 
| 2737 |  |  |  |  |  |  | @hits | 
| 2738 | 0 | 0 |  |  |  | 0 | or $self->_debug("Search successful but found no matching messages"); | 
| 2739 |  |  |  |  |  |  |  | 
| 2740 |  |  |  |  |  |  | # return empty list | 
| 2741 |  |  |  |  |  |  | return | 
| 2742 |  |  |  |  |  |  | wantarray     ? @hits | 
| 2743 | 0 | 0 |  |  |  | 0 | : !@hits        ? \@hits | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 2744 |  |  |  |  |  |  | : $self->Ranges ? $self->Range( \@hits ) | 
| 2745 |  |  |  |  |  |  | :                 \@hits; | 
| 2746 |  |  |  |  |  |  | } | 
| 2747 |  |  |  |  |  |  |  | 
| 2748 |  |  |  |  |  |  | # returns a Thread data structure | 
| 2749 |  |  |  |  |  |  | my $thread_parser; | 
| 2750 |  |  |  |  |  |  |  | 
| 2751 |  |  |  |  |  |  | sub thread { | 
| 2752 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 2753 |  |  |  |  |  |  |  | 
| 2754 | 0 | 0 |  |  |  | 0 | return undef unless defined $self->has_capability("THREAD=REFERENCES"); | 
| 2755 | 0 |  | 0 |  |  | 0 | my $algorythm = shift | 
| 2756 |  |  |  |  |  |  | || ( | 
| 2757 |  |  |  |  |  |  | $self->has_capability("THREAD=REFERENCES") | 
| 2758 |  |  |  |  |  |  | ? 'REFERENCES' | 
| 2759 |  |  |  |  |  |  | : 'ORDEREDSUBJECT' | 
| 2760 |  |  |  |  |  |  | ); | 
| 2761 |  |  |  |  |  |  |  | 
| 2762 | 0 |  | 0 |  |  | 0 | my $charset = shift || 'UTF-8'; | 
| 2763 | 0 | 0 |  |  |  | 0 | my @a = @_ ? @_ : 'ALL'; | 
| 2764 |  |  |  |  |  |  |  | 
| 2765 |  |  |  |  |  |  | $a[-1] = $self->Quote( $a[-1], 1 ) | 
| 2766 | 0 | 0 | 0 |  |  | 0 | if @a > 1 && !exists $SEARCH_KEYS{ uc $a[-1] }; | 
| 2767 |  |  |  |  |  |  |  | 
| 2768 | 0 | 0 |  |  |  | 0 | $self->_imap_uid_command( THREAD => $algorythm, $charset, @a ) | 
| 2769 |  |  |  |  |  |  | or return undef; | 
| 2770 |  |  |  |  |  |  |  | 
| 2771 | 0 | 0 |  |  |  | 0 | unless ($thread_parser) { | 
| 2772 | 0 | 0 | 0 |  |  | 0 | return if ( defined($thread_parser) and $thread_parser == 0 ); | 
| 2773 |  |  |  |  |  |  |  | 
| 2774 | 0 |  |  |  |  | 0 | my $class = $self->_load_module("Thread"); | 
| 2775 | 0 | 0 |  |  |  | 0 | unless ($class) { | 
| 2776 | 0 |  |  |  |  | 0 | $thread_parser = 0; | 
| 2777 | 0 |  |  |  |  | 0 | return undef; | 
| 2778 |  |  |  |  |  |  | } | 
| 2779 | 0 |  |  |  |  | 0 | $thread_parser = $class->new; | 
| 2780 |  |  |  |  |  |  | } | 
| 2781 |  |  |  |  |  |  |  | 
| 2782 | 0 |  |  |  |  | 0 | my $thread; | 
| 2783 | 0 |  |  |  |  | 0 | foreach ( $self->History ) { | 
| 2784 | 0 | 0 |  |  |  | 0 | /^\*\s+THREAD\s+/ or next; | 
| 2785 | 0 |  |  |  |  | 0 | s/$CR?$LF|$LF+/ /og; | 
| 2786 | 0 |  |  |  |  | 0 | $thread = $thread_parser->start($_); | 
| 2787 |  |  |  |  |  |  | } | 
| 2788 |  |  |  |  |  |  |  | 
| 2789 | 0 | 0 |  |  |  | 0 | unless ($thread) { | 
| 2790 | 0 |  |  |  |  | 0 | $self->LastError( | 
| 2791 |  |  |  |  |  |  | "Thread search completed successfully but found no matching messages" | 
| 2792 |  |  |  |  |  |  | ); | 
| 2793 | 0 |  |  |  |  | 0 | return undef; | 
| 2794 |  |  |  |  |  |  | } | 
| 2795 |  |  |  |  |  |  |  | 
| 2796 | 0 |  |  |  |  | 0 | $thread; | 
| 2797 |  |  |  |  |  |  | } | 
| 2798 |  |  |  |  |  |  |  | 
| 2799 |  |  |  |  |  |  | sub delete_message { | 
| 2800 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 2801 | 0 | 0 |  |  |  | 0 | my @msgs = map { ref $_ eq 'ARRAY' ? @$_ : split /\,/ } @_; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2802 |  |  |  |  |  |  |  | 
| 2803 | 0 | 0 |  |  |  | 0 | $self->store( join( ',', @msgs ), '+FLAGS.SILENT', '(\Deleted)' ) | 
| 2804 |  |  |  |  |  |  | ? scalar @msgs | 
| 2805 |  |  |  |  |  |  | : undef; | 
| 2806 |  |  |  |  |  |  | } | 
| 2807 |  |  |  |  |  |  |  | 
| 2808 |  |  |  |  |  |  | sub restore_message { | 
| 2809 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 2810 | 0 | 0 |  |  |  | 0 | my $msgs = join ',', map { ref $_ eq 'ARRAY' ? @$_ : split /\,/ } @_; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2811 |  |  |  |  |  |  |  | 
| 2812 | 0 | 0 |  |  |  | 0 | $self->store( $msgs, '-FLAGS', '(\Deleted)' ) or return undef; | 
| 2813 | 0 |  |  |  |  | 0 | scalar grep /^\*\s\d+\sFETCH\s\(.*FLAGS.*(?!\\Deleted)/, $self->Results; | 
| 2814 |  |  |  |  |  |  | } | 
| 2815 |  |  |  |  |  |  |  | 
| 2816 |  |  |  |  |  |  | sub uidvalidity { | 
| 2817 | 0 |  |  | 0 | 1 | 0 | my ( $self, $folder ) = @_; | 
| 2818 | 0 | 0 |  |  |  | 0 | $self->status( $folder, "UIDVALIDITY" ) or return undef; | 
| 2819 | 0 |  |  | 0 |  | 0 | my $line = first { /UIDVALIDITY/i } $self->History; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2820 | 0 | 0 | 0 |  |  | 0 | defined $line && $line =~ /\(UIDVALIDITY\s+([^\)]+)/ ? $1 : undef; | 
| 2821 |  |  |  |  |  |  | } | 
| 2822 |  |  |  |  |  |  |  | 
| 2823 |  |  |  |  |  |  | sub uidnext { | 
| 2824 | 0 |  |  | 0 | 1 | 0 | my ( $self, $folder ) = @_; | 
| 2825 | 0 | 0 |  |  |  | 0 | $self->status( $folder, "UIDNEXT" ) or return undef; | 
| 2826 | 0 |  |  | 0 |  | 0 | my $line = first { /UIDNEXT/i } $self->History; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2827 | 0 | 0 | 0 |  |  | 0 | defined $line && $line =~ /\(UIDNEXT\s+([^\)]+)/ ? $1 : undef; | 
| 2828 |  |  |  |  |  |  | } | 
| 2829 |  |  |  |  |  |  |  | 
| 2830 |  |  |  |  |  |  | # sort @caps for consistency? | 
| 2831 |  |  |  |  |  |  | sub capability { | 
| 2832 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 2833 |  |  |  |  |  |  |  | 
| 2834 | 0 | 0 |  |  |  | 0 | if ( $self->{CAPABILITY} ) { | 
| 2835 | 0 |  |  |  |  | 0 | my @caps = keys %{ $self->{CAPABILITY} }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2836 | 0 | 0 |  |  |  | 0 | return wantarray ? @caps : \@caps; | 
| 2837 |  |  |  |  |  |  | } | 
| 2838 |  |  |  |  |  |  |  | 
| 2839 | 0 | 0 |  |  |  | 0 | $self->_imap_command('CAPABILITY') | 
| 2840 |  |  |  |  |  |  | or return undef; | 
| 2841 |  |  |  |  |  |  |  | 
| 2842 | 0 |  |  |  |  | 0 | my @caps = map { split } grep /^\*\s+CAPABILITY\s+/, $self->History; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2843 | 0 |  |  |  |  | 0 | splice( @caps, 0, 2 );    # remove * CAPABILITY from array | 
| 2844 |  |  |  |  |  |  |  | 
| 2845 |  |  |  |  |  |  | # use iterator as we may append to @caps for CAPA=VALUE | 
| 2846 | 0 |  |  |  |  | 0 | for ( my $i = 0 ; $i < @caps ; $i++ ) { | 
| 2847 | 0 |  | 0 |  |  | 0 | $self->{CAPABILITY}->{ uc $caps[$i] } ||= []; | 
| 2848 | 0 |  |  |  |  | 0 | my ( $capa, $cval ) = split( /=/, $caps[$i], 2 ); | 
| 2849 | 0 | 0 |  |  |  | 0 | if ( defined $cval ) { | 
| 2850 | 0 |  |  |  |  | 0 | $capa = uc $capa; | 
| 2851 | 0 | 0 |  |  |  | 0 | push( @caps, $capa ) unless exists $self->{CAPABILITY}->{$capa}; | 
| 2852 | 0 |  |  |  |  | 0 | push( @{ $self->{CAPABILITY}->{$capa} }, $cval ); | 
|  | 0 |  |  |  |  | 0 |  | 
| 2853 |  |  |  |  |  |  | } | 
| 2854 |  |  |  |  |  |  | } | 
| 2855 |  |  |  |  |  |  |  | 
| 2856 | 0 | 0 |  |  |  | 0 | return wantarray ? @caps : \@caps; | 
| 2857 |  |  |  |  |  |  | } | 
| 2858 |  |  |  |  |  |  |  | 
| 2859 |  |  |  |  |  |  | # use "" not undef when lookup fails to differentiate imap command | 
| 2860 |  |  |  |  |  |  | # failure vs lack of capability | 
| 2861 |  |  |  |  |  |  | sub has_capability { | 
| 2862 | 0 |  |  | 0 | 1 | 0 | my ( $self, $which ) = @_; | 
| 2863 | 0 | 0 |  |  |  | 0 | $self->capability or return undef; | 
| 2864 | 0 |  |  |  |  | 0 | my $aref = []; | 
| 2865 |  |  |  |  |  |  |  | 
| 2866 |  |  |  |  |  |  | # exists in CAPABILITIES? possibly in CAPA=VALUE format? | 
| 2867 | 0 | 0 |  |  |  | 0 | if ( defined $which ) { | 
| 2868 | 0 |  |  |  |  | 0 | $which = uc $which; | 
| 2869 | 0 | 0 |  |  |  | 0 | if ( exists $self->{CAPABILITY}{$which} ) { | 
| 2870 | 0 | 0 |  |  |  | 0 | if ( @{ $self->{CAPABILITY}{$which} } ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 2871 | 0 |  |  |  |  | 0 | $aref = $self->{CAPABILITY}{$which}; | 
| 2872 |  |  |  |  |  |  | } | 
| 2873 |  |  |  |  |  |  | else { | 
| 2874 | 0 |  |  |  |  | 0 | $aref = [$which]; | 
| 2875 |  |  |  |  |  |  | } | 
| 2876 |  |  |  |  |  |  | } | 
| 2877 |  |  |  |  |  |  | } | 
| 2878 |  |  |  |  |  |  |  | 
| 2879 | 0 | 0 |  |  |  | 0 | return @$aref if wantarray; | 
| 2880 | 0 | 0 |  |  |  | 0 | return scalar @$aref ? $aref : ""; | 
| 2881 |  |  |  |  |  |  | } | 
| 2882 |  |  |  |  |  |  |  | 
| 2883 |  |  |  |  |  |  | sub imap4rev1 { | 
| 2884 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 2885 | 0 | 0 |  |  |  | 0 | return $self->{_IMAP4REV1} if exists $self->{_IMAP4REV1}; | 
| 2886 | 0 |  |  |  |  | 0 | $self->{_IMAP4REV1} = $self->has_capability('IMAP4REV1'); | 
| 2887 |  |  |  |  |  |  | } | 
| 2888 |  |  |  |  |  |  |  | 
| 2889 |  |  |  |  |  |  | #??? what a horror! | 
| 2890 |  |  |  |  |  |  | sub namespace { | 
| 2891 |  |  |  |  |  |  |  | 
| 2892 |  |  |  |  |  |  | # Returns a nested list as follows: | 
| 2893 |  |  |  |  |  |  | # [ | 
| 2894 |  |  |  |  |  |  | #  [ | 
| 2895 |  |  |  |  |  |  | #   [ $user_prefix,  $user_delim  ] (,[$user_prefix2  ,$user_delim  ],...), | 
| 2896 |  |  |  |  |  |  | #  ], | 
| 2897 |  |  |  |  |  |  | #  [ | 
| 2898 |  |  |  |  |  |  | #   [ $shared_prefix,$shared_delim] (,[$shared_prefix2,$shared_delim],... ), | 
| 2899 |  |  |  |  |  |  | #  ], | 
| 2900 |  |  |  |  |  |  | #  [ | 
| 2901 |  |  |  |  |  |  | #   [$public_prefix, $public_delim] (,[$public_prefix2,$public_delim],...), | 
| 2902 |  |  |  |  |  |  | #  ], | 
| 2903 |  |  |  |  |  |  | # ]; | 
| 2904 |  |  |  |  |  |  |  | 
| 2905 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 2906 | 0 | 0 |  |  |  | 0 | unless ( $self->has_capability("NAMESPACE") ) { | 
| 2907 | 0 | 0 |  |  |  | 0 | $self->LastError( "NO NAMESPACE not supported by " . $self->Server ) | 
| 2908 |  |  |  |  |  |  | unless $self->LastError; | 
| 2909 | 0 |  |  |  |  | 0 | return undef; | 
| 2910 |  |  |  |  |  |  | } | 
| 2911 |  |  |  |  |  |  |  | 
| 2912 | 0 | 0 |  |  |  | 0 | my $got = $self->_imap_command("NAMESPACE") or return undef; | 
| 2913 | 0 | 0 |  |  |  | 0 | my @namespaces = map { /^\* NAMESPACE (.*)/ ? $1 : () } $got->Results; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2914 |  |  |  |  |  |  |  | 
| 2915 | 0 |  |  |  |  | 0 | my $namespace = shift @namespaces; | 
| 2916 | 0 |  |  |  |  | 0 | $namespace =~ s/$CR?$LF$//o; | 
| 2917 |  |  |  |  |  |  |  | 
| 2918 | 0 |  |  |  |  | 0 | my ( $personal, $shared, $public ) = $namespace =~ m# | 
| 2919 |  |  |  |  |  |  | (NIL|\((?:\([^\)]+\)\s*)+\))\s | 
| 2920 |  |  |  |  |  |  | (NIL|\((?:\([^\)]+\)\s*)+\))\s | 
| 2921 |  |  |  |  |  |  | (NIL|\((?:\([^\)]+\)\s*)+\)) | 
| 2922 |  |  |  |  |  |  | #xi; | 
| 2923 |  |  |  |  |  |  |  | 
| 2924 | 0 |  |  |  |  | 0 | my @ns; | 
| 2925 | 0 |  |  |  |  | 0 | $self->_debug("NAMESPACE: pers=$personal, shared=$shared, pub=$public"); | 
| 2926 | 0 |  |  |  |  | 0 | foreach ( $personal, $shared, $public ) { | 
| 2927 | 0 | 0 |  |  |  | 0 | uc $_ ne 'NIL' or next; | 
| 2928 | 0 |  |  |  |  | 0 | s/^\((.*)\)$/$1/; | 
| 2929 |  |  |  |  |  |  |  | 
| 2930 | 0 |  |  |  |  | 0 | my @pieces = m#\(([^\)]*)\)#g; | 
| 2931 | 0 |  |  |  |  | 0 | $self->_debug("NAMESPACE pieces: @pieces"); | 
| 2932 |  |  |  |  |  |  |  | 
| 2933 | 0 |  |  |  |  | 0 | push @ns, [ map { [m#"([^"]*)"\s*#g] } @pieces ]; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2934 |  |  |  |  |  |  | } | 
| 2935 |  |  |  |  |  |  |  | 
| 2936 | 0 | 0 |  |  |  | 0 | return wantarray ? @ns : \@ns; | 
| 2937 |  |  |  |  |  |  | } | 
| 2938 |  |  |  |  |  |  |  | 
| 2939 |  |  |  |  |  |  | sub internaldate { | 
| 2940 | 0 |  |  | 0 | 1 | 0 | my ( $self, $msg ) = @_; | 
| 2941 | 0 | 0 |  |  |  | 0 | $self->_imap_uid_command( FETCH => $msg, 'INTERNALDATE' ) | 
| 2942 |  |  |  |  |  |  | or return undef; | 
| 2943 | 0 |  |  |  |  | 0 | my $hist = join '', $self->History; | 
| 2944 | 0 | 0 |  |  |  | 0 | return $hist =~ /\bINTERNALDATE "([^"]*)"/i ? $1 : undef; | 
| 2945 |  |  |  |  |  |  | } | 
| 2946 |  |  |  |  |  |  |  | 
| 2947 |  |  |  |  |  |  | sub is_parent { | 
| 2948 | 0 |  |  | 0 | 1 | 0 | my ( $self, $folder ) = @_; | 
| 2949 | 0 | 0 |  |  |  | 0 | my $list = $self->list( undef, $folder ) or return undef; | 
| 2950 |  |  |  |  |  |  |  | 
| 2951 | 0 |  |  |  |  | 0 | my $attrs; | 
| 2952 | 0 |  |  |  |  | 0 | foreach my $resp (@$list) { | 
| 2953 | 0 |  |  |  |  | 0 | my $rec = $self->_list_or_lsub_response_parse($resp); | 
| 2954 | 0 | 0 |  |  |  | 0 | next unless defined $rec->{attrs}; | 
| 2955 | 0 | 0 |  |  |  | 0 | $self->_debug("unexpected attrs data: @$list\n") if $attrs; | 
| 2956 | 0 |  |  |  |  | 0 | $attrs = $rec->{attrs}; | 
| 2957 |  |  |  |  |  |  | } | 
| 2958 |  |  |  |  |  |  |  | 
| 2959 | 0 | 0 |  |  |  | 0 | if ($attrs) { | 
| 2960 | 0 | 0 |  | 0 |  | 0 | return undef if first { lc($_) eq '\noinferiors' } @$attrs; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2961 | 0 | 0 |  | 0 |  | 0 | return 1     if first { lc($_) eq '\haschildren' } @$attrs; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2962 | 0 | 0 |  | 0 |  | 0 | return 0     if first { lc($_) eq '\hasnochildren' } @$attrs; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2963 |  |  |  |  |  |  | } | 
| 2964 |  |  |  |  |  |  | else { | 
| 2965 | 0 |  |  |  |  | 0 | $self->_debug( join( "\n\t", "no attrs for '$folder' in:", @$list ) ); | 
| 2966 |  |  |  |  |  |  | } | 
| 2967 |  |  |  |  |  |  |  | 
| 2968 |  |  |  |  |  |  | # BUG? This may be overkill for normal use cases... | 
| 2969 |  |  |  |  |  |  | # flag not supported or not returned for some reason, try via folders() | 
| 2970 | 0 |  | 0 |  |  | 0 | my $sep = $self->separator($folder) || $self->separator(undef); | 
| 2971 | 0 | 0 |  |  |  | 0 | return undef unless defined $sep; | 
| 2972 |  |  |  |  |  |  |  | 
| 2973 | 0 |  |  |  |  | 0 | my $lead = $folder . $sep; | 
| 2974 | 0 |  |  |  |  | 0 | my $len  = length $lead; | 
| 2975 | 0 |  |  |  |  | 0 | scalar grep { $lead eq substr( $_, 0, $len ) } $self->folders; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2976 |  |  |  |  |  |  | } | 
| 2977 |  |  |  |  |  |  |  | 
| 2978 |  |  |  |  |  |  | sub selectable { | 
| 2979 | 0 |  |  | 0 | 1 | 0 | my ( $self, $f ) = @_; | 
| 2980 | 0 | 0 |  |  |  | 0 | my $info = $self->list( "", $f ) or return undef; | 
| 2981 | 0 |  |  |  |  | 0 | return not( grep /[\s(]\\Noselect[)\s]/i, @$info ); | 
| 2982 |  |  |  |  |  |  | } | 
| 2983 |  |  |  |  |  |  |  | 
| 2984 |  |  |  |  |  |  | # append( $self, $folder, $text [, $optmsg] ) | 
| 2985 |  |  |  |  |  |  | # - conserve memory and use $_[0] to avoid copying $text (it may be huge!) | 
| 2986 |  |  |  |  |  |  | # - BUG?: should deprecate this method in favor of append_string | 
| 2987 |  |  |  |  |  |  | sub append { | 
| 2988 | 0 |  |  | 0 | 1 | 0 | my $self   = shift; | 
| 2989 | 0 |  |  |  |  | 0 | my $folder = shift; | 
| 2990 |  |  |  |  |  |  |  | 
| 2991 |  |  |  |  |  |  | # $message_string is whatever is left in @_ | 
| 2992 | 0 | 0 |  |  |  | 0 | $self->append_string( $folder, ( @_ > 1 ? join( $CRLF, @_ ) : $_[0] ) ); | 
| 2993 |  |  |  |  |  |  | } | 
| 2994 |  |  |  |  |  |  |  | 
| 2995 |  |  |  |  |  |  | sub _clean_flags { | 
| 2996 | 0 |  |  | 0 |  | 0 | my ( $self, $flags ) = @_; | 
| 2997 | 0 |  |  |  |  | 0 | $flags =~ s/^\s+//; | 
| 2998 | 0 |  |  |  |  | 0 | $flags =~ s/\s+$//; | 
| 2999 | 0 | 0 |  |  |  | 0 | $flags = "($flags)" if $flags !~ /^\(.*\)$/; | 
| 3000 | 0 |  |  |  |  | 0 | return $flags; | 
| 3001 |  |  |  |  |  |  | } | 
| 3002 |  |  |  |  |  |  |  | 
| 3003 |  |  |  |  |  |  | # RFC 3501: date-day-fixed = (SP DIGIT) / 2DIGIT | 
| 3004 |  |  |  |  |  |  | sub _clean_date { | 
| 3005 | 0 |  |  | 0 |  | 0 | my ( $self, $date ) = @_; | 
| 3006 | 0 | 0 |  |  |  | 0 | $date =~ s/^\s+// if $date !~ /^\s\d/; | 
| 3007 | 0 |  |  |  |  | 0 | $date =~ s/\s+$//; | 
| 3008 | 0 | 0 |  |  |  | 0 | $date = qq("$date") if $date !~ /^"/; | 
| 3009 | 0 |  |  |  |  | 0 | return $date; | 
| 3010 |  |  |  |  |  |  | } | 
| 3011 |  |  |  |  |  |  |  | 
| 3012 |  |  |  |  |  |  | sub _append_command { | 
| 3013 | 0 |  |  | 0 |  | 0 | my ( $self, $folder, $flags, $date, $length ) = @_; | 
| 3014 | 0 | 0 |  |  |  | 0 | return join( " ", | 
|  |  | 0 |  |  |  |  |  | 
| 3015 |  |  |  |  |  |  | "APPEND $folder", | 
| 3016 |  |  |  |  |  |  | ( $flags ? $flags : () ), | 
| 3017 |  |  |  |  |  |  | ( $date  ? $date  : () ), | 
| 3018 |  |  |  |  |  |  | "{" . $length . "}", | 
| 3019 |  |  |  |  |  |  | ); | 
| 3020 |  |  |  |  |  |  | } | 
| 3021 |  |  |  |  |  |  |  | 
| 3022 |  |  |  |  |  |  | # append_string( $self, $folder, $text, $flags, $date ) | 
| 3023 |  |  |  |  |  |  | # - conserve memory and use $_[2] to avoid copying $text (it may be huge!) | 
| 3024 |  |  |  |  |  |  | sub append_string($$$;$$) { | 
| 3025 | 0 |  |  | 0 | 1 | 0 | my ( $self, $folder, $flags, $date ) = @_[ 0, 1, 3, 4 ]; | 
| 3026 |  |  |  |  |  |  |  | 
| 3027 |  |  |  |  |  |  | #my $text = $_[2]; # conserve memory and use $_[2] instead! | 
| 3028 | 0 |  |  |  |  | 0 | my $maxl = $self->Maxappendstringlength; | 
| 3029 |  |  |  |  |  |  |  | 
| 3030 |  |  |  |  |  |  | # on "large" strings use append_file to conserve memory | 
| 3031 | 0 | 0 | 0 |  |  | 0 | if ( $_[2] and $maxl and length( $_[2] ) > $maxl ) { | 
|  |  |  | 0 |  |  |  |  | 
| 3032 | 0 |  |  |  |  | 0 | $self->_debug("append_string: using in memory file"); | 
| 3033 | 0 |  |  |  |  | 0 | return $self->append_file( $folder, \( $_[2] ), undef, $flags, $date ); | 
| 3034 |  |  |  |  |  |  | } | 
| 3035 |  |  |  |  |  |  |  | 
| 3036 | 0 | 0 |  |  |  | 0 | my $text = defined( $_[2] ) ? $_[2] : ''; | 
| 3037 |  |  |  |  |  |  |  | 
| 3038 | 0 |  |  |  |  | 0 | $folder = $self->Quote($folder); | 
| 3039 | 0 | 0 |  |  |  | 0 | $flags  = $self->_clean_flags($flags) if ( defined $flags ); | 
| 3040 | 0 | 0 |  |  |  | 0 | $date   = $self->_clean_date($date) if ( defined $date ); | 
| 3041 | 0 |  |  |  |  | 0 | $text =~ s/\r?\n/$CRLF/og; | 
| 3042 |  |  |  |  |  |  |  | 
| 3043 | 0 |  |  |  |  | 0 | my $cmd = $self->_append_command( $folder, $flags, $date, length($text) ); | 
| 3044 | 0 |  |  |  |  | 0 | $cmd .= $CRLF . $text . $CRLF; | 
| 3045 |  |  |  |  |  |  |  | 
| 3046 | 0 | 0 |  |  |  | 0 | $self->_imap_command( { addcrlf => 0 }, $cmd ) or return undef; | 
| 3047 |  |  |  |  |  |  |  | 
| 3048 | 0 |  |  |  |  | 0 | my $data = join '', $self->Results; | 
| 3049 |  |  |  |  |  |  |  | 
| 3050 |  |  |  |  |  |  | # look for something like return size or self if no size found: | 
| 3051 |  |  |  |  |  |  | #  OK [APPENDUID  ] APPEND completed | 
| 3052 | 0 | 0 |  |  |  | 0 | my $ret = $data =~ m#\s+(\d+)\]# ? $1 : $self; | 
| 3053 |  |  |  |  |  |  |  | 
| 3054 | 0 |  |  |  |  | 0 | return $ret; | 
| 3055 |  |  |  |  |  |  | } | 
| 3056 |  |  |  |  |  |  |  | 
| 3057 |  |  |  |  |  |  | # BUG?: not much/any savings on cygwin perl 5.10 when using in memory file | 
| 3058 |  |  |  |  |  |  | # BUG?: we do not retry if sending data fails after getting the OK to send | 
| 3059 |  |  |  |  |  |  | sub append_file { | 
| 3060 | 0 |  |  | 0 | 1 | 0 | my ( $self, $folder, $file, $control, $flags, $date ) = @_; | 
| 3061 |  |  |  |  |  |  |  | 
| 3062 | 0 |  |  |  |  | 0 | my @err; | 
| 3063 | 0 | 0 | 0 |  |  | 0 | push( @err, "folder not specified" ) | 
| 3064 |  |  |  |  |  |  | unless ( defined($folder) and $folder ne "" ); | 
| 3065 |  |  |  |  |  |  |  | 
| 3066 | 0 |  |  |  |  | 0 | my $fh; | 
| 3067 | 0 | 0 | 0 |  |  | 0 | if ( !defined($file) ) { | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 3068 | 0 |  |  |  |  | 0 | push( @err, "file not specified" ); | 
| 3069 |  |  |  |  |  |  | } | 
| 3070 |  |  |  |  |  |  | elsif ( ref($file) and ref($file) ne "SCALAR" ) { | 
| 3071 | 0 |  |  |  |  | 0 | $fh = $file;    # let the caller pass in their own file handle directly | 
| 3072 |  |  |  |  |  |  | } | 
| 3073 |  |  |  |  |  |  | elsif ( !ref($file) and !-f $file ) { | 
| 3074 | 0 |  |  |  |  | 0 | push( @err, "file '$file' not found" ); | 
| 3075 |  |  |  |  |  |  | } | 
| 3076 |  |  |  |  |  |  | else { | 
| 3077 |  |  |  |  |  |  |  | 
| 3078 |  |  |  |  |  |  | # $file can be a name or a scalar reference (for in memory file) | 
| 3079 |  |  |  |  |  |  | # avoid IO::File bug handling scalar refs in perl <= 5.8.8? | 
| 3080 |  |  |  |  |  |  | # - buggy: $fh = IO::File->new( $file, 'r' ) | 
| 3081 | 0 |  |  |  |  | 0 | local ($!); | 
| 3082 | 0 | 0 |  |  |  | 0 | open( $fh, "<", $file ) | 
| 3083 |  |  |  |  |  |  | or push( @err, "Unable to open file '$file': $!" ); | 
| 3084 |  |  |  |  |  |  | } | 
| 3085 |  |  |  |  |  |  |  | 
| 3086 | 0 | 0 |  |  |  | 0 | if (@err) { | 
| 3087 | 0 |  |  |  |  | 0 | $self->LastError( join( ", ", @err ) ); | 
| 3088 | 0 |  |  |  |  | 0 | return undef; | 
| 3089 |  |  |  |  |  |  | } | 
| 3090 |  |  |  |  |  |  |  | 
| 3091 | 0 |  |  |  |  | 0 | binmode($fh); | 
| 3092 |  |  |  |  |  |  |  | 
| 3093 | 0 | 0 |  |  |  | 0 | $folder = $self->Quote($folder)       if ( defined $folder ); | 
| 3094 | 0 | 0 |  |  |  | 0 | $flags  = $self->_clean_flags($flags) if ( defined $flags ); | 
| 3095 |  |  |  |  |  |  |  | 
| 3096 |  |  |  |  |  |  | # allow the date to be specified or even use mtime on file | 
| 3097 | 0 | 0 |  |  |  | 0 | if ($date) { | 
| 3098 | 0 | 0 |  |  |  | 0 | $date = $self->Rfc3501_datetime( ( stat($fh) )[9] ) if ( $date eq "1" ); | 
| 3099 | 0 |  |  |  |  | 0 | $date = $self->_clean_date($date); | 
| 3100 |  |  |  |  |  |  | } | 
| 3101 |  |  |  |  |  |  |  | 
| 3102 |  |  |  |  |  |  | # BUG? seems wasteful to do this always, provide a "fast path" option? | 
| 3103 | 0 |  |  |  |  | 0 | my $length = 0; | 
| 3104 |  |  |  |  |  |  | { | 
| 3105 | 0 |  |  |  |  | 0 | local $/ = "\n";    # just in case global is not default | 
|  | 0 |  |  |  |  | 0 |  | 
| 3106 | 0 |  |  |  |  | 0 | while ( my $line = <$fh> ) {    # do no read the whole file at once! | 
| 3107 | 0 |  |  |  |  | 0 | $line =~ s/\r?\n$/$CRLF/; | 
| 3108 | 0 |  |  |  |  | 0 | $length += length($line); | 
| 3109 |  |  |  |  |  |  | } | 
| 3110 | 0 |  |  |  |  | 0 | seek( $fh, 0, 0 ); | 
| 3111 |  |  |  |  |  |  | } | 
| 3112 |  |  |  |  |  |  |  | 
| 3113 | 0 |  |  |  |  | 0 | my $cmd = $self->_append_command( $folder, $flags, $date, $length ); | 
| 3114 | 0 |  |  |  |  | 0 | my $rc = $self->_imap_command( $cmd, '+' ); | 
| 3115 | 0 | 0 |  |  |  | 0 | unless ($rc) { | 
| 3116 | 0 |  |  |  |  | 0 | $self->LastError( "Error sending '$cmd': " . $self->LastError ); | 
| 3117 | 0 |  |  |  |  | 0 | return undef; | 
| 3118 |  |  |  |  |  |  | } | 
| 3119 |  |  |  |  |  |  |  | 
| 3120 |  |  |  |  |  |  | # Now send the message itself | 
| 3121 | 0 |  |  |  |  | 0 | my ( $buffer, $buflen ) = ( "", 0 ); | 
| 3122 | 0 |  | 0 |  |  | 0 | until ( !$buflen and eof($fh) ) { | 
| 3123 |  |  |  |  |  |  |  | 
| 3124 | 0 | 0 |  |  |  | 0 | if ( $buflen < APPEND_BUFFER_SIZE ) { | 
| 3125 |  |  |  |  |  |  | FILLBUFF: | 
| 3126 | 0 |  |  |  |  | 0 | while ( my $line = <$fh> ) { | 
| 3127 | 0 |  |  |  |  | 0 | $line =~ s/\r?\n$/$CRLF/; | 
| 3128 | 0 |  |  |  |  | 0 | $buffer .= $line; | 
| 3129 | 0 |  |  |  |  | 0 | $buflen = length($buffer); | 
| 3130 | 0 | 0 |  |  |  | 0 | last FILLBUFF if ( $buflen >= APPEND_BUFFER_SIZE ); | 
| 3131 |  |  |  |  |  |  | } | 
| 3132 |  |  |  |  |  |  | } | 
| 3133 |  |  |  |  |  |  |  | 
| 3134 |  |  |  |  |  |  | # exit loop entirely if we are out of data | 
| 3135 | 0 | 0 |  |  |  | 0 | last unless $buflen; | 
| 3136 |  |  |  |  |  |  |  | 
| 3137 |  |  |  |  |  |  | # save anything over desired buffer size for next iteration | 
| 3138 | 0 | 0 |  |  |  | 0 | my $savebuff = | 
| 3139 |  |  |  |  |  |  | ( $buflen > APPEND_BUFFER_SIZE ) | 
| 3140 |  |  |  |  |  |  | ? substr( $buffer, APPEND_BUFFER_SIZE ) | 
| 3141 |  |  |  |  |  |  | : undef; | 
| 3142 |  |  |  |  |  |  |  | 
| 3143 |  |  |  |  |  |  | # reduce buffer to desired size | 
| 3144 | 0 |  |  |  |  | 0 | $buffer = substr( $buffer, 0, APPEND_BUFFER_SIZE ); | 
| 3145 |  |  |  |  |  |  |  | 
| 3146 | 0 |  |  |  |  | 0 | my $bytes_written = $self->_send_bytes( \$buffer ); | 
| 3147 | 0 | 0 |  |  |  | 0 | unless ($bytes_written) { | 
| 3148 | 0 |  |  |  |  | 0 | $self->LastError( "Error appending message: " . $self->LastError ); | 
| 3149 | 0 |  |  |  |  | 0 | return undef; | 
| 3150 |  |  |  |  |  |  | } | 
| 3151 |  |  |  |  |  |  |  | 
| 3152 |  |  |  |  |  |  | # retain any saved data and continue loop | 
| 3153 | 0 | 0 |  |  |  | 0 | $buffer = defined($savebuff) ? $savebuff : ""; | 
| 3154 | 0 |  |  |  |  | 0 | $buflen = length($buffer); | 
| 3155 |  |  |  |  |  |  | } | 
| 3156 |  |  |  |  |  |  |  | 
| 3157 |  |  |  |  |  |  | # finish off append | 
| 3158 | 0 | 0 |  |  |  | 0 | unless ( $self->_send_bytes( \$CRLF ) ) { | 
| 3159 | 0 |  |  |  |  | 0 | $self->LastError( "Error appending CRLF: " . $self->LastError ); | 
| 3160 | 0 |  |  |  |  | 0 | return undef; | 
| 3161 |  |  |  |  |  |  | } | 
| 3162 |  |  |  |  |  |  |  | 
| 3163 |  |  |  |  |  |  | # Now for the crucial test: Did the append work or not? | 
| 3164 |  |  |  |  |  |  | # look for " (OK|BAD|NO)" | 
| 3165 | 0 | 0 |  |  |  | 0 | my $code = $self->_get_response( $self->Count ) or return undef; | 
| 3166 |  |  |  |  |  |  |  | 
| 3167 | 0 | 0 |  |  |  | 0 | if ( $code eq 'OK' ) { | 
| 3168 | 0 |  |  |  |  | 0 | my $data = join '', $self->Results; | 
| 3169 |  |  |  |  |  |  |  | 
| 3170 |  |  |  |  |  |  | # look for something like return size or self if no size found: | 
| 3171 |  |  |  |  |  |  | #  OK [APPENDUID  ] APPEND completed | 
| 3172 | 0 | 0 |  |  |  | 0 | my $ret = $data =~ m#\s+(\d+)\]# ? $1 : $self; | 
| 3173 |  |  |  |  |  |  |  | 
| 3174 | 0 |  |  |  |  | 0 | return $ret; | 
| 3175 |  |  |  |  |  |  | } | 
| 3176 |  |  |  |  |  |  | else { | 
| 3177 | 0 |  |  |  |  | 0 | return undef; | 
| 3178 |  |  |  |  |  |  | } | 
| 3179 |  |  |  |  |  |  | } | 
| 3180 |  |  |  |  |  |  |  | 
| 3181 |  |  |  |  |  |  | # BUG? we should retry if "socket closed while..." but do not currently | 
| 3182 |  |  |  |  |  |  | sub authenticate { | 
| 3183 | 0 |  |  | 0 | 1 | 0 | my ( $self, $scheme, $response ) = @_; | 
| 3184 | 0 |  | 0 |  |  | 0 | $scheme   ||= $self->Authmechanism; | 
| 3185 | 0 |  | 0 |  |  | 0 | $response ||= $self->Authcallback; | 
| 3186 | 0 |  |  |  |  | 0 | my $clear = $self->Clear; | 
| 3187 | 0 | 0 | 0 |  |  | 0 | $self->Clear($clear) | 
| 3188 |  |  |  |  |  |  | if $self->Count >= $clear && $clear > 0; | 
| 3189 |  |  |  |  |  |  |  | 
| 3190 | 0 | 0 |  |  |  | 0 | if ( !$scheme ) { | 
|  |  | 0 |  |  |  |  |  | 
| 3191 | 0 |  |  |  |  | 0 | $self->LastError("Authmechanism not set"); | 
| 3192 | 0 |  |  |  |  | 0 | return undef; | 
| 3193 |  |  |  |  |  |  | } | 
| 3194 |  |  |  |  |  |  | elsif ( $scheme eq 'LOGIN' ) { | 
| 3195 | 0 |  |  |  |  | 0 | $self->LastError("Authmechanism LOGIN is invalid, use login()"); | 
| 3196 | 0 |  |  |  |  | 0 | return undef; | 
| 3197 |  |  |  |  |  |  | } | 
| 3198 |  |  |  |  |  |  |  | 
| 3199 | 0 |  |  |  |  | 0 | my $string = "AUTHENTICATE $scheme"; | 
| 3200 |  |  |  |  |  |  |  | 
| 3201 |  |  |  |  |  |  | # use _imap_command for retry mechanism... | 
| 3202 | 0 | 0 |  |  |  | 0 | $self->_imap_command( $string, '+' ) or return undef; | 
| 3203 |  |  |  |  |  |  |  | 
| 3204 | 0 |  |  |  |  | 0 | my $count = $self->Count; | 
| 3205 | 0 |  |  |  |  | 0 | my $code; | 
| 3206 |  |  |  |  |  |  |  | 
| 3207 |  |  |  |  |  |  | # look for "+ " or just "+" | 
| 3208 | 0 |  |  |  |  | 0 | foreach my $line ( $self->Results ) { | 
| 3209 | 0 | 0 |  |  |  | 0 | if ( $line =~ /^\+\s*(.*?)\s*$/ ) { | 
| 3210 | 0 |  |  |  |  | 0 | $code = $1; | 
| 3211 | 0 |  |  |  |  | 0 | last; | 
| 3212 |  |  |  |  |  |  | } | 
| 3213 |  |  |  |  |  |  | } | 
| 3214 |  |  |  |  |  |  |  | 
| 3215 |  |  |  |  |  |  | # BUG? use _load_module for these too? | 
| 3216 | 0 | 0 |  |  |  | 0 | if ( $scheme eq 'CRAM-MD5' ) { | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 3217 |  |  |  |  |  |  | $response ||= sub { | 
| 3218 | 0 |  |  | 0 |  | 0 | my ( $code, $client ) = @_; | 
| 3219 | 0 |  |  |  |  | 0 | require Digest::HMAC_MD5; | 
| 3220 | 0 |  |  |  |  | 0 | my $hmac = | 
| 3221 |  |  |  |  |  |  | Digest::HMAC_MD5::hmac_md5_hex( decode_base64($code), | 
| 3222 |  |  |  |  |  |  | $client->Password ); | 
| 3223 | 0 |  |  |  |  | 0 | encode_base64( $client->User . " " . $hmac, '' ); | 
| 3224 | 0 |  | 0 |  |  | 0 | }; | 
| 3225 |  |  |  |  |  |  | } | 
| 3226 |  |  |  |  |  |  | elsif ( $scheme eq 'DIGEST-MD5' ) { | 
| 3227 |  |  |  |  |  |  | $response ||= sub { | 
| 3228 | 0 |  |  | 0 |  | 0 | my ( $code, $client ) = @_; | 
| 3229 | 0 |  |  |  |  | 0 | require Authen::SASL; | 
| 3230 | 0 |  |  |  |  | 0 | require Digest::MD5; | 
| 3231 |  |  |  |  |  |  |  | 
| 3232 | 0 | 0 |  |  |  | 0 | my $authname = | 
| 3233 |  |  |  |  |  |  | defined $client->Authuser ? $client->Authuser : $client->User; | 
| 3234 |  |  |  |  |  |  |  | 
| 3235 | 0 |  |  |  |  | 0 | my $sasl = Authen::SASL->new( | 
| 3236 |  |  |  |  |  |  | mechanism => 'DIGEST-MD5', | 
| 3237 |  |  |  |  |  |  | callback  => { | 
| 3238 |  |  |  |  |  |  | user     => $client->User, | 
| 3239 |  |  |  |  |  |  | pass     => $client->Password, | 
| 3240 |  |  |  |  |  |  | authname => $authname | 
| 3241 |  |  |  |  |  |  | } | 
| 3242 |  |  |  |  |  |  | ); | 
| 3243 |  |  |  |  |  |  |  | 
| 3244 |  |  |  |  |  |  | # client_new is an empty function for DIGEST-MD5 | 
| 3245 | 0 |  |  |  |  | 0 | my $conn = $sasl->client_new( 'imap', 'localhost', '' ); | 
| 3246 | 0 |  |  |  |  | 0 | my $answer = $conn->client_step( decode_base64 $code); | 
| 3247 |  |  |  |  |  |  |  | 
| 3248 | 0 | 0 |  |  |  | 0 | encode_base64( $answer, '' ) | 
| 3249 |  |  |  |  |  |  | if defined $answer; | 
| 3250 | 0 |  | 0 |  |  | 0 | }; | 
| 3251 |  |  |  |  |  |  | } | 
| 3252 |  |  |  |  |  |  | elsif ( $scheme eq 'PLAIN' ) {    # PLAIN SASL | 
| 3253 |  |  |  |  |  |  | $response ||= sub { | 
| 3254 | 0 |  |  | 0 |  | 0 | my ( $code, $client ) = @_; | 
| 3255 | 0 | 0 |  |  |  | 0 | encode_base64(            # [authname] user password | 
|  |  | 0 |  |  |  |  |  | 
| 3256 |  |  |  |  |  |  | join( | 
| 3257 |  |  |  |  |  |  | chr(0), | 
| 3258 |  |  |  |  |  |  | defined $client->Proxy | 
| 3259 |  |  |  |  |  |  | ? ( $client->User, $client->Proxy ) | 
| 3260 |  |  |  |  |  |  | : ( "", $client->User ), | 
| 3261 |  |  |  |  |  |  | defined $client->Password ? $client->Password : "", | 
| 3262 |  |  |  |  |  |  | ), | 
| 3263 |  |  |  |  |  |  | '' | 
| 3264 |  |  |  |  |  |  | ); | 
| 3265 | 0 |  | 0 |  |  | 0 | }; | 
| 3266 |  |  |  |  |  |  | } | 
| 3267 |  |  |  |  |  |  | elsif ( $scheme eq 'NTLM' ) { | 
| 3268 |  |  |  |  |  |  | $response ||= sub { | 
| 3269 | 0 |  |  | 0 |  | 0 | my ( $code, $client ) = @_; | 
| 3270 |  |  |  |  |  |  |  | 
| 3271 | 0 |  |  |  |  | 0 | require Authen::NTLM; | 
| 3272 | 0 |  |  |  |  | 0 | Authen::NTLM::ntlm_user( $client->User ); | 
| 3273 | 0 |  |  |  |  | 0 | Authen::NTLM::ntlm_password( $client->Password ); | 
| 3274 | 0 | 0 |  |  |  | 0 | Authen::NTLM::ntlm_domain( $client->Domain ) if $client->Domain; | 
| 3275 | 0 |  |  |  |  | 0 | Authen::NTLM::ntlm($code); | 
| 3276 | 0 |  | 0 |  |  | 0 | }; | 
| 3277 |  |  |  |  |  |  | } | 
| 3278 |  |  |  |  |  |  |  | 
| 3279 | 0 |  |  |  |  | 0 | my $resp = $response->( $code, $self ); | 
| 3280 | 0 | 0 |  |  |  | 0 | unless ( defined($resp) ) { | 
| 3281 | 0 |  |  |  |  | 0 | $self->LastError( "Error getting $scheme data: " . $self->LastError ); | 
| 3282 | 0 |  |  |  |  | 0 | return undef; | 
| 3283 |  |  |  |  |  |  | } | 
| 3284 | 0 | 0 |  |  |  | 0 | unless ( $self->_send_line($resp) ) { | 
| 3285 | 0 |  |  |  |  | 0 | $self->LastError( "Error sending $scheme data: " . $self->LastError ); | 
| 3286 | 0 |  |  |  |  | 0 | return undef; | 
| 3287 |  |  |  |  |  |  | } | 
| 3288 |  |  |  |  |  |  |  | 
| 3289 |  |  |  |  |  |  | # this code may be a little too custom to try and use _get_response() | 
| 3290 |  |  |  |  |  |  | # look for "+ " (not just "+") otherwise " (OK|BAD|NO)" | 
| 3291 | 0 |  |  |  |  | 0 | undef $code; | 
| 3292 | 0 |  |  |  |  | 0 | until ($code) { | 
| 3293 | 0 | 0 |  |  |  | 0 | my $output = $self->_read_line or return undef; | 
| 3294 | 0 |  |  |  |  | 0 | foreach my $o (@$output) { | 
| 3295 | 0 |  |  |  |  | 0 | $self->_record( $count, $o ); | 
| 3296 | 0 | 0 |  |  |  | 0 | $code = $o->[DATA] =~ /^\+\s+(.*?)\s*$/ ? $1 : undef; | 
| 3297 |  |  |  |  |  |  |  | 
| 3298 | 0 | 0 |  |  |  | 0 | if ($code) { | 
| 3299 | 0 | 0 |  |  |  | 0 | unless ( $self->_send_line( $response->( $code, $self ) ) ) { | 
| 3300 | 0 |  |  |  |  | 0 | $self->LastError( | 
| 3301 |  |  |  |  |  |  | "Error sending $scheme data: " . $self->LastError ); | 
| 3302 | 0 |  |  |  |  | 0 | return undef; | 
| 3303 |  |  |  |  |  |  | } | 
| 3304 | 0 |  |  |  |  | 0 | undef $code;    # clear code as we are not finished yet | 
| 3305 |  |  |  |  |  |  | } | 
| 3306 |  |  |  |  |  |  |  | 
| 3307 | 0 | 0 |  |  |  | 0 | if ( $o->[DATA] =~ /^$count\s+(OK|NO|BAD)\b/i ) { | 
|  |  | 0 |  |  |  |  |  | 
| 3308 | 0 |  |  |  |  | 0 | $code = uc($1); | 
| 3309 | 0 | 0 |  |  |  | 0 | $self->LastError( $o->[DATA] ) unless ( $code eq 'OK' ); | 
| 3310 |  |  |  |  |  |  | } | 
| 3311 |  |  |  |  |  |  | elsif ( $o->[DATA] =~ /^\*\s+BYE/ ) { | 
| 3312 | 0 |  |  |  |  | 0 | $self->State(Unconnected); | 
| 3313 | 0 |  |  |  |  | 0 | $self->LastError( $o->[DATA] ); | 
| 3314 | 0 |  |  |  |  | 0 | return undef; | 
| 3315 |  |  |  |  |  |  | } | 
| 3316 |  |  |  |  |  |  | } | 
| 3317 |  |  |  |  |  |  | } | 
| 3318 |  |  |  |  |  |  |  | 
| 3319 | 0 | 0 |  |  |  | 0 | return undef unless $code eq 'OK'; | 
| 3320 |  |  |  |  |  |  |  | 
| 3321 | 0 | 0 |  |  |  | 0 | Authen::NTLM::ntlm_reset() | 
| 3322 |  |  |  |  |  |  | if $scheme eq 'NTLM'; | 
| 3323 |  |  |  |  |  |  |  | 
| 3324 | 0 |  |  |  |  | 0 | $self->State(Authenticated); | 
| 3325 | 0 |  |  |  |  | 0 | return $self; | 
| 3326 |  |  |  |  |  |  | } | 
| 3327 |  |  |  |  |  |  |  | 
| 3328 |  |  |  |  |  |  | # UIDPLUS response from a copy: [COPYUID (uidvalidity) (origuid) (newuid)] | 
| 3329 |  |  |  |  |  |  | sub copy { | 
| 3330 | 0 |  |  | 0 | 1 | 0 | my ( $self, $target, @msgs ) = @_; | 
| 3331 |  |  |  |  |  |  |  | 
| 3332 |  |  |  |  |  |  | my $msgs = | 
| 3333 |  |  |  |  |  |  | $self->Ranges | 
| 3334 |  |  |  |  |  |  | ? $self->Range(@msgs) | 
| 3335 | 0 | 0 |  |  |  | 0 | : join ',', map { ref $_ ? @$_ : $_ } @msgs; | 
|  | 0 | 0 |  |  |  | 0 |  | 
| 3336 |  |  |  |  |  |  |  | 
| 3337 | 0 | 0 |  |  |  | 0 | $self->_imap_uid_command( COPY => $msgs, $self->Quote($target) ) | 
| 3338 |  |  |  |  |  |  | or return undef; | 
| 3339 |  |  |  |  |  |  |  | 
| 3340 | 0 |  |  |  |  | 0 | my @results = $self->History; | 
| 3341 |  |  |  |  |  |  |  | 
| 3342 | 0 |  |  |  |  | 0 | my @uids; | 
| 3343 | 0 |  |  |  |  | 0 | foreach (@results) { | 
| 3344 | 0 |  |  |  |  | 0 | chomp; | 
| 3345 | 0 |  |  |  |  | 0 | s/$CR?$LF$//o; | 
| 3346 | 0 | 0 |  |  |  | 0 | s/^.*\[COPYUID\s+\d+\s+[\d:,]+\s+([\d:,]+)\].*/$1/ or next; | 
| 3347 | 0 | 0 |  |  |  | 0 | push @uids, /(\d+):(\d+)/ ? ( $1 ... $2 ) : ( split /\,/ ); | 
| 3348 |  |  |  |  |  |  |  | 
| 3349 |  |  |  |  |  |  | } | 
| 3350 | 0 | 0 |  |  |  | 0 | return @uids ? join( ",", @uids ) : $self; | 
| 3351 |  |  |  |  |  |  | } | 
| 3352 |  |  |  |  |  |  |  | 
| 3353 |  |  |  |  |  |  | sub move { | 
| 3354 | 0 |  |  | 0 | 1 | 0 | my ( $self, $target, @msgs ) = @_; | 
| 3355 |  |  |  |  |  |  |  | 
| 3356 | 0 | 0 | 0 |  |  | 0 | $self->exists($target) | 
| 3357 |  |  |  |  |  |  | or $self->create($target) && $self->subscribe($target); | 
| 3358 |  |  |  |  |  |  |  | 
| 3359 |  |  |  |  |  |  | my $uids = | 
| 3360 | 0 | 0 |  |  |  | 0 | $self->copy( $target, map { ref $_ eq 'ARRAY' ? @$_ : $_ } @msgs ) | 
|  | 0 | 0 |  |  |  | 0 |  | 
| 3361 |  |  |  |  |  |  | or return undef; | 
| 3362 |  |  |  |  |  |  |  | 
| 3363 | 0 | 0 |  |  |  | 0 | unless ( $self->delete_message(@msgs) ) { | 
| 3364 | 0 |  |  |  |  | 0 | local ($!);    # old versions of Carp could reset $! | 
| 3365 | 0 |  |  |  |  | 0 | carp $self->LastError; | 
| 3366 |  |  |  |  |  |  | } | 
| 3367 |  |  |  |  |  |  |  | 
| 3368 | 0 |  |  |  |  | 0 | return $uids; | 
| 3369 |  |  |  |  |  |  | } | 
| 3370 |  |  |  |  |  |  |  | 
| 3371 |  |  |  |  |  |  | sub set_flag { | 
| 3372 | 0 |  |  | 0 | 1 | 0 | my ( $self, $flag, @msgs ) = @_; | 
| 3373 | 0 | 0 |  |  |  | 0 | @msgs = @{ $msgs[0] } if ref $msgs[0] eq 'ARRAY'; | 
|  | 0 |  |  |  |  | 0 |  | 
| 3374 | 0 | 0 |  |  |  | 0 | $flag = "\\$flag" | 
| 3375 |  |  |  |  |  |  | if $flag =~ /^(?:Answered|Flagged|Deleted|Seen|Draft)$/i; | 
| 3376 |  |  |  |  |  |  |  | 
| 3377 | 0 | 0 |  |  |  | 0 | my $which = $self->Ranges ? $self->Range(@msgs) : join( ',', @msgs ); | 
| 3378 | 0 |  |  |  |  | 0 | return $self->store( $which, '+FLAGS.SILENT', "($flag)" ); | 
| 3379 |  |  |  |  |  |  | } | 
| 3380 |  |  |  |  |  |  |  | 
| 3381 |  |  |  |  |  |  | sub see { | 
| 3382 | 0 |  |  | 0 | 1 | 0 | my ( $self, @msgs ) = @_; | 
| 3383 | 0 | 0 |  |  |  | 0 | @msgs = @{ $msgs[0] } if ref $msgs[0] eq 'ARRAY'; | 
|  | 0 |  |  |  |  | 0 |  | 
| 3384 | 0 |  |  |  |  | 0 | return $self->set_flag( '\\Seen', @msgs ); | 
| 3385 |  |  |  |  |  |  | } | 
| 3386 |  |  |  |  |  |  |  | 
| 3387 |  |  |  |  |  |  | sub mark { | 
| 3388 | 0 |  |  | 0 | 1 | 0 | my ( $self, @msgs ) = @_; | 
| 3389 | 0 | 0 |  |  |  | 0 | @msgs = @{ $msgs[0] } if ref $msgs[0] eq 'ARRAY'; | 
|  | 0 |  |  |  |  | 0 |  | 
| 3390 | 0 |  |  |  |  | 0 | return $self->set_flag( '\\Flagged', @msgs ); | 
| 3391 |  |  |  |  |  |  | } | 
| 3392 |  |  |  |  |  |  |  | 
| 3393 |  |  |  |  |  |  | sub unmark { | 
| 3394 | 0 |  |  | 0 | 1 | 0 | my ( $self, @msgs ) = @_; | 
| 3395 | 0 | 0 |  |  |  | 0 | @msgs = @{ $msgs[0] } if ref $msgs[0] eq 'ARRAY'; | 
|  | 0 |  |  |  |  | 0 |  | 
| 3396 | 0 |  |  |  |  | 0 | return $self->unset_flag( '\\Flagged', @msgs ); | 
| 3397 |  |  |  |  |  |  | } | 
| 3398 |  |  |  |  |  |  |  | 
| 3399 |  |  |  |  |  |  | sub unset_flag { | 
| 3400 | 0 |  |  | 0 | 1 | 0 | my ( $self, $flag, @msgs ) = @_; | 
| 3401 | 0 | 0 |  |  |  | 0 | @msgs = @{ $msgs[0] } if ref $msgs[0] eq 'ARRAY'; | 
|  | 0 |  |  |  |  | 0 |  | 
| 3402 |  |  |  |  |  |  |  | 
| 3403 | 0 | 0 |  |  |  | 0 | $flag = "\\$flag" | 
| 3404 |  |  |  |  |  |  | if $flag =~ /^(?:Answered|Flagged|Deleted|Seen|Draft)$/i; | 
| 3405 |  |  |  |  |  |  |  | 
| 3406 | 0 |  |  |  |  | 0 | return $self->store( join( ",", @msgs ), "-FLAGS.SILENT ($flag)" ); | 
| 3407 |  |  |  |  |  |  | } | 
| 3408 |  |  |  |  |  |  |  | 
| 3409 |  |  |  |  |  |  | sub deny_seeing { | 
| 3410 | 0 |  |  | 0 | 1 | 0 | my ( $self, @msgs ) = @_; | 
| 3411 | 0 | 0 |  |  |  | 0 | @msgs = @{ $msgs[0] } if ref $msgs[0] eq 'ARRAY'; | 
|  | 0 |  |  |  |  | 0 |  | 
| 3412 | 0 |  |  |  |  | 0 | return $self->unset_flag( '\\Seen', @msgs ); | 
| 3413 |  |  |  |  |  |  | } | 
| 3414 |  |  |  |  |  |  |  | 
| 3415 |  |  |  |  |  |  | sub size { | 
| 3416 | 0 |  |  | 0 | 1 | 0 | my ( $self, $msg ) = @_; | 
| 3417 | 0 | 0 |  |  |  | 0 | my $data = $self->fetch( $msg, "(RFC822.SIZE)" ) or return undef; | 
| 3418 |  |  |  |  |  |  |  | 
| 3419 |  |  |  |  |  |  | # beware of response like: * NO Cannot open message $msg | 
| 3420 | 0 |  |  |  |  | 0 | my $cmd = shift @$data; | 
| 3421 | 0 |  |  |  |  | 0 | my $err; | 
| 3422 | 0 |  |  |  |  | 0 | foreach my $line (@$data) { | 
| 3423 | 0 | 0 |  |  |  | 0 | return $1 if ( $line =~ /RFC822\.SIZE\s+(\d+)/ ); | 
| 3424 | 0 | 0 |  |  |  | 0 | $err = $line if ( $line =~ /\* NO\b/ ); | 
| 3425 |  |  |  |  |  |  | } | 
| 3426 |  |  |  |  |  |  |  | 
| 3427 | 0 | 0 |  |  |  | 0 | if ($err) { | 
|  |  | 0 |  |  |  |  |  | 
| 3428 | 0 |  |  |  |  | 0 | my $info = "$err was returned for $cmd"; | 
| 3429 | 0 |  |  |  |  | 0 | $info =~ s/$CR?$LF//og; | 
| 3430 | 0 |  |  |  |  | 0 | $self->LastError($info); | 
| 3431 |  |  |  |  |  |  | } | 
| 3432 |  |  |  |  |  |  | elsif ( !$self->LastError ) { | 
| 3433 | 0 |  |  |  |  | 0 | my $info = "no RFC822.SIZE found in: " . join( " ", @$data ); | 
| 3434 | 0 |  |  |  |  | 0 | $self->LastError($info); | 
| 3435 |  |  |  |  |  |  | } | 
| 3436 | 0 |  |  |  |  | 0 | return undef; | 
| 3437 |  |  |  |  |  |  | } | 
| 3438 |  |  |  |  |  |  |  | 
| 3439 |  |  |  |  |  |  | sub getquotaroot { | 
| 3440 | 0 |  |  | 0 | 1 | 0 | my ( $self, $what ) = @_; | 
| 3441 | 0 | 0 |  |  |  | 0 | my $who = defined $what ? $self->Quote($what) : "INBOX"; | 
| 3442 | 0 | 0 |  |  |  | 0 | return $self->_imap_command("GETQUOTAROOT $who") ? $self->Results : undef; | 
| 3443 |  |  |  |  |  |  | } | 
| 3444 |  |  |  |  |  |  |  | 
| 3445 |  |  |  |  |  |  | # BUG? using user/$User here and INBOX in quota/quota_usage | 
| 3446 |  |  |  |  |  |  | sub getquota { | 
| 3447 | 0 |  |  | 0 | 1 | 0 | my ( $self, $what ) = @_; | 
| 3448 | 0 | 0 |  |  |  | 0 | my $who = defined $what ? $self->Quote($what) : "user/" . $self->User; | 
| 3449 | 0 | 0 |  |  |  | 0 | return $self->_imap_command("GETQUOTA $who") ? $self->Results : undef; | 
| 3450 |  |  |  |  |  |  | } | 
| 3451 |  |  |  |  |  |  |  | 
| 3452 |  |  |  |  |  |  | # usage: $self->setquota($quotaroot, storage => 512, ...) | 
| 3453 |  |  |  |  |  |  | sub setquota(@) { | 
| 3454 | 0 |  |  | 0 | 1 | 0 | my ( $self, $what ) = ( shift, shift ); | 
| 3455 | 0 | 0 |  |  |  | 0 | my $who = defined $what ? $self->Quote($what) : "user/" . $self->User; | 
| 3456 | 0 |  |  |  |  | 0 | my @limits; | 
| 3457 | 0 |  |  |  |  | 0 | while (@_) { | 
| 3458 | 0 |  |  |  |  | 0 | my ( $k, $v ) = ( $self->Quote( uc( shift @_ ) ), shift @_ ); | 
| 3459 | 0 |  |  |  |  | 0 | push( @limits, "($k $v)" ); | 
| 3460 |  |  |  |  |  |  | } | 
| 3461 | 0 |  |  |  |  | 0 | my $limits = join( ' ', @limits ); | 
| 3462 | 0 | 0 |  |  |  | 0 | $self->_imap_command("SETQUOTA $who $limits") ? $self->Results : undef; | 
| 3463 |  |  |  |  |  |  | } | 
| 3464 |  |  |  |  |  |  |  | 
| 3465 |  |  |  |  |  |  | sub quota { | 
| 3466 | 0 |  | 0 | 0 | 1 | 0 | my ( $self, $what ) = ( shift, shift || "INBOX" ); | 
| 3467 | 0 | 0 |  |  |  | 0 | my $tref = $self->getquota($what) or return undef; | 
| 3468 | 0 |  |  |  |  | 0 | shift @$tref;    # pop off command | 
| 3469 | 0 | 0 |  |  |  | 0 | return ( map { /.*STORAGE\s+\d+\s+(\d+).*\n$/ ? $1 : () } @$tref )[0]; | 
|  | 0 |  |  |  |  | 0 |  | 
| 3470 |  |  |  |  |  |  | } | 
| 3471 |  |  |  |  |  |  |  | 
| 3472 |  |  |  |  |  |  | sub quota_usage { | 
| 3473 | 0 |  | 0 | 0 | 1 | 0 | my ( $self, $what ) = ( shift, shift || "INBOX" ); | 
| 3474 | 0 | 0 |  |  |  | 0 | my $tref = $self->getquota($what) or return undef; | 
| 3475 | 0 |  |  |  |  | 0 | shift @$tref;    # pop off command | 
| 3476 | 0 | 0 |  |  |  | 0 | return ( map { /.*STORAGE\s+(\d+)\s+\d+.*\n$/ ? $1 : () } @$tref )[0]; | 
|  | 0 |  |  |  |  | 0 |  | 
| 3477 |  |  |  |  |  |  | } | 
| 3478 |  |  |  |  |  |  |  | 
| 3479 |  |  |  |  |  |  | # rfc3501: | 
| 3480 |  |  |  |  |  |  | #   atom-specials   = "(" / ")" / "{" / SP / CTL / list-wildcards / | 
| 3481 |  |  |  |  |  |  | #                  quoted-specials / resp-specials | 
| 3482 |  |  |  |  |  |  | #   list-wildcards  = "%" / "*" | 
| 3483 |  |  |  |  |  |  | #   quoted-specials = DQUOTE / "\" | 
| 3484 |  |  |  |  |  |  | #   resp-specials   = "]" | 
| 3485 |  |  |  |  |  |  | # rfc2060: | 
| 3486 |  |  |  |  |  |  | #   CTL ::= | 
| 3487 |  |  |  |  |  |  | # Paranoia/safety: | 
| 3488 |  |  |  |  |  |  | #   encode strings with "}" / "[" / "]" / non-ascii chars | 
| 3489 |  |  |  |  |  |  | sub Quote($;$) { | 
| 3490 | 0 |  |  | 0 | 1 | 0 | my ( $self, $name, $force ) = @_; | 
| 3491 | 0 | 0 | 0 |  |  | 0 | if ( $force or $name =~ /["\\[:^ascii:][:cntrl:]]/s ) { | 
|  |  | 0 | 0 |  |  |  |  | 
| 3492 | 0 |  |  |  |  | 0 | return "{" . length($name) . "}" . $CRLF . $name; | 
| 3493 |  |  |  |  |  |  | } | 
| 3494 |  |  |  |  |  |  | elsif ( $name =~ /[(){}\s%*\[\]]/s or $name eq "" ) { | 
| 3495 | 0 |  |  |  |  | 0 | return qq("$name"); | 
| 3496 |  |  |  |  |  |  | } | 
| 3497 |  |  |  |  |  |  | else { | 
| 3498 | 0 |  |  |  |  | 0 | return $name; | 
| 3499 |  |  |  |  |  |  | } | 
| 3500 |  |  |  |  |  |  | } | 
| 3501 |  |  |  |  |  |  |  | 
| 3502 |  |  |  |  |  |  | # legacy behavior: strip double quote around folder name args! | 
| 3503 |  |  |  |  |  |  | sub Massage($;$) { | 
| 3504 | 0 |  |  | 0 | 1 | 0 | my ( $self, $name, $notFolder ) = @_; | 
| 3505 | 0 | 0 |  |  |  | 0 | $name =~ s/^\"(.*)\"$/$1/s unless $notFolder; | 
| 3506 | 0 |  |  |  |  | 0 | return $self->Quote($name); | 
| 3507 |  |  |  |  |  |  | } | 
| 3508 |  |  |  |  |  |  |  | 
| 3509 |  |  |  |  |  |  | sub unseen_count { | 
| 3510 | 0 |  |  | 0 | 1 | 0 | my ( $self, $folder ) = ( shift, shift ); | 
| 3511 | 0 |  | 0 |  |  | 0 | $folder ||= $self->Folder; | 
| 3512 | 0 | 0 |  |  |  | 0 | $self->status( $folder, 'UNSEEN' ) or return undef; | 
| 3513 |  |  |  |  |  |  |  | 
| 3514 |  |  |  |  |  |  | my $r = | 
| 3515 | 0 |  |  | 0 |  | 0 | first { s/\*\s+STATUS\s+.*\(UNSEEN\s+(\d+)\s*\)/$1/ } $self->History; | 
|  | 0 |  |  |  |  | 0 |  | 
| 3516 |  |  |  |  |  |  |  | 
| 3517 | 0 |  |  |  |  | 0 | $r =~ s/\D//g; | 
| 3518 | 0 |  |  |  |  | 0 | return $r; | 
| 3519 |  |  |  |  |  |  | } | 
| 3520 |  |  |  |  |  |  |  | 
| 3521 |  |  |  |  |  |  | sub State($) { | 
| 3522 | 0 |  |  | 0 | 1 | 0 | my ( $self, $state ) = @_; | 
| 3523 |  |  |  |  |  |  |  | 
| 3524 | 0 | 0 |  |  |  | 0 | if ( defined $state ) { | 
| 3525 | 0 |  |  |  |  | 0 | $self->{State} = $state; | 
| 3526 |  |  |  |  |  |  |  | 
| 3527 |  |  |  |  |  |  | # discard cached capability info after authentication | 
| 3528 | 0 | 0 |  |  |  | 0 | delete $self->{CAPABILITY} if ( $state == Authenticated ); | 
| 3529 |  |  |  |  |  |  | } | 
| 3530 |  |  |  |  |  |  |  | 
| 3531 | 0 | 0 |  |  |  | 0 | return defined( $self->{State} ) ? $self->{State} : Unconnected; | 
| 3532 |  |  |  |  |  |  | } | 
| 3533 |  |  |  |  |  |  |  | 
| 3534 | 0 |  |  | 0 | 1 | 0 | sub Status          { shift->State } | 
| 3535 | 0 |  |  | 0 | 1 | 0 | sub IsUnconnected   { shift->State == Unconnected } | 
| 3536 | 0 |  |  | 0 | 1 | 0 | sub IsConnected     { shift->State >= Connected } | 
| 3537 | 0 |  |  | 0 | 1 | 0 | sub IsAuthenticated { shift->State >= Authenticated } | 
| 3538 | 0 |  |  | 0 | 1 | 0 | sub IsSelected      { shift->State == Selected } | 
| 3539 |  |  |  |  |  |  |  | 
| 3540 |  |  |  |  |  |  | # The following private methods all work on an output line array. | 
| 3541 |  |  |  |  |  |  | # _data returns the data portion of an output array: | 
| 3542 | 0 | 0 | 0 | 0 |  | 0 | sub _data { ref $_[1] && defined $_[1]->[TYPE] ? $_[1]->[DATA] : undef } | 
| 3543 |  |  |  |  |  |  |  | 
| 3544 |  |  |  |  |  |  | # _index returns the index portion of an output array: | 
| 3545 | 0 | 0 | 0 | 0 |  | 0 | sub _index { ref $_[1] && defined $_[1]->[TYPE] ? $_[1]->[INDEX] : undef } | 
| 3546 |  |  |  |  |  |  |  | 
| 3547 |  |  |  |  |  |  | # _type returns the type portion of an output array: | 
| 3548 | 0 | 0 |  | 0 |  | 0 | sub _type { ref $_[1] && $_[1]->[TYPE] } | 
| 3549 |  |  |  |  |  |  |  | 
| 3550 |  |  |  |  |  |  | # _is_literal returns true if this is a literal: | 
| 3551 | 11 | 0 | 33 | 11 |  | 25 | sub _is_literal { ref $_[1] && $_[1]->[TYPE] && $_[1]->[TYPE] eq 'LITERAL' } | 
| 3552 |  |  |  |  |  |  |  | 
| 3553 |  |  |  |  |  |  | # _is_output_or_literal returns true if this is an | 
| 3554 |  |  |  |  |  |  | #      output line (or the literal part of one): | 
| 3555 |  |  |  |  |  |  |  | 
| 3556 |  |  |  |  |  |  | sub _is_output_or_literal { | 
| 3557 | 0 | 0 | 0 | 0 |  |  | ref $_[1] | 
|  |  |  | 0 |  |  |  |  | 
| 3558 |  |  |  |  |  |  | && defined $_[1]->[TYPE] | 
| 3559 |  |  |  |  |  |  | && ( $_[1]->[TYPE] eq "OUTPUT" || $_[1]->[TYPE] eq "LITERAL" ); | 
| 3560 |  |  |  |  |  |  | } | 
| 3561 |  |  |  |  |  |  |  | 
| 3562 |  |  |  |  |  |  | # _is_output returns true if this is an output line: | 
| 3563 | 0 | 0 | 0 | 0 |  |  | sub _is_output { ref $_[1] && $_[1]->[TYPE] && $_[1]->[TYPE] eq "OUTPUT" } | 
| 3564 |  |  |  |  |  |  |  | 
| 3565 |  |  |  |  |  |  | # _is_input returns true if this is an input line: | 
| 3566 | 0 | 0 | 0 | 0 |  |  | sub _is_input { ref $_[1] && $_[1]->[TYPE] && $_[1]->[TYPE] eq "INPUT" } | 
| 3567 |  |  |  |  |  |  |  | 
| 3568 |  |  |  |  |  |  | # _next_index returns next_index for a transaction; may legitimately | 
| 3569 |  |  |  |  |  |  | # return 0 when successful. | 
| 3570 | 0 |  |  | 0 |  |  | sub _next_index { my $r = $_[0]->_transaction( $_[1] ); $r } | 
|  | 0 |  |  |  |  |  |  | 
| 3571 |  |  |  |  |  |  |  | 
| 3572 |  |  |  |  |  |  | sub Range { | 
| 3573 | 0 |  |  | 0 | 1 |  | my ( $self, $targ ) = ( shift, shift ); | 
| 3574 |  |  |  |  |  |  |  | 
| 3575 | 0 | 0 |  |  |  |  | UNIVERSAL::isa( $targ, 'Mail::IMAPClient::MessageSet' ) | 
| 3576 |  |  |  |  |  |  | ? $targ->cat(@_) | 
| 3577 |  |  |  |  |  |  | : Mail::IMAPClient::MessageSet->new( $targ, @_ ); | 
| 3578 |  |  |  |  |  |  | } | 
| 3579 |  |  |  |  |  |  |  | 
| 3580 |  |  |  |  |  |  | 1; |