File Coverage

blib/lib/Mail/IMAPClient.pm
Criterion Covered Total %
statement 145 1735 8.3
branch 56 1256 4.4
condition 21 409 5.1
subroutine 27 212 12.7
pod 125 135 92.5
total 374 3747 9.9


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