File Coverage

blib/lib/Mail/IMAPClient.pm
Criterion Covered Total %
statement 145 1731 8.3
branch 56 1254 4.4
condition 21 407 5.1
subroutine 27 212 12.7
pod 125 135 92.5
total 374 3739 10.0


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;