Subversion Repositories sysadmin_scripts

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
14 rodolico 1
package Mail::IMAPClient;
2
 
3
# $Id: IMAPClient.pm,v 20001010.20 2003/06/13 18:30:55 dkernen Exp $
4
 
5
$Mail::IMAPClient::VERSION = '2.2.9';
6
$Mail::IMAPClient::VERSION = '2.2.9';  	# do it twice to make sure it takes
7
 
8
use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
9
use Socket();
10
use IO::Socket();
11
use IO::Socket::SSL();
12
use IO::Select();
13
use IO::File();
14
use Carp qw(carp);
15
#use Data::Dumper;
16
use Errno qw/EAGAIN/;
17
 
18
#print "Found Fcntl in $INC{'Fcntl.pm'}\n";
19
#Fcntl->import;
20
 
21
use constant Unconnected => 0;
22
 
23
use constant Connected         => 1;         	# connected; not logged in
24
 
25
use constant Authenticated => 2;      		# logged in; no mailbox selected
26
 
27
use constant Selected => 3;   		        # mailbox selected
28
 
29
use constant INDEX => 0;              		# Array index for output line number
30
 
31
use constant TYPE => 1;               		# Array index for line type 
32
						#    (either OUTPUT, INPUT, or LITERAL)
33
 
34
use constant DATA => 2;                       	# Array index for output line data
35
 
36
use constant NonFolderArg => 1;			# Value to pass to Massage to 
37
						# indicate non-folder argument
38
 
39
 
40
 
41
my %SEARCH_KEYS = map { ( $_ => 1 ) } qw/
42
	ALL ANSWERED BCC BEFORE BODY CC DELETED DRAFT FLAGGED
43
	FROM HEADER KEYWORD LARGER NEW NOT OLD ON OR RECENT
44
	SEEN SENTBEFORE SENTON SENTSINCE SINCE SMALLER SUBJECT
45
	TEXT TO UID UNANSWERED UNDELETED UNDRAFT UNFLAGGED 
46
	UNKEYWORD UNSEEN
47
/;
48
 
49
sub _debug {
50
	my $self = shift;
51
	return unless $self->Debug;
52
	my $fh = $self->{Debug_fh} || \*STDERR; 
53
	print $fh @_;
54
}
55
 
56
sub MaxTempErrors {
57
	my $self = shift;
58
	$_[0]->{Maxtemperrors} = $_[1] if defined($_[1]);
59
	return $_[0]->{Maxtemperrors};
60
}
61
 
62
# This function is used by the accessor methods
63
#
64
sub _do_accessor {
65
  my $datum = shift;
66
 
67
  if ( defined($_[1]) and $datum eq 'Fast_io' and ref($_[0]->{Socket})) {
68
    if ($_[1]) {                      # Passed the "True" flag
69
      my $fcntl = 0;
70
      eval { $fcntl=fcntl($_[0]->{Socket}, F_GETFL, 0) } ;
71
      if ($@) {
72
      $_[0]->{Fast_io} = 0;
73
      carp ref($_[0]) . " not using Fast_IO; not available on this platform"
74
        if ( ( $^W or $_[0]->Debug) and not $_[0]->{_fastio_warning_}++);
75
      } else {
76
      $_[0]->{Fast_io} = 1;
77
      $_[0]->{_fcntl} = $fcntl;
78
      my $newflags = $fcntl;
79
      $newflags |= O_NONBLOCK;
80
      fcntl($_[0]->{Socket}, F_SETFL, $newflags) ;
81
 
82
      }
83
    } else {
84
      eval { fcntl($_[0]->{Socket}, F_SETFL, $_[0]->{_fcntl}) } 
85
		if exists $_[0]->{_fcntl};
86
      $_[0]->{Fast_io} = 0;
87
      delete $_[0]->{_fcntl} if exists $_[0]->{_fcntl};
88
    }
89
  } elsif ( defined($_[1]) and $datum eq 'Socket' ) {
90
 
91
    # Get rid of fcntl settings for obsolete socket handles:
92
    delete $_[0]->{_fcntl} ;
93
    # Register this handle in a select vector:
94
    $_[0]->{_select} = IO::Select->new($_[1]) ;
95
  }
96
 
97
  if (scalar(@_) > 1) {
98
    $@ = $_[1] if $datum eq 'LastError';
99
    chomp $@ if $datum eq 'LastError';
100
    return $_[0]->{$datum} = $_[1] ;
101
  } else {
102
    return $_[0]->{$datum};
103
  }
104
}
105
 
106
# the following for loop sets up eponymous accessor methods for 
107
# the object's parameters:
108
 
109
BEGIN {
110
 for my $datum (
111
		qw( 	State Port Server Folder Fast_io Peek
112
			User Password Socket Timeout Buffer
113
			Debug LastError Count Uid Debug_fh Maxtemperrors
114
			EnableServerResponseInLiteral
115
			Authmechanism Authcallback Ranges
116
			Readmethod Showcredentials
117
			Prewritemethod
118
			Ssl
119
		)
120
 ) {
121
        no strict 'refs';
122
        *$datum = sub { _do_accessor($datum, @_); };
123
 }
124
 
125
 eval {
126
   require Digest::HMAC_MD5;
127
   require MIME::Base64;
128
 };
129
 if ($@) {
130
   $Mail::IMAPClient::_CRAM_MD5_ERR =
131
     "Internal CRAM-MD5 implementation not available: $@";
132
   $Mail::IMAPClient::_CRAM_MD5_ERR =~ s/\n+$/\n/;
133
 }
134
}
135
 
136
sub Wrap { 	shift->Clear(@_); 	}
137
 
138
# The following class method is for creating valid dates in appended msgs:
139
 
140
sub Rfc822_date {
141
my $class=      shift;
142
#Date: Fri, 09 Jul 1999 13:10:55 -0000#
143
my $date =      $class =~ /^\d+$/ ? $class : shift ;
144
my @date =      gmtime($date);
145
my @dow  =      qw{ Sun Mon Tue Wed Thu Fri Sat };
146
my @mnt  =      qw{ Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec};
147
#
148
return          sprintf(
149
                        "%s, %2.2d %s %4.4s %2.2d:%2.2d:%2.2d -%4.4d",
150
                        $dow[$date[6]],
151
                        $date[3],
152
                        $mnt[$date[4]],
153
                        $date[5]+=1900,
154
                        $date[2],
155
                        $date[1],
156
                        $date[0],
157
                        $date[8]) ;
158
}
159
 
160
# The following class method is for creating valid dates for use in IMAP search strings:
161
 
162
sub Rfc2060_date {
163
my $class=      shift;
164
# 11-Jan-2000
165
my $date =      $class =~ /^\d+$/ ? $class : shift ;
166
my @date =      gmtime($date);
167
my @mnt  =      qw{ Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec};
168
#
169
return          sprintf(
170
                        "%2.2d-%s-%4.4s",
171
                        $date[3],
172
                        $mnt[$date[4]],
173
                        $date[5]+=1900
174
		) ;
175
}
176
 
177
# The following class method strips out <CR>'s so lines end with <LF> 
178
#	instead of <CR><LF>:
179
 
180
sub Strip_cr {
181
	my $class = shift;
182
	unless ( ref($_[0]) or scalar(@_) > 1 ) {
183
		(my $string = $_[0]) =~ s/\x0d\x0a/\x0a/gm;
184
		return $string;
185
	}
186
	return wantarray ?     	map { s/\x0d\x0a/\0a/gm ; $_ }  
187
				(ref($_[0]) ? @{$_[0]}  : @_)  		: 
188
				[ map { s/\x0d\x0a/\x0a/gm ; $_ } 
189
				  ref($_[0]) ? @{$_[0]} : @_ 
190
				] ;
191
}
192
 
193
# The following defines a special method to deal with the Clear parameter:
194
 
195
sub Clear {
196
	my $self = shift;
197
	defined(my $clear = shift) or return $self->{Clear}; 
198
 
199
	my $oldclear   = $self->{Clear};
200
	$self->{Clear} = $clear;
201
 
202
	my (@keys) = sort { $b <=> $a } keys %{$self->{"History"}}  ;
203
 
204
	for ( my $i = $clear; $i < @keys ; $i++ ) 
205
		{ delete $self->{'History'}{$keys[$i]} }
206
 
207
	return $oldclear;
208
}
209
 
210
# read-only access to the transaction number:
211
sub Transaction { shift->Count };
212
 
213
# the constructor:
214
sub new {
215
	my $class 	= shift;
216
	my $self  	= 	{
217
		LastError	=> "", 
218
		Uid 		=> 1, 
219
		Count 		=> 0,
220
		Fast_io 	=> 1,
221
		"Clear"		=> 5, 
222
	};
223
	while (scalar(@_)) {
224
		$self->{ucfirst(lc($_[0]))} = $_[1]; shift, shift;
225
	}
226
	bless $self, ref($class)||$class;
227
 
228
	$self->State(Unconnected);
229
 
230
	$self->{Debug_fh} ||= \*STDERR;
231
	select((select($self->{Debug_fh}),$|++)[0]) ;
232
 	$self->_debug("Using Mail::IMAPClient version $Mail::IMAPClient::VERSION " .
233
		"and perl version " . (defined $^V ? join(".",unpack("CCC",$^V)) : "") . 
234
		" ($])\n") if $self->Debug;
235
	$self->LastError(0);
236
	$self->Maxtemperrors or $self->Maxtemperrors("unlimited") ;
237
	return $self->connect if $self->Server and !$self->Socket;
238
	return $self;
239
}
240
 
241
 
242
sub connect {
243
	my $self = shift;
244
 
245
	$self->Port(143) 
246
		if 	defined ($IO::Socket::INET::VERSION) 
247
		and 	$IO::Socket::INET::VERSION eq '1.25' 
248
		and 	!$self->Port;
249
	%$self = (%$self, @_);
250
	my $sock = ($self->Ssl ? IO::Socket::SSL->new : IO::Socket::INET->new);
251
	my $dp = ($self->Ssl ? 'imaps(993)' : 'imap(143)');
252
	$sock->configure({
253
		PeerAddr => $self->Server		,
254
                PeerPort => $self->Port||$dp	       	,
255
                Proto    => 'tcp' 			,
256
                Timeout  => $self->Timeout||0		,
257
		Debug	=> $self->Debug 		,
258
	})						;
259
 
260
	unless ( defined($sock) ) {
261
 
262
		$self->LastError( "Unable to connect to $self->{Server}: $!\n");	
263
		$@ 		= "Unable to connect to $self->{Server}: $!";	
264
		carp 		  "Unable to connect to $self->{Server}: $!" 
265
				unless defined wantarray;	
266
		return undef;
267
	}
268
	$self->Socket($sock);
269
	$self->State(Connected);
270
 
271
	$sock->autoflush(1)				;
272
 
273
	my ($code, $output);
274
        $output = "";
275
 
276
        until ( $code ) {
277
 
278
                $output = $self->_read_line or return undef;
279
                for my $o (@$output) {
280
			$self->_debug("Connect: Received this from readline: " . 
281
					join("/",@$o) . "\n");
282
                        $self->_record($self->Count,$o);	# $o is a ref
283
                      next unless $o->[TYPE] eq "OUTPUT";
284
                      ($code) = $o->[DATA] =~ /^\*\s+(OK|BAD|NO)/i  ;
285
                }
286
 
287
        }
288
 
289
	if ($code =~ /BYE|NO /) {
290
		$self->State(Unconnected);
291
		return undef ;
292
	}
293
 
294
	if ($self->User and $self->Password) {
295
		return $self->login ;
296
	} else {
297
		return $self;	
298
	}
299
}
300
 
301
 
302
sub login {
303
	my $self = shift;
304
	return $self->authenticate($self->Authmechanism,$self->Authcallback) 
305
		if $self->{Authmechanism};
306
 
307
	my $id   = $self->User;
308
	my $has_quotes = $id =~ /^".*"$/ ? 1 : 0;
309
	#my $string = 	"Login " . ( $has_quotes ? $id : qq("$id") ) . " " . 
310
	#		"{" . length($self->Password) . 
311
	#		"}\r\n".$self->Password."\r\n";
312
	my $string = "Login " . ( $has_quotes ? $id : qq("$id") ) . " " . $self->Password .  "\r\n";
313
	$self->_imap_command($string) 
314
		and $self->State(Authenticated);
315
	# $self->folders and $self->separator unless $self->NoAutoList;
316
	unless ( $self->IsAuthenticated) {
317
		my($carp) 	=  $self->LastError;
318
		$carp 		=~ s/^[\S]+ ([^\x0d\x0a]*)\x0d?\x0a/$1/;
319
 		carp $carp unless defined wantarray;
320
		return undef;
321
	}
322
	return $self;
323
}
324
 
325
sub separator {
326
	my $self = shift;
327
	my $target = shift ; 
328
 
329
	unless ( defined($target) ) {
330
		my $sep = "";
331
		# 	separator is namespace's 1st thing's 1st thing's 2nd thing:
332
		eval { 	$sep = $self->namespace->[0][0][1] } 	;
333
		return $sep if $sep;
334
	}	
335
 
336
	defined($target) or $target = "";
337
	$target ||= '""' ;
338
 
339
 
340
 
341
	# The fact that the response might end with {123} doesn't really matter here:
342
 
343
	unless (exists $self->{"$target${;}SEPARATOR"}) {
344
		my $list = (grep(/^\*\s+LIST\s+/,($self->list(undef,$target)||("NO")) ))[0] || 
345
				qq("/");
346
		my $s = (split(/\s+/,$list))[3];
347
		defined($s) and $self->{"$target${;}SEPARATOR"} = 
348
				( $s eq 'NIL' ? 'NIL' : substr($s, 1,length($s)-2) );
349
	}
350
	return $self->{$target,'SEPARATOR'};
351
}
352
 
353
sub sort {
354
    my $self = shift;
355
    my @hits;
356
    my @a = @_;
357
    $@ = "";
358
    $a[0] = "($a[0])" unless $a[0] =~ /^\(.*\)$/;      # wrap criteria in parens
359
    $self->_imap_command( ( $self->Uid ? "UID " : "" ) . "SORT ". join(' ',@a))
360
         or return wantarray ? @hits : \@hits ;
361
    my @results =  $self->History($self->Count);
362
 
363
    for my $r (@results) {
364
        chomp $r;
365
        $r =~ s/\r$//;
366
        $r =~ s/^\*\s+SORT\s+// or next;   
367
        push @hits, grep(/\d/,(split(/\s+/,$r)));
368
    }
369
    return wantarray ? @hits : \@hits;     
370
}
371
 
372
sub list {
373
	my $self = shift;
374
	my ($reference, $target) = (shift, shift);
375
	$reference = "" unless defined($reference);
376
	$target = '*' unless defined($target);
377
	$target = '""' if $target eq "";
378
	$target 	  = $self->Massage($target) unless $target eq '*' or $target eq '""';
379
	my $string 	=  qq(LIST "$reference" $target);
380
	$self->_imap_command($string)  or return undef;
381
	return wantarray ? 	
382
			$self->History($self->Count) 				  : 
383
                       	[ map { $_->[DATA] } @{$self->{'History'}{$self->Count}}] ;
384
}
385
 
386
sub lsub {
387
	my $self = shift;
388
	my ($reference, $target) = (shift, shift);
389
	$reference = "" unless defined($reference);
390
	$target = '*' unless defined($target);
391
	$target           = $self->Massage($target);
392
	my $string      =  qq(LSUB "$reference" $target);
393
	$self->_imap_command($string)  or return undef;
394
	return wantarray ?      $self->History($self->Count)            : 
395
                              [ map { $_->[DATA] } @{$self->{'History'}{$self->Count}}        ] ;
396
}
397
 
398
sub subscribed {
399
        my $self = shift;
400
	my $what = shift ;
401
 
402
        my @folders ;  
403
 
404
	my @list = $self->lsub(undef,( $what? "$what" . 
405
		$self->separator($what) . "*" : undef ) );
406
	push @list, $self->lsub(undef, $what) if $what and $self->exists($what) ;
407
 
408
      	# my @list = map { $self->_debug("Pushing $_->[${\(DATA)}] \n"); $_->[DATA] } 
409
	#	@$output;
410
 
411
	my $m;
412
 
413
	for ($m = 0; $m < scalar(@list); $m++ ) {
414
		if ($list[$m] && $list[$m]  !~ /\x0d\x0a$/ ) {
415
			$list[$m] .= $list[$m+1] ;
416
			$list[$m+1] = "";	
417
		}
418
 
419
 
420
		# $self->_debug("Subscribed: examining $list[$m]\n");
421
 
422
		push @folders, $1||$2 
423
			if $list[$m] =~
424
                        /       ^\*\s+LSUB               # * LSUB
425
                                \s+\([^\)]*\)\s+         # (Flags)
426
                                (?:"[^"]*"|NIL)\s+	 # "delimiter" or NIL
427
                                (?:"([^"]*)"|(.*))\x0d\x0a$  # Name or "Folder name"
428
                        /ix;
429
 
430
        } 
431
 
432
        # for my $f (@folders) { $f =~ s/^\\FOLDER LITERAL:://;}
433
	my @clean = () ; my %memory = (); 
434
	foreach my $f (@folders) { push @clean, $f unless $memory{$f}++ }
435
        return wantarray ? @clean : \@clean ;
436
}
437
 
438
 
439
sub deleteacl {
440
	my $self = shift;
441
	my ($target, $user ) = @_;
442
	$target 	  = $self->Massage($target);
443
	$user		  =~ s/^"(.*)"$/$1/;
444
	$user 	  	  =~ s/"/\\"/g;
445
	my $string 	=  qq(DELETEACL $target "$user");
446
	$self->_imap_command($string)  or return undef;
447
 
448
	return wantarray ? 	$self->History($self->Count) 				: 
449
                              [ map {$_->[DATA] } @{$self->{'History'}{$self->Count}}] ;
450
}
451
 
452
sub setacl {
453
        my $self = shift;
454
        my ($target, $user, $acl) = @_;
455
        $user = $self->User unless length($user);
456
        $target = $self->Folder unless length($target);
457
        $target           = $self->Massage($target);
458
        $user             =~ s/^"(.*)"$/$1/;
459
        $user             =~ s/"/\\"/g;
460
        $acl              =~ s/^"(.*)"$/$1/;
461
        $acl              =~ s/"/\\"/g;
462
        my $string      =  qq(SETACL $target "$user" "$acl");
463
        $self->_imap_command($string)  or return undef;
464
        return wantarray			?
465
		$self->History($self->Count)	:
466
		[map{$_->[DATA]}@{$self->{'History'}{$self->Count}}]
467
	;
468
}
469
 
470
 
471
sub getacl {
472
        my $self = shift;
473
        my ($target) = @_;
474
        $target = $self->Folder unless defined($target);
475
        my $mtarget           = $self->Massage($target);
476
        my $string      =  qq(GETACL $mtarget);
477
        $self->_imap_command($string)  or return undef;
478
	my @history = $self->History($self->Count);
479
	#$self->_debug("Getacl history: ".join("|",@history).">>>End of History<<<" ) ;
480
	my $perm = ""; 
481
	my $hash = {};
482
	for ( my $x = 0; $x < scalar(@history) ; $x++ ) {
483
        	if ( $history[$x] =~ /^\* ACL/ ) {
484
 
485
			$perm = $history[$x]=~ /^\* ACL $/	? 
486
				$history[++$x].$history[++$x] 	: 
487
				$history[$x];		
488
 
489
			$perm =~ s/\s?\x0d\x0a$//;
490
			piece:  until ( $perm =~ /\Q$target\E"?$/ or !$perm) {
491
				#$self->_debug(qq(Piece: permline=$perm and " 
492
				#	"pattern = /\Q$target\E"? \$/));
493
				$perm =~ s/\s([^\s]+)\s?$// or last piece;
494
				my($p) = $1;
495
				$perm =~ s/\s([^\s]+)\s?$// or last piece;
496
				my($u) = $1;
497
				$hash->{$u} = $p;
498
				$self->_debug("Permissions: $u => $p \n");
499
			}
500
 
501
		}
502
	}
503
        return $hash;
504
}
505
 
506
sub listrights {
507
	my $self = shift;
508
	my ($target, $user) = @_;
509
	$user = $self->User unless defined($user);
510
	$target = $self->Folder unless defined($target);
511
	$target 	  = $self->Massage($target);
512
	$user		  =~ s/^"(.*)"$/$1/;
513
	$user 	  	  =~ s/"/\\"/g;
514
	my $string 	=  qq(LISTRIGHTS $target "$user");
515
	$self->_imap_command($string)  or return undef;
516
	my $resp = ( grep(/^\* LISTRIGHTS/, $self->History($self->Count) ) )[0];
517
	my @rights = split(/\s/,$resp);	
518
	shift @rights, shift @rights, shift @rights, shift @rights;
519
	my $rights = join("",@rights);
520
	$rights =~ s/"//g;	
521
	return wantarray ? split(//,$rights) : $rights ;
522
}
523
 
524
sub select {
525
	my $self = shift;
526
	my $target = shift ;  
527
	return undef unless defined($target);
528
 
529
	my $qqtarget = $self->Massage($target);
530
 
531
	my $string 	=  qq/SELECT $qqtarget/;
532
 
533
	my $old = $self->Folder;
534
 
535
	if ($self->_imap_command($string) and $self->State(Selected)) {
536
		$self->Folder($target);
537
		return $old||$self;
538
	} else { 
539
		return undef;
540
	}
541
}
542
 
543
sub message_string {
544
	my $self = shift;
545
	my $msg  = shift;
546
	my $expected_size = $self->size($msg);
547
	return undef unless(defined $expected_size);	# unable to get size
548
	my $cmd  =  	$self->has_capability('IMAP4REV1') 				? 
549
				"BODY" . ( $self->Peek ? '.PEEK[]' : '[]' ) 		: 
550
				"RFC822" .  ( $self->Peek ? '.PEEK' : ''  )		;
551
 
552
	$self->fetch($msg,$cmd) or return undef;
553
 
554
	my $string = "";
555
 
556
	foreach my $result  (@{$self->{"History"}{$self->Transaction}}) { 
557
              $string .= $result->[DATA] 
558
		if defined($result) and $self->_is_literal($result) ;
559
	}      
560
	# BUG? should probably return undef if length != expected
561
	if ( length($string) != $expected_size ) { 
562
		carp "${self}::message_string: " .
563
			"expected $expected_size bytes but received " . 
564
			length($string) 
565
			if $self->Debug or $^W; 
566
	}
567
	if ( length($string) > $expected_size ) 
568
	{ $string = substr($string,0,$expected_size) }
569
	if ( length($string) < $expected_size ) {
570
		$self->LastError("${self}::message_string: expected ".
571
			"$expected_size bytes but received " . 
572
			length($string)."\n");
573
		return undef;
574
	}
575
	return $string;
576
}
577
 
578
sub bodypart_string {
579
	my($self, $msg, $partno, $bytes, $offset) = @_;
580
 
581
	unless ( $self->has_capability('IMAP4REV1') ) {
582
		$self->LastError(
583
				"Unable to get body part; server " . 
584
				$self->Server . 
585
				" does not support IMAP4REV1"
586
		);
587
		return undef;
588
	}
589
	my $cmd = "BODY" . ( $self->Peek ? ".PEEK[$partno]" : "[$partno]" ) 	;
590
	$offset ||= 0 ;
591
	$cmd .= "<$offset.$bytes>" if $bytes;
592
 
593
	$self->fetch($msg,$cmd) or return undef;
594
 
595
	my $string = "";
596
 
597
	foreach my $result  (@{$self->{"History"}{$self->Transaction}}) { 
598
              $string .= $result->[DATA] 
599
		if defined($result) and $self->_is_literal($result) ;
600
	}      
601
	return $string;
602
}
603
 
604
sub message_to_file {
605
	my $self = shift;
606
	my $fh   = shift;
607
	my @msgs = @_;
608
	my $handle;
609
 
610
	if ( ref($fh) ) {
611
		$handle = $fh;
612
	} else { 
613
		$handle = IO::File->new(">>$fh");
614
		unless ( defined($handle)) {
615
			$@ = "Unable to open $fh: $!";
616
			$self->LastError("Unable to open $fh: $!\n");
617
			carp $@ if $^W;
618
			return undef;
619
		}
620
		binmode $handle;	# For those of you who need something like this...
621
	} 
622
 
623
        my $clear = $self->Clear;
624
	my $cmd = $self->Peek ? 'BODY.PEEK[]' : 'BODY[]';
625
	$cmd = $self->Peek ? 'RFC822.PEEK' : 'RFC822' unless $self->imap4rev1;
626
 
627
	my $string = ( $self->Uid ? "UID " : "" ) . "FETCH " . join(",",@msgs) . " $cmd";
628
 
629
        $self->Clear($clear)
630
                if $self->Count >= $clear and $clear > 0;
631
 
632
        my $trans       = $self->Count($self->Count+1);
633
 
634
        $string         = "$trans $string" ;
635
 
636
        $self->_record($trans,[ 0, "INPUT", "$string\x0d\x0a"] );
637
 
638
        my $feedback = $self->_send_line("$string");
639
 
640
        unless ($feedback) {
641
                $self->LastError( "Error sending '$string' to IMAP: $!\n");
642
                $@ = "Error sending '$string' to IMAP: $!";
643
                return undef;
644
        }
645
 
646
        my ($code, $output);
647
        $output = "";
648
 
649
        READ: until ( $code)  {
650
                $output = $self->_read_line($handle) or return undef; # avoid possible infinite loop
651
                for my $o (@$output) {
652
                        $self->_record($trans,$o);	# $o is a ref
653
                        # $self->_debug("Received from readline: ${\($o->[DATA])}<<END OF RESULT>>\n");
654
                        next unless $self->_is_output($o);
655
                        ($code) = $o->[DATA] =~ /^$trans (OK|BAD|NO)/mi ;
656
                        if ($o->[DATA] =~ /^\*\s+BYE/im) {
657
                                $self->State(Unconnected);
658
                                return undef ;
659
                        }
660
                }
661
        }
662
 
663
        # $self->_debug("Command $string: returned $code\n");
664
	close $handle unless ref($fh);
665
        return $code =~ /^OK/im ? $self : undef ;
666
 
667
}
668
 
669
sub message_uid {
670
	my $self = shift;
671
	my $msg  = shift;
672
	my @uid = $self->fetch($msg,"UID");
673
	my $uid;
674
	while ( my $u = shift @uid and !$uid) {
675
		($uid) = $u =~ /\(UID\s+(\d+)\s*\)\r?$/;
676
	}
677
	return $uid;
678
}
679
 
680
sub original_migrate {
681
	my($self,$peer,$msgs,$folder) = @_;
682
	unless ( eval { $peer->IsConnected } ) {
683
		$self->LastError("Invalid or unconnected " .  ref($self). 
684
				 " object used as target for migrate." );
685
		return undef;
686
	}
687
	unless ($folder) {
688
		$folder = $self->Folder;
689
		$peer->exists($folder) 		or 
690
			$peer->create($folder) 	or 
691
			(
692
				$self->LastError("Unable to created folder $folder on target mailbox: ".
693
					"$peer->LastError") and 
694
				return undef 
695
			) ;
696
	}			
697
	if ( $msgs =~ /^all$/i ) { $msgs = $self->search("ALL") }
698
	foreach my $mid ( ref($msgs) ? @$msgs : $msgs ) {
699
		my $uid = $peer->append($folder,$self->message_string($mid));
700
		$self->LastError("Trouble appending to peer: " . $peer->LastError . "\n");
701
	}
702
}
703
 
704
 
705
sub migrate {
706
 
707
	my($self,$peer,$msgs,$folder) 	= @_;
708
	my($toSock,$fromSock) 		= ( $peer->Socket, $self->Socket);
709
	my $bufferSize 			= $self->Buffer || 4096;
710
	my $fromBuffer 			= "";
711
	my $clear 			= $self->Clear;
712
 
713
	unless ( eval { $peer->IsConnected } ) {
714
		$self->LastError("Invalid or unconnected " . 
715
			ref($self) . " object used as target for migrate. $@");
716
		return undef;
717
	}
718
 
719
	unless ($folder) {
720
		$folder = $self->Folder 	or
721
			$self->LastError( "No folder selected on source mailbox.") 
722
			and return undef;
723
 
724
		$peer->exists($folder)		or 
725
			$peer->create($folder)	or 
726
			(
727
				$self->LastError(
728
				  "Unable to create folder $folder on target mailbox: ".
729
				  $peer->LastError . "\n"
730
				) and return undef 
731
			) ;
732
	}
733
	$msgs or $msgs eq "0" or $msgs = "all";	
734
	if ( $msgs =~ /^all$/i ) { $msgs = $self->search("ALL") }
735
	my $range = $self->Range($msgs) ;
736
	$self->_debug("Migrating the following msgs from $folder: " . 
737
		" $range\n");
738
		# ( ref($msgs) ? join(", ",@$msgs) : $msgs) );
739
 
740
	#MIGMSG:	foreach my $mid ( ref($msgs) ? @$msgs : (split(/,\s*/,$msgs)) ) {#}
741
	MIGMSG:	foreach my $mid ( $range->unfold ) {
742
		# Set up counters for size of msg and portion of msg remaining to
743
		# process:
744
		$self->_debug("Migrating message $mid in folder $folder\n") 
745
			if $self->Debug;
746
		my $leftSoFar = my $size = $self->size($mid);
747
 
748
		# fetch internaldate and flags of original message:
749
		my $intDate = '"' . $self->internaldate($mid) . '"' ;
750
		my $flags   = "(" . join(" ",grep(!/\\Recent/i,$self->flags($mid)) ) . ")" ;
751
		$flags = "" if  $flags eq "()" ;
752
 
753
		# set up transaction numbers for from and to connections:
754
		my $trans       = $self->Count($self->Count+1);
755
		my $ptrans      = $peer->Count($peer->Count+1);
756
 
757
		# If msg size is less than buffersize then do whole msg in one 
758
		# transaction:
759
		if ( $size <= $bufferSize ) {
760
			my $new_mid = $peer->append_string($peer->Massage($folder),
761
					$self->message_string($mid) ,$flags,
762
					$intDate) ;
763
		        $self->_debug("Copied message $mid in folder $folder to " . 
764
				    $peer->User .
765
				    '@' . $peer->Server . 
766
				    ". New Message UID is $new_mid.\n" 
767
		        ) if $self->Debug;
768
 
769
		        $peer->_debug("Copied message $mid in folder $folder from " . 
770
				$self->User .
771
				'@' . $self->Server . ". New Message UID is $new_mid.\n" 
772
		        ) if $peer->Debug;
773
 
774
 
775
			next MIGMSG;
776
		}
777
 
778
		# otherwise break it up into digestible pieces:
779
		my ($cmd, $pattern);
780
		if ( $self->imap4rev1 ) {
781
			# imap4rev1 supports FETCH BODY 
782
			$cmd = $self->Peek ? 'BODY.PEEK[]' : 'BODY[]';
783
			$pattern = sub {
784
                                #$self->_debug("Data fed to pattern: $_[0]<END>\n");
785
                                my($one) = $_[0] =~ /\(.*BODY\[\]<\d+> \{(\d+)\}/i ; # ;-)
786
					# or $self->_debug("Didn't match pattern\n") ; 
787
                                #$self->_debug("Returning from pattern: $1\n") if defined($1);
788
				return $one ;
789
                        } ;
790
		} else {
791
			# older imaps use (deprecated) FETCH RFC822:
792
			$cmd = $self->Peek ? 'RFC822.PEEK' : 'RFC822' ;
793
			$pattern = sub {
794
				my($one) = shift =~ /\(RFC822\[\]<\d+> \{(\d+)\}/i; 
795
				return $one ;
796
			};
797
		}
798
 
799
 
800
		# Now let's warn the peer that there's a message coming:
801
 
802
		my $pstring = 	"$ptrans APPEND " . 
803
				$self->Massage($folder). 
804
				" " . 
805
				( $flags ? "$flags " : () ) . 
806
				( $intDate ? "$intDate " : () ) . 
807
				"{" . $size . "}"  ;
808
 
809
		$self->_debug("About to issue APPEND command to peer " .
810
			"for msg $mid\n") 		if $self->Debug;
811
 
812
		my $feedback2 = $peer->_send_line( $pstring ) ;
813
 
814
		$peer->_record($ptrans,[ 
815
			0, 
816
			"INPUT", 
817
			"$pstring" ,
818
		] ) ;
819
		unless ($feedback2) {
820
		   $self->LastError("Error sending '$pstring' to target IMAP: $!\n");
821
		   return undef;
822
		}
823
		# Get the "+ Go ahead" response:
824
		my $code = 0;
825
		until ($code eq '+' or $code =~ /NO|BAD|OK/ ) {
826
	  	  my $readSoFar = 0 ;
827
		  $readSoFar += sysread($toSock,$fromBuffer,1,$readSoFar)||0
828
			until $fromBuffer =~ /\x0d\x0a/;
829
 
830
		  #$peer->_debug("migrate: response from target server: " .
831
		  #	"$fromBuffer<END>\n") 	if $peer->Debug;
832
 
833
		  ($code)= $fromBuffer =~ /^(\+)|^(?:\d+\s(?:BAD|NO))/ ;
834
		  $code ||=0;
835
 
836
		  $peer->_debug( "$folder: received $fromBuffer from server\n") 
837
		  if $peer->Debug;
838
 
839
	  	  # ... and log it in the history buffers
840
		  $self->_record($trans,[ 
841
			0, 
842
			"OUTPUT", 
843
			"Mail::IMAPClient migrating message $mid to $peer->User\@$peer->Server"
844
		  ] ) ;
845
		  $peer->_record($ptrans,[ 
846
			0, 
847
			"OUTPUT", 
848
			$fromBuffer
849
		  ] ) ;
850
 
851
 
852
		}
853
		unless ( $code eq '+'  ) {
854
			$^W and warn "$@\n";
855
			$self->Debug and $self->_debug("Error writing to target host: $@\n");
856
			next MIGMSG;	
857
		}
858
		# Here is where we start sticking in UID if that parameter
859
		# is turned on:	
860
		my $string = ( $self->Uid ? "UID " : "" ) . "FETCH $mid $cmd";
861
 
862
		# Clean up history buffer if necessary:
863
		$self->Clear($clear)
864
			if $self->Count >= $clear and $clear > 0;
865
 
866
 
867
	   # position will tell us how far from beginning of msg the
868
	   # next IMAP FETCH should start (1st time start at offet zero):
869
	   my $position = 0;
870
	   #$self->_debug("There are $leftSoFar bytes left versus a buffer of $bufferSize bytes.\n");
871
	   my $chunkCount = 0;
872
	   while ( $leftSoFar > 0 ) {
873
		$self->_debug("Starting chunk " . ++$chunkCount . "\n");
874
 
875
		my $newstring         ="$trans $string<$position."  .
876
					( $leftSoFar > $bufferSize ? $bufferSize : $leftSoFar ) . 
877
					">" ;
878
 
879
		$self->_record($trans,[ 0, "INPUT", "$newstring\x0d\x0a"] );
880
		$self->_debug("Issuing migration command: $newstring\n" )
881
			if $self->Debug;;
882
 
883
		my $feedback = $self->_send_line("$newstring");
884
 
885
		unless ($feedback) {
886
		   $self->LastError("Error sending '$newstring' to source IMAP: $!\n");
887
		   return undef;
888
		}
889
		my $chunk = "";
890
		until ($chunk = $pattern->($fromBuffer) ) {
891
		   $fromBuffer = "" ;
892
	    	   until ( $fromBuffer=~/\x0d\x0a$/ ) {
893
	    	   	sysread($fromSock,$fromBuffer,1,length($fromBuffer)) ; 
894
			#$self->_debug("migrate chunk $chunkCount:" . 
895
			#	"Read from source: $fromBuffer<END>\n");
896
		   }
897
 
898
		   $self->_record($trans,[ 0, "OUTPUT", "$fromBuffer"] ) ;
899
 
900
		   if ( $fromBuffer =~ /^$trans (?:NO|BAD)/ ) {
901
			$self->LastError($fromBuffer) ;
902
			next MIGMSG;
903
		   }
904
 
905
		   if ( $fromBuffer =~ /^$trans (?:OK)/ ) {
906
			$self->LastError("Unexpected good return code " .
907
				"from source host: " . $fromBuffer) ;
908
			next MIGMSG;
909
		   }
910
 
911
		}
912
		$fromBuffer = "";
913
		my $readSoFar = 0 ;
914
		$readSoFar += sysread($fromSock,$fromBuffer,$chunk-$readSoFar,$readSoFar)||0
915
			until $readSoFar >= $chunk;
916
		#$self->_debug("migrateRead: chunk=$chunk readSoFar=$readSoFar " .
917
		#	"Buffer=$fromBuffer<END_OF_BUFFER\n") if $self->Debug;
918
 
919
		my $wroteSoFar 	= 0;
920
		my $temperrs 	= 0;
921
		my $optimize 	= 0;
922
 
923
		until ( $wroteSoFar >= $chunk ) {
924
		 #$peer->_debug("Chunk $chunkCount: Next write will attempt to write " .
925
		 #	"this substring:\n" .
926
		 #	substr($fromBuffer,$wroteSoFar,$chunk-$wroteSoFar) .
927
		 #	"<END_OF_SUBSTRING>\n"
928
		 #);
929
 
930
		 until ( $wroteSoFar >= $readSoFar ) {
931
		    $!=0;
932
		    my $ret = syswrite(
933
				$toSock,
934
				$fromBuffer,
935
				$chunk - $wroteSoFar, 
936
				$wroteSoFar )||0 ;
937
 
938
		    $wroteSoFar += $ret;
939
 
940
		    if ($! == &EAGAIN ) {
941
			if ( 	$self->{Maxtemperrors} !~ /^unlimited/i
942
			    	and $temperrs++ > ($self->{Maxtemperrors}||10) 
943
			) {
944
				$self->LastError("Persistent '${!}' errors\n");
945
				$self->_debug("Persistent '${!}' errors\n");
946
				return undef;
947
			}
948
			$optimize = 1;
949
		    } else {
950
			# avoid infinite loops on syswrite error
951
			return undef unless(defined $ret);	 
952
		    }
953
		    # Optimization of wait time between syswrite calls
954
		    # only runs if syscalls run too fast and fill the 
955
		    # buffer causing "EAGAIN: Resource Temp. Unavail" errors. The
956
		    # premise is that $maxwrite will be approx. the same as 
957
		    # the smallest buffer between the sending and receiving side. 
958
		    # Waiting time between syscalls should ideally be exactly as 
959
		    # long as it takes the receiving side to empty that buffer, 
960
		    # minus a little bit to prevent it from
961
		    # emptying completely and wasting time in the select call.
962
		    if ($optimize) {
963
		        my $waittime = .02; 
964
		    	$maxwrite = $ret if $maxwrite < $ret;
965
		    	push( @last5writes, $ret );
966
		    	shift( @last5writes ) if $#last5writes > 5;
967
			    my $bufferavail = 0;
968
			    $bufferavail += $_ for ( @last5writes );
969
			    $bufferavail /= ($#last5writes||1);
970
			    # Buffer is staying pretty full; 
971
			    # we should increase the wait period
972
			    # to reduce transmission overhead/number of packets sent
973
			    if ( $bufferavail < .4 * $maxwrite ) {
974
				$waittime *= 1.3;
975
 
976
			    # Buffer is nearly or totally empty; 
977
			    # we're wasting time in select
978
			    # call that could be used to send data, 
979
			    # so reduce the wait period
980
			    } elsif ( $bufferavail > .9 * $maxwrite ) {
981
				$waittime *= .5;
982
			    }
983
		    	CORE::select(undef, undef, undef, $waittime);
984
		    }
985
		    if ( defined($ret) ) {
986
			$temperrs = 0  ;
987
		    }
988
		    $peer->_debug("Chunk $chunkCount: " .
989
			"Wrote $wroteSoFar bytes (out of $chunk)\n");
990
		   }
991
		}
992
		$position += $readSoFar ;
993
		$leftSoFar -= $readSoFar;
994
		$fromBuffer = "";
995
		# Finish up reading the server response from the fetch cmd
996
		# 	on the source system:
997
		{
998
		my $code = 0;
999
		until ( $code)  {
1000
 
1001
			# escape infinite loop if read_line never returns any data:
1002
 
1003
			$self->_debug("Reading from source server; expecting " .
1004
				"') OK' type response\n") if $self->Debug;
1005
 
1006
			$output = $self->_read_line or return undef; 
1007
			for my $o (@$output) {
1008
 
1009
				$self->_record($trans,$o);      # $o is a ref
1010
 
1011
				# $self->_debug("Received from readline: " .
1012
				# "${\($o->[DATA])}<<END OF RESULT>>\n");
1013
 
1014
				next unless $self->_is_output($o);
1015
 
1016
				($code) = $o->[DATA] =~ /^$trans (OK|BAD|NO)/mi ;
1017
 
1018
				if ($o->[DATA] =~ /^\*\s+BYE/im) {
1019
					$self->State(Unconnected);
1020
					return undef ;
1021
				}
1022
	   		}
1023
	   	}
1024
	   	} # end scope for my $code
1025
	   }
1026
	   # Now let's send a <CR><LF> to the peer to signal end of APPEND cmd:
1027
	   {
1028
	    my $wroteSoFar = 0;
1029
	    $fromBuffer = "\x0d\x0a";
1030
	    $!=0;
1031
	    $wroteSoFar += syswrite($toSock,$fromBuffer,2-$wroteSoFar,$wroteSoFar)||0 
1032
	    		until $wroteSoFar >= 2;
1033
 
1034
	   }
1035
	   # Finally, let's get the new message's UID from the peer:
1036
	   my $new_mid = "";
1037
           {
1038
                my $code = 0;
1039
                until ( $code)  {
1040
                        # escape infinite loop if read_line never returns any data:
1041
			$peer->_debug("Reading from target: " .
1042
				"expecting new uid in response\n") if $peer->Debug;
1043
 
1044
                        $output = $peer->_read_line or next MIGMSG;
1045
 
1046
                        for my $o (@$output) {
1047
 
1048
                                $peer->_record($ptrans,$o);      # $o is a ref
1049
 
1050
                                # $peer->_debug("Received from readline: " .
1051
                                # "${\($o->[DATA])}<<END OF RESULT>>\n");
1052
 
1053
                                next unless $peer->_is_output($o);
1054
 
1055
                                ($code) = $o->[DATA] =~ /^$ptrans (OK|BAD|NO)/mi ;
1056
				($new_mid)= $o->[DATA] =~ /APPENDUID \d+ (\d+)/ if $code;
1057
				#$peer->_debug("Code line: " . $o->[DATA] . 
1058
				#	"\nCode=$code mid=$new_mid\n" ) if $code;
1059
 
1060
                                if ($o->[DATA] =~ /^\*\s+BYE/im) {
1061
                                        $peer->State(Unconnected);
1062
                                        return undef ;
1063
                                }
1064
                        }
1065
			$new_mid||="unknown" ;
1066
                }
1067
             } # end scope for my $code
1068
 
1069
	     $self->_debug("Copied message $mid in folder $folder to " . $peer->User .
1070
			    '@' . $peer->Server . ". New Message UID is $new_mid.\n" 
1071
	     ) if $self->Debug;
1072
 
1073
	     $peer->_debug("Copied message $mid in folder $folder from " . $self->User .
1074
			    '@' . $self->Server . ". New Message UID is $new_mid.\n" 
1075
	     ) if $peer->Debug;
1076
 
1077
 
1078
	  # ... and finish up reading the server response from the fetch cmd
1079
	  # 	on the source system:
1080
	      # {
1081
	#	my $code = 0;
1082
	#	until ( $code)  {
1083
	#		# escape infinite loop if read_line never returns any data:
1084
        #      		unless ($output = $self->_read_line ) {
1085
	#			$self->_debug($self->LastError) ;
1086
	#			next MIGMSG;
1087
	#		}
1088
	#		for my $o (@$output) {
1089
#
1090
#				$self->_record($trans,$o);      # $o is a ref
1091
#
1092
#				# $self->_debug("Received from readline: " .
1093
#				# "${\($o->[DATA])}<<END OF RESULT>>\n");
1094
#
1095
#				next unless $self->_is_output($o);
1096
#
1097
#			 	($code) = $o->[DATA] =~ /^$trans (OK|BAD|NO)/mi ;
1098
#
1099
#			      	if ($o->[DATA] =~ /^\*\s+BYE/im) {
1100
#					$self->State(Unconnected);
1101
#					return undef ;
1102
#				}
1103
#			}
1104
#		}
1105
#		}
1106
 
1107
	     	# and clean up the I/O buffer:
1108
	     	$fromBuffer = "";
1109
	     }
1110
	return $self;	
1111
}
1112
 
1113
 
1114
sub body_string {
1115
	my $self = shift;
1116
	my $msg  = shift;
1117
	my $ref = $self->fetch($msg,"BODY" . ( $self->Peek ? ".PEEK" : "" ) . "[TEXT]");
1118
 
1119
        my $string = "";
1120
    	foreach my $result  (@{$ref}) 	{ 
1121
                $string .= $result->[DATA] if defined($result) and $self->_is_literal($result) ;
1122
        }
1123
	return $string if $string;
1124
 
1125
        my $head = shift @$ref;
1126
        $self->_debug("body_string: first shift = '$head'\n");
1127
 
1128
        until ( (! $head)  or $head =~ /(?:.*FETCH .*\(.*BODY\[TEXT\])|(?:^\d+ BAD )|(?:^\d NO )/i ) {
1129
                $self->_debug("body_string: shifted '$head'\n");
1130
                $head = shift(@$ref) ;
1131
        }
1132
	unless ( scalar(@$ref) ) {
1133
			$self->LastError("Unable to parse server response from " . $self->LastIMAPCommand );
1134
			return undef ;
1135
	}
1136
	my $popped ; $popped = pop @$ref until 	
1137
			( 
1138
				( 	defined($popped) and 
1139
					# (-:	Smile!
1140
					$popped =~ /\)\x0d\x0a$/ 
1141
				) 	or
1142
					not grep(
1143
						# (-:	Smile again!
1144
						/\)\x0d\x0a$/,
1145
						@$ref
1146
					)
1147
			);
1148
 
1149
        if      ($head =~ /BODY\[TEXT\]\s*$/i )     {       # Next line is a literal
1150
                        $string .= shift @$ref while scalar(@$ref);
1151
                        $self->_debug("String is now $string\n") if $self->Debug;
1152
        }
1153
 
1154
        return $string||undef;
1155
}
1156
 
1157
 
1158
sub examine {
1159
	my $self = shift;
1160
	my $target = shift ; return undef unless defined($target);
1161
	$target = $self->Massage($target);
1162
	my $string 	=  qq/EXAMINE $target/;
1163
 
1164
	my $old = $self->Folder;
1165
 
1166
	if ($self->_imap_command($string) and $self->State(Selected)) {
1167
		$self->Folder($target);
1168
		return $old||$self;
1169
	} else { 
1170
		return undef;
1171
	}
1172
}
1173
 
1174
sub idle {
1175
	my $self = shift;
1176
	my $good = '+';
1177
	my $count = $self->Count +1;
1178
	return $self->_imap_command("IDLE",$good) ? $count : undef;
1179
}
1180
 
1181
sub done {
1182
	my $self 	= shift;
1183
 
1184
	my $count 	= shift||$self->Count;
1185
 
1186
	my $clear = "";
1187
	$clear = $self->Clear;
1188
 
1189
	$self->Clear($clear) 
1190
		if $self->Count >= $clear and $clear > 0;
1191
 
1192
	my $string = "DONE\x0d\x0a";
1193
	$self->_record($count,[ $self->_next_index($count), "INPUT", "$string\x0d\x0a"] );
1194
 
1195
	my $feedback = $self->_send_line("$string",1);
1196
 
1197
	unless ($feedback) {
1198
		$self->LastError( "Error sending '$string' to IMAP: $!\n");
1199
		return undef;
1200
	}
1201
 
1202
	my ($code, $output);	
1203
	$output = "";
1204
 
1205
	until ( $code and $code =~ /(OK|BAD|NO)/m ) {
1206
 
1207
		$output = $self->_read_line or return undef;	
1208
		for my $o (@$output) { 
1209
			$self->_record($count,$o);	# $o is a ref
1210
			next unless $self->_is_output($o);
1211
                      	($code) = $o->[DATA] =~ /^(?:$count) (OK|BAD|NO)/m  ;
1212
                      if ($o->[DATA] =~ /^\*\s+BYE/) {
1213
				$self->State(Unconnected);
1214
			}
1215
		}
1216
	}	
1217
	return $code =~ /^OK/ ? @{$self->Results} : undef ;
1218
 
1219
}
1220
 
1221
sub tag_and_run {
1222
	my $self = shift;
1223
	my $string = shift;
1224
	my $good = shift;
1225
	$self->_imap_command($string,$good);
1226
	return @{$self->Results};
1227
}
1228
# _{name} methods are undocumented and meant to be private.
1229
 
1230
# _imap_command runs a command, inserting the correct tag
1231
# and <CR><LF> and whatnot.
1232
# When updating _imap_command, remember to examine the run method, too, since it is very similar.
1233
#
1234
 
1235
sub _imap_command {
1236
 
1237
	my $self 	= shift;
1238
	my $string 	= shift 	or return undef;
1239
	my $good 	= shift 	|| 'GOOD';
1240
 
1241
	my $qgood = quotemeta($good);
1242
 
1243
	my $clear = "";
1244
	$clear = $self->Clear;
1245
 
1246
	$self->Clear($clear) 
1247
		if $self->Count >= $clear and $clear > 0;
1248
 
1249
	my $count 	= $self->Count($self->Count+1);
1250
 
1251
	$string 	= "$count $string" ;
1252
 
1253
	$self->_record($count,[ 0, "INPUT", "$string\x0d\x0a"] );
1254
 
1255
	my $feedback = $self->_send_line("$string");
1256
 
1257
	unless ($feedback) {
1258
		$self->LastError( "Error sending '$string' to IMAP: $!\n");
1259
		$@ = "Error sending '$string' to IMAP: $!";
1260
		carp "Error sending '$string' to IMAP: $!" if $^W;
1261
		return undef;
1262
	}
1263
 
1264
	my ($code, $output);	
1265
	$output = "";
1266
 
1267
	READ: until ( $code)  {
1268
	    	# escape infinite loop if read_line never returns any data:
1269
              	$output = $self->_read_line or return undef; 
1270
 
1271
		for my $o (@$output) { 
1272
			$self->_record($count,$o);	# $o is a ref
1273
                      # $self->_debug("Received from readline: ${\($o->[DATA])}<<END OF RESULT>>\n");
1274
			next unless $self->_is_output($o);
1275
			if ( $good eq '+' ) {
1276
                      		$o->[DATA] =~ /^$count (OK|BAD|NO|$qgood)|^($qgood)/mi ;
1277
				$code = $1||$2 ;
1278
			} else {
1279
                      		($code) = $o->[DATA] =~ /^$count (OK|BAD|NO|$qgood)/mi ;
1280
			}
1281
                      if ($o->[DATA] =~ /^\*\s+BYE/im) {
1282
				$self->State(Unconnected);
1283
				return undef ;
1284
			}
1285
		}
1286
	}	
1287
 
1288
	# $self->_debug("Command $string: returned $code\n");
1289
	return $code =~ /^OK|$qgood/im ? $self : undef ;
1290
 
1291
}
1292
 
1293
sub run {
1294
	my $self 	= shift;
1295
	my $string 	= shift 	or return undef;
1296
	my $good 	= shift 	|| 'GOOD';
1297
	my $count 	= $self->Count($self->Count+1);
1298
	my($tag)	= $string =~ /^(\S+) /  ;
1299
 
1300
	unless ($tag) {
1301
		$self->LastError("Invalid string passed to run method; no tag found.\n");
1302
	}
1303
 
1304
	my $qgood = quotemeta($good);
1305
 
1306
	my $clear = "";
1307
	$clear = $self->Clear;
1308
 
1309
	$self->Clear($clear) 
1310
		if $self->Count >= $clear and $clear > 0;
1311
 
1312
	$self->_record($count,[ $self->_next_index($count), "INPUT", "$string"] );
1313
 
1314
	my $feedback = $self->_send_line("$string",1);
1315
 
1316
	unless ($feedback) {
1317
		$self->LastError( "Error sending '$string' to IMAP: $!\n");
1318
		return undef;
1319
	}
1320
 
1321
	my ($code, $output);	
1322
	$output = "";
1323
 
1324
	until ( $code =~ /(OK|BAD|NO|$qgood)/m ) {
1325
 
1326
		$output = $self->_read_line or return undef;	
1327
		for my $o (@$output) { 
1328
			$self->_record($count,$o);	# $o is a ref
1329
			next unless $self->_is_output($o);
1330
			if ( $good eq '+' ) {
1331
			   $o->[DATA] =~ /^(?:$tag|\*) (OK|BAD|NO|$qgood)|(^$qgood)/m  ;
1332
			   $code = $1||$2;
1333
			} else {
1334
                      		($code) = 
1335
				   $o->[DATA] =~ /^(?:$tag|\*) (OK|BAD|NO|$qgood)/m  ;
1336
			}
1337
                      if ($o->[DATA] =~ /^\*\s+BYE/) {
1338
				$self->State(Unconnected);
1339
			}
1340
		}
1341
	}	
1342
	$self->{'History'}{$tag} = $self->{"History"}{$count} unless $tag eq $count;
1343
	return $code =~ /^OK|$qgood/ ? @{$self->Results} : undef ;
1344
 
1345
}
1346
#sub bodystruct {	# return bodystruct 
1347
#}
1348
 
1349
# _record saves the conversation into the History structure:
1350
sub _record {
1351
 
1352
	my ($self,$count,$array) = ( shift, shift, shift);
1353
	local($^W)= undef;
1354
 
1355
	#$self->_debug(sprintf("in _record: count is $count, values are %s/%s/%s and caller is " . 
1356
	#	join(":",caller()) . "\n",@$array));
1357
 
1358
      if (    #       $array->[DATA] and 
1359
              $array->[DATA] =~ /^\d+ LOGIN/i and
1360
		! $self->Showcredentials
1361
      ) { 
1362
 
1363
              $array->[DATA] =~ s/LOGIN.*/LOGIN XXXXXXXX XXXXXXXX/i ;
1364
	}
1365
 
1366
	push @{$self->{"History"}{$count}}, $array;
1367
 
1368
      if ( $array->[DATA] =~ /^\d+\s+(BAD|NO)\s/im ) {
1369
              $self->LastError("$array->[DATA]") ;
1370
              $@ = $array->[DATA];
1371
              carp "$array->[DATA]" if $^W ;
1372
	}
1373
	return $self;
1374
}
1375
 
1376
#_send_line writes to the socket:
1377
sub _send_line {
1378
	my($self,$string,$suppress) = (shift, shift, shift);
1379
 
1380
	#$self->_debug("_send_line: Connection state = " . 
1381
	#		$self->State . " and socket fh = " . 
1382
	#		($self->Socket||"undef") . "\n")
1383
	#if $self->Debug;
1384
 
1385
	unless ($self->IsConnected and $self->Socket) {
1386
		$self->LastError("NO Not connected.\n");
1387
		carp "Not connected" if $^W;
1388
		return undef;
1389
	}
1390
 
1391
	unless ($string =~ /\x0d\x0a$/ or $suppress ) {
1392
 
1393
		chomp $string;
1394
		$string .= "\x0d" unless $string =~ /\x0d$/;	
1395
		$string .= "\x0a" ;
1396
	}
1397
	if ( 
1398
		$string =~ /^[^\x0a{]*\{(\d+)\}\x0d\x0a/ 	   # ;-}
1399
	) 	{
1400
		my($p1,$p2,$len) ;
1401
		if ( ($p1,$len)   = 
1402
			$string =~ /^([^\x0a{]*\{(\d+)\}\x0d\x0a)/ # } for vi
1403
			and  (
1404
				$len < 32766 ? 
1405
				( ($p2) = $string =~ /
1406
					^[^\x0a{]*
1407
					\{\d+\}
1408
					\x0d\x0a
1409
					(
1410
						.{$len}
1411
						.*\x0d\x0a
1412
					)
1413
				/x ) :
1414
 
1415
				( ($p2) = $string =~ /	^[^\x0a{]*
1416
							\{\d+\}
1417
							\x0d\x0a
1418
							(.*\x0d\x0a)
1419
						    /x 	
1420
				   and length($p2) == $len  ) # }} for vi
1421
		     )
1422
		) {
1423
			$self->_debug("Sending literal string " .
1424
				"in two parts: $p1\n\tthen: $p2\n");
1425
			$self->_send_line($p1) or return undef;
1426
			$output = $self->_read_line or return undef;
1427
			foreach my $o (@$output) {
1428
				# $o is already an array ref:
1429
				$self->_record($self->Count,$o);              
1430
                              ($code) = $o->[DATA] =~ /(^\+|NO|BAD)/i;
1431
                              if ($o->[DATA] =~ /^\*\s+BYE/) {
1432
					$self->State(Unconnected);
1433
					close $fh;
1434
					return undef ;
1435
                              } elsif ( $o->[DATA]=~ /^\d+\s+(NO|BAD)/i ) {
1436
					close $fh;
1437
					return undef;
1438
				}
1439
			}
1440
			if ( $code eq '+' ) 	{ $string = $p2; } 
1441
			else 			{ return undef ; }
1442
		}
1443
 
1444
	}
1445
	if ($self->Debug) {
1446
		my $dstring = $string;
1447
		if ( $dstring =~ m[\d+\s+Login\s+]i) {
1448
			$dstring =~ 
1449
			  s(\b(?:\Q$self->{Password}\E|\Q$self->{User}\E)\b)
1450
			('X' x length($self->{Password}))eg;
1451
		}
1452
		_debug $self, "Sending: $dstring\n" if $self->Debug;
1453
	}
1454
	my $total = 0;
1455
	my $temperrs = 0;
1456
	my $optimize = 0;
1457
     	my $maxwrite = 0;
1458
     	my $waittime = .02;
1459
     	my @last5writes = (1);
1460
	$string = $self->Prewritemethod->($self,$string) if $self->Prewritemethod;
1461
	_debug $self, "Sending: $string\n" if $self->Debug and $self->Prewritemethod;
1462
 
1463
	until ($total >= length($string)) {
1464
		my $ret = 0;
1465
	        $!=0;
1466
		$ret =	syswrite(	
1467
					$self->Socket, 
1468
					$string, 
1469
					length($string)-$total, 
1470
					$total
1471
					);
1472
		$ret||=0;
1473
		if ($! == &EAGAIN ) {
1474
			if ( 	$self->{Maxtemperrors} !~ /^unlimited/i
1475
			    	and $temperrs++ > ($self->{Maxtemperrors}||10) 
1476
			) {
1477
				$self->LastError("Persistent '${!}' errors\n");
1478
				$self->_debug("Persistent '${!}' errors\n");
1479
				return undef;
1480
			}
1481
			$optimize = 1;
1482
		} else {
1483
			# avoid infinite loops on syswrite error
1484
			return undef unless(defined $ret);	 
1485
		}
1486
		# Optimization of wait time between syswrite calls
1487
		# only runs if syscalls run too fast and fill the 
1488
		# buffer causing "EAGAIN: Resource Temp. Unavail" errors. The
1489
		# premise is that $maxwrite will be approx. the same as 
1490
		# the smallest buffer between the sending and receiving side. 
1491
		# Waiting time between syscalls should ideally be exactly as 
1492
		# long as it takes the receiving side to empty that buffer, 
1493
		# minus a little bit to prevent it from
1494
		# emptying completely and wasting time in the select call.
1495
		if ($optimize) {
1496
		    $maxwrite = $ret if $maxwrite < $ret;
1497
		    push( @last5writes, $ret );
1498
		    shift( @last5writes ) if $#last5writes > 5;
1499
		    my $bufferavail = 0;
1500
		    $bufferavail += $_ for ( @last5writes );
1501
		    $bufferavail /= $#last5writes;
1502
		    # Buffer is staying pretty full; 
1503
		    # we should increase the wait period
1504
		    # to reduce transmission overhead/number of packets sent
1505
		    if ( $bufferavail < .4 * $maxwrite ) {
1506
			$waittime *= 1.3;
1507
 
1508
		    # Buffer is nearly or totally empty; 
1509
		    # we're wasting time in select
1510
		    # call that could be used to send data, 
1511
		    # so reduce the wait period
1512
		    } elsif ( $bufferavail > .9 * $maxwrite ) {
1513
			$waittime *= .5;
1514
		    }
1515
		    $self->_debug("Output buffer full; waiting $waittime seconds for relief\n");
1516
		    CORE::select(undef, undef, undef, $waittime);
1517
		}
1518
		if ( defined($ret) ) {
1519
			$temperrs = 0  ;
1520
			$total += $ret ;
1521
		}
1522
	}
1523
	_debug $self,"Sent $total bytes\n" if $self->Debug;
1524
	return $total;
1525
}
1526
 
1527
# _read_line reads from the socket. It is called by:
1528
# 	append	append_file	authenticate	connect		_imap_command
1529
#
1530
# It is also re-implemented in:
1531
#	message_to_file
1532
#
1533
# syntax: $output = $self->_readline( ( $literal_callback|undef ) , ( $output_callback|undef ) ) ;
1534
# 	  Both input argument are optional, but if supplied must either be a filehandle, coderef, or undef.
1535
#
1536
#	Returned argument is a reference to an array of arrays, ie: 
1537
#	$output = [ 
1538
#			[ $index, 'OUTPUT'|'LITERAL', $output_line ] ,
1539
#			[ $index, 'OUTPUT'|'LITERAL', $output_line ] ,
1540
#			... 	# etc,
1541
#	];
1542
 
1543
sub _read_line {
1544
	my $self 	= shift;	
1545
	my $sh		= $self->Socket;
1546
	my $literal_callback    = shift;
1547
	my $output_callback = shift;
1548
 
1549
	unless ($self->IsConnected and $self->Socket) {
1550
		$self->LastError("NO Not connected.\n");
1551
		carp "Not connected" if $^W;
1552
		return undef;
1553
	}
1554
 
1555
	my $iBuffer	= ""; 
1556
	my $oBuffer	= [];
1557
	my $count	= 0;
1558
	my $index	= $self->_next_index($self->Transaction);
1559
	my $rvec 	= my $ready = my $errors = 0; 
1560
	my $timeout	= $self->Timeout;
1561
 
1562
	my $readlen 	= 1;
1563
	my $fast_io	= $self->Fast_io;	# Remember setting to reduce future method calls
1564
 
1565
	if ( $fast_io ) {
1566
 
1567
		# set fcntl if necessary:
1568
		exists $self->{_fcntl} or $self->Fast_io($fast_io);
1569
		$readlen = $self->{Buffer}||4096;
1570
	}
1571
	until (	
1572
		# there's stuff in output buffer:
1573
		scalar(@$oBuffer)	and 			
1574
 
1575
		# the last thing there has cr-lf:
1576
                $oBuffer->[-1][DATA] =~ /\x0d\x0a$/  and     
1577
 
1578
		# that thing is an output line:
1579
                $oBuffer->[-1][TYPE]    eq "OUTPUT"  and     
1580
 
1581
		# and the input buffer has been MT'ed:
1582
		$iBuffer		eq "" 		
1583
 
1584
	) {
1585
              my $transno = $self->Transaction;  # used below in several places
1586
		if ($timeout) {
1587
			vec($rvec, fileno($self->Socket), 1) = 1;
1588
			my @ready = $self->{_select}->can_read($timeout) ;
1589
			unless ( @ready ) {
1590
				$self->LastError("Tag $transno: " .
1591
					"Timeout after $timeout seconds " .
1592
					"waiting for data from server\n");	
1593
				$self->_record($transno,
1594
					[	$self->_next_index($transno),
1595
						"ERROR",
1596
						"$transno * NO Timeout after ".
1597
						"$timeout seconds " .
1598
						"during read from " .
1599
						"server\x0d\x0a"
1600
					]
1601
				);
1602
				$self->LastError(
1603
					"Timeout after $timeout seconds " .
1604
					"during read from server\x0d\x0a"
1605
				);
1606
				return undef;
1607
			}
1608
		}
1609
 
1610
		local($^W) = undef;	# Now quiet down warnings
1611
 
1612
		# read "$readlen" bytes (or less):
1613
              # need to check return code from $self->_sysread 
1614
  	      #	in case other end has shut down!!!
1615
              my $ret = $self->_sysread( $sh, \$iBuffer, $readlen, length($iBuffer)) ;
1616
	      # $self->_debug("Read so far: $iBuffer<<END>>\n");
1617
              if($timeout and ! defined($ret)) { # Blocking read error...
1618
                  my $msg = "Error while reading data from server: $!\x0d\x0a";
1619
                  $self->_record($transno,
1620
                                 [ $self->_next_index($transno),
1621
                                   "ERROR", "$transno * NO $msg "
1622
                                   ]);
1623
                  $@ = "$msg";
1624
                  return undef;
1625
              }
1626
              elsif(defined($ret) and $ret == 0) {    # Caught EOF...
1627
                  my $msg="Socket closed while reading data from server.\x0d\x0a";
1628
                  $self->_record($transno,
1629
                                 [ $self->_next_index($transno),
1630
                                   "ERROR", "$transno * NO $msg "
1631
                                   ]);
1632
                  $@ = "$msg";
1633
                  return undef;
1634
              }
1635
              # successfully wrote to other end, keep going...
1636
              $count += $ret if defined($ret);
1637
		LINES: while ( $iBuffer =~ s/^(.*?\x0d?\x0a)// ) {
1638
		   my $current_line = $1;
1639
 
1640
		   # $self->_debug("BUFFER: pulled from buffer: <BEGIN>${current_line}<END>\n" .
1641
		   # 	"and left with buffer contents of: <BEGIN>${iBuffer}<END>\n");
1642
 
1643
		   LITERAL: if ($current_line =~ s/\{(\d+)\}\x0d\x0a$//) {
1644
			# This part handles IMAP "Literals", 
1645
			# which according to rfc2060 look something like this:
1646
			# [tag]|* BLAH BLAH {nnn}\r\n
1647
			# [nnn bytes of literally transmitted stuff]
1648
			# [part of line that follows literal data]\r\n
1649
 
1650
			# Set $len to be length of impending literal:
1651
			my $len = $1 ;
1652
 
1653
			$self->_debug("LITERAL: received literal in line ".
1654
				"$current_line of length $len; ".
1655
				"attempting to ".
1656
				"retrieve from the " . length($iBuffer) . 
1657
				" bytes in: $iBuffer<END_OF_iBuffer>\n");
1658
 
1659
			# Xfer up to $len bytes from front of $iBuffer to $litstring: 
1660
			my $litstring = substr($iBuffer, 0, $len);
1661
			$iBuffer = substr($iBuffer, length($litstring), 
1662
					length($iBuffer) - length($litstring) ) ;
1663
 
1664
			# Figure out what's left to read (i.e. what part of 
1665
			# literal wasn't in buffer):
1666
			my $remainder_count = $len - length($litstring);
1667
			my $callback_value = "";
1668
 
1669
			if ( defined($literal_callback) ) 	{	
1670
				if 	( $literal_callback =~ /GLOB/) 	{	
1671
					print $literal_callback $litstring ;
1672
					$litstring = "";
1673
				} elsif ($literal_callback =~ /CODE/ ) {
1674
					# Don't do a thing
1675
 
1676
				} else 	{
1677
					$self->LastError(
1678
						ref($literal_callback) . 
1679
						" is an invalid callback type; " .
1680
						"must be a filehandle or coderef\n"
1681
					); 
1682
				}
1683
 
1684
 
1685
			}
1686
			if ($remainder_count > 0 and $timeout) {
1687
				# If we're doing timeouts then here we set up select 
1688
				# and wait for data from the the IMAP socket.
1689
				vec($rvec, fileno($self->Socket), 1) = 1;
1690
				unless ( CORE::select( $ready = $rvec, 
1691
							undef, 
1692
							$errors = $rvec, 
1693
							$timeout) 
1694
				) {	
1695
					# Select failed; that means bad news. 
1696
					# Better tell someone.
1697
					$self->LastError("Tag " . $transno . 
1698
						": Timeout waiting for literal data " .
1699
						"from server\n");	
1700
					carp "Tag " . $transno . 
1701
						": Timeout waiting for literal data " .
1702
						"from server\n"
1703
						if $self->Debug or $^W;	
1704
					return undef;
1705
				}	
1706
			} 
1707
 
1708
			fcntl($sh, F_SETFL, $self->{_fcntl}) 
1709
				if $fast_io and defined($self->{_fcntl});
1710
			while ( $remainder_count > 0 ) {	   # As long as not done,
1711
				$self->_debug("Still need $remainder_count to " .
1712
					"complete literal string\n");
1713
				my $ret	= $self->_sysread(   	   # bytes read
1714
						$sh, 		   # IMAP handle 
1715
						\$litstring,	   # place to read into
1716
						$remainder_count,  # bytes left to read
1717
						length($litstring) # offset to read into
1718
				) ;
1719
				$self->_debug("Received ret=$ret and buffer = " .
1720
				"\n$litstring<END>\nwhile processing LITERAL\n");
1721
				if ( $timeout and !defined($ret)) { # possible timeout
1722
					$self->_record($transno, [ 
1723
						$self->_next_index($transno),
1724
						"ERROR",
1725
						"$transno * NO Error reading data " .
1726
						"from server: $!\n"
1727
						]
1728
					);
1729
					return undef;
1730
				} elsif ( $ret == 0 and eof($sh) ) {
1731
					$self->_record($transno, [ 
1732
						$self->_next_index($transno),
1733
						"ERROR",
1734
						"$transno * ".
1735
						"BYE Server unexpectedly " .
1736
						"closed connection: $!\n"	
1737
						]
1738
					);
1739
					$self->State(Unconnected);
1740
					return undef;
1741
				}
1742
				# decrement remaining bytes by amt read:
1743
				$remainder_count -= $ret;	   
1744
 
1745
				if ( length($litstring) > $len ) {
1746
                                    # copy the extra struff into the iBuffer:
1747
                                    $iBuffer = substr(
1748
                                        $litstring,   
1749
                                        $len, 
1750
                                        length($litstring) - $len 
1751
                                    );
1752
                                    $litstring = substr($litstring, 0, $len) ;
1753
                                }
1754
 
1755
				if ( defined($literal_callback) ) {
1756
					if ( $literal_callback =~ /GLOB/ ) {
1757
						print $literal_callback $litstring;
1758
						$litstring = "";
1759
					} 
1760
				}
1761
 
1762
			}
1763
			$literal_callback->($litstring) 
1764
				if defined($litstring) and 
1765
				$literal_callback =~ /CODE/;
1766
 
1767
			$self->Fast_io($fast_io) if $fast_io;
1768
 
1769
		# Now let's make sure there are no IMAP server output lines 
1770
		# (i.e. [tag|*] BAD|NO|OK Text) embedded in the literal string
1771
		# (There shouldn't be but I've seen it done!), but only if
1772
		# EnableServerResponseInLiteral is set to true
1773
 
1774
			my $embedded_output = 0;
1775
			my $lastline = ( split(/\x0d?\x0a/,$litstring))[-1] 
1776
				if $litstring;
1777
 
1778
			if ( 	$self->EnableServerResponseInLiteral and
1779
				$lastline and 
1780
				$lastline =~ /^(?:\*|(\d+))\s(BAD|NO|OK)/i 
1781
			) {
1782
			  $litstring =~ s/\Q$lastline\E\x0d?\x0a//;
1783
			  $embedded_output++;
1784
 
1785
			  $self->_debug("Got server output mixed in " .
1786
					"with literal: $lastline\n"
1787
			  ) 	if $self->Debug;
1788
 
1789
			}
1790
		  	# Finally, we need to stuff the literal onto the 
1791
			# end of the oBuffer:
1792
			push @$oBuffer, [ $index++, "OUTPUT" , $current_line],
1793
					[ $index++, "LITERAL", $litstring   ];
1794
			push @$oBuffer,	[ $index++, "OUTPUT",  $lastline    ] 
1795
					if $embedded_output;
1796
 
1797
		  } else { 
1798
			push @$oBuffer, [ $index++, "OUTPUT" , $current_line ]; 
1799
		  }
1800
 
1801
		}
1802
		#$self->_debug("iBuffer is now: $iBuffer<<END OF BUFFER>>\n");
1803
	}
1804
	#	_debug $self, "Buffer is now $buffer\n";
1805
      _debug $self, "Read: " . join("",map {$_->[DATA]} @$oBuffer) ."\n" 
1806
		if $self->Debug;
1807
	return scalar(@$oBuffer) ? $oBuffer : undef ;
1808
}
1809
 
1810
sub _sysread {
1811
	my $self = shift @_;
1812
	if ( exists $self->{Readmethod} )  {
1813
		return $self->Readmethod->($self,@_) ;
1814
	} else {
1815
		my($handle,$buffer,$count,$offset) = @_;
1816
		return sysread( $handle, $$buffer, $count, $offset);
1817
	}
1818
}
1819
 
1820
=begin obsolete
1821
 
1822
sub old_read_line {
1823
	my $self 	= shift;	
1824
	my $sh		= $self->Socket;
1825
	my $literal_callback    = shift;
1826
	my $output_callback = shift;
1827
 
1828
	unless ($self->IsConnected and $self->Socket) {
1829
		$self->LastError("NO Not connected.\n");
1830
		carp "Not connected" if $^W;
1831
		return undef;
1832
	}
1833
 
1834
	my $iBuffer	= ""; 
1835
	my $oBuffer	= [];
1836
	my $count	= 0;
1837
	my $index	= $self->_next_index($self->Transaction);
1838
	my $rvec 	= my $ready = my $errors = 0; 
1839
	my $timeout	= $self->Timeout;
1840
 
1841
	my $readlen 	= 1;
1842
	my $fast_io	= $self->Fast_io;	# Remember setting to reduce future method calls
1843
 
1844
	if ( $fast_io ) {
1845
 
1846
		# set fcntl if necessary:
1847
		exists $self->{_fcntl} or $self->Fast_io($fast_io);
1848
		$readlen = $self->{Buffer}||4096;
1849
	}
1850
	until (	
1851
		# there's stuff in output buffer:
1852
		scalar(@$oBuffer)	and 			
1853
 
1854
		# the last thing there has cr-lf:
1855
                $oBuffer->[-1][DATA] =~ /\x0d\x0a$/  and     
1856
 
1857
		# that thing is an output line:
1858
                $oBuffer->[-1][TYPE]    eq "OUTPUT"  and     
1859
 
1860
		# and the input buffer has been MT'ed:
1861
		$iBuffer		eq "" 		
1862
 
1863
	) {
1864
              my $transno = $self->Transaction;  # used below in several places
1865
		if ($timeout) {
1866
			vec($rvec, fileno($self->Socket), 1) = 1;
1867
			my @ready = $self->{_select}->can_read($timeout) ;
1868
			unless ( @ready ) {
1869
				$self->LastError("Tag $transno: " .
1870
					"Timeout after $timeout seconds " .
1871
					"waiting for data from server\n");	
1872
				$self->_record($transno,
1873
					[	$self->_next_index($transno),
1874
						"ERROR",
1875
						"$transno * NO Timeout after ".
1876
						"$timeout seconds " .
1877
						"during read from " .
1878
						"server\x0d\x0a"
1879
					]
1880
				);
1881
				$self->LastError(
1882
					"Timeout after $timeout seconds " .
1883
					"during read from server\x0d\x0a"
1884
				);
1885
				return undef;
1886
			}
1887
		}
1888
 
1889
		local($^W) = undef;	# Now quiet down warnings
1890
 
1891
		# read "$readlen" bytes (or less):
1892
              # need to check return code from sysread in case other end has shut down!!!
1893
              my $ret = sysread( $sh, $iBuffer, $readlen, length($iBuffer)) ;
1894
		# $self->_debug("Read so far: $iBuffer<<END>>\n");
1895
              if($timeout and ! defined($ret)) { # Blocking read error...
1896
                  my $msg = "Error while reading data from server: $!\x0d\x0a";
1897
                  $self->_record($transno,
1898
                                 [ $self->_next_index($transno),
1899
                                   "ERROR", "$transno * NO $msg "
1900
                                   ]);
1901
                  $@ = "$msg";
1902
                  return undef;
1903
              }
1904
              elsif(defined($ret) and $ret == 0) {    # Caught EOF...
1905
                  my $msg="Socket closed while reading data from server.\x0d\x0a";
1906
                  $self->_record($transno,
1907
                                 [ $self->_next_index($transno),
1908
                                   "ERROR", "$transno * NO $msg "
1909
                                   ]);
1910
                  $@ = "$msg";
1911
                  return undef;
1912
              }
1913
              # successfully wrote to other end, keep going...
1914
              $count += $ret if defined($ret);
1915
		LINES: while ( $iBuffer =~ s/^(.*?\x0d?\x0a)// ) {
1916
		   my $current_line = $1;
1917
 
1918
		   # $self->_debug("BUFFER: pulled from buffer: <BEGIN>${current_line}<END>\n" .
1919
		   # 	"and left with buffer contents of: <BEGIN>${iBuffer}<END>\n");
1920
 
1921
		   LITERAL: if ($current_line =~ s/\{(\d+)\}\x0d\x0a$//) {
1922
			# This part handles IMAP "Literals", which according to rfc2060 look something like this:
1923
			# [tag]|* BLAH BLAH {nnn}\r\n
1924
			# [nnn bytes of literally transmitted stuff]
1925
			# [part of line that follows literal data]\r\n
1926
 
1927
			# Set $len to be length of impending literal:
1928
			my $len = $1 ;
1929
 
1930
			$self->_debug("LITERAL: received literal in line $current_line of length $len; ".
1931
			"attempting to ".
1932
			"retrieve from the " . length($iBuffer) . " bytes in: $iBuffer<END_OF_iBuffer>\n");
1933
 
1934
			# Transfer up to $len bytes from front of $iBuffer to $litstring: 
1935
			my $litstring = substr($iBuffer, 0, $len);
1936
			$iBuffer = substr($iBuffer, length($litstring), length($iBuffer) - length($litstring) ) ;
1937
 
1938
			# Figure out what's left to read (i.e. what part of literal wasn't in buffer):
1939
			my $remainder_count = $len - length($litstring);
1940
			my $callback_value = "";
1941
 
1942
			if ( defined($literal_callback) ) 	{	
1943
				if 	( $literal_callback =~ /GLOB/) 	{	
1944
					print $literal_callback $litstring ;
1945
					$litstring = "";
1946
				} elsif ($literal_callback =~ /CODE/ ) {
1947
					# Don't do a thing
1948
 
1949
				} else 	{
1950
					$self->LastError(
1951
						ref($literal_callback) . 
1952
						" is an invalid callback type; must be a filehandle or coderef"
1953
					); 
1954
				}
1955
 
1956
 
1957
			}
1958
			if ($remainder_count > 0 and $timeout) {
1959
				# If we're doing timeouts then here we set up select and wait for data from the
1960
				# the IMAP socket.
1961
				vec($rvec, fileno($self->Socket), 1) = 1;
1962
				unless ( CORE::select( $ready = $rvec, 
1963
							undef, 
1964
							$errors = $rvec, 
1965
							$timeout) 
1966
				) {	
1967
					# Select failed; that means bad news. 
1968
					# Better tell someone.
1969
					$self->LastError("Tag " . $transno . 
1970
						": Timeout waiting for literal data " .
1971
						"from server\n");	
1972
					carp "Tag " . $transno . 
1973
						": Timeout waiting for literal data " .
1974
						"from server\n"
1975
						if $self->Debug or $^W;	
1976
					return undef;
1977
				}	
1978
			} 
1979
 
1980
			fcntl($sh, F_SETFL, $self->{_fcntl}) 
1981
				if $fast_io and defined($self->{_fcntl});
1982
			while ( $remainder_count > 0 ) {	   # As long as not done,
1983
 
1984
				my $ret	= sysread(	   	   # bytes read
1985
						$sh, 		   # IMAP handle 
1986
						$litstring,	   # place to read into
1987
						$remainder_count,  # bytes left to read
1988
						length($litstring) # offset to read into
1989
				) ;
1990
				if ( $timeout and !defined($ret)) { # possible timeout
1991
					$self->_record($transno, [ 
1992
						$self->_next_index($transno),
1993
						"ERROR",
1994
						"$transno * NO Error reading data " .
1995
						"from server: $!\n"
1996
						]
1997
					);
1998
					return undef;
1999
				} elsif ( $ret == 0 and eof($sh) ) {
2000
					$self->_record($transno, [ 
2001
						$self->_next_index($transno),
2002
						"ERROR",
2003
						"$transno * ".
2004
						"BYE Server unexpectedly " .
2005
						"closed connection: $!\n"	
2006
						]
2007
					);
2008
					$self->State(Unconnected);
2009
					return undef;
2010
				}
2011
				# decrement remaining bytes by amt read:
2012
				$remainder_count -= $ret;	   
2013
 
2014
				if ( defined($literal_callback) ) {
2015
					if ( $literal_callback =~ /GLOB/ ) {
2016
						print $literal_callback $litstring;
2017
						$litstring = "";
2018
					} 
2019
				}
2020
 
2021
			}
2022
			$literal_callback->($litstring) 
2023
				if defined($litstring) and 
2024
				$literal_callback =~ /CODE/;
2025
 
2026
			$self->Fast_io($fast_io) if $fast_io;
2027
 
2028
		# Now let's make sure there are no IMAP server output lines 
2029
		# (i.e. [tag|*] BAD|NO|OK Text) embedded in the literal string
2030
		# (There shouldn't be but I've seen it done!), but only if
2031
		# EnableServerResponseInLiteral is set to true
2032
 
2033
			my $embedded_output = 0;
2034
			my $lastline = ( split(/\x0d?\x0a/,$litstring))[-1] 
2035
				if $litstring;
2036
 
2037
			if ( 	$self->EnableServerResponseInLiteral and
2038
				$lastline and 
2039
				$lastline =~ /^(?:\*|(\d+))\s(BAD|NO|OK)/i 
2040
			) {
2041
			  $litstring =~ s/\Q$lastline\E\x0d?\x0a//;
2042
			  $embedded_output++;
2043
 
2044
			  $self->_debug("Got server output mixed in " .
2045
					"with literal: $lastline\n"
2046
			  ) 	if $self->Debug;
2047
 
2048
			}
2049
		  	# Finally, we need to stuff the literal onto the 
2050
			# end of the oBuffer:
2051
			push @$oBuffer, [ $index++, "OUTPUT" , $current_line],
2052
					[ $index++, "LITERAL", $litstring   ];
2053
			push @$oBuffer,	[ $index++, "OUTPUT",  $lastline    ] 
2054
					if $embedded_output;
2055
 
2056
		  } else { 
2057
			push @$oBuffer, [ $index++, "OUTPUT" , $current_line ]; 
2058
		  }
2059
 
2060
		}
2061
		#$self->_debug("iBuffer is now: $iBuffer<<END OF BUFFER>>\n");
2062
	}
2063
	#	_debug $self, "Buffer is now $buffer\n";
2064
      _debug $self, "Read: " . join("",map {$_->[DATA]} @$oBuffer) ."\n" 
2065
		if $self->Debug;
2066
	return scalar(@$oBuffer) ? $oBuffer : undef ;
2067
}
2068
 
2069
=end obsolete
2070
 
2071
=cut
2072
 
2073
 
2074
sub Report {
2075
	my $self = shift;
2076
#	$self->_debug( "Dumper: " . Data::Dumper::Dumper($self) . 
2077
#			"\nReporting on following keys: " . join(", ",keys %{$self->{'History'}}). "\n");
2078
	return 	map { 
2079
                      map { $_->[DATA] } @{$self->{"History"}{$_}} 
2080
	}		sort { $a <=> $b } keys %{$self->{"History"}}
2081
	;
2082
}
2083
 
2084
 
2085
sub Results {
2086
	my $self 	= shift	;
2087
	my $transaction = shift||$self->Count;
2088
 
2089
	return wantarray 							? 
2090
              map {$_->[DATA] }       @{$self->{"History"}{$transaction}}     : 
2091
              [ map {$_->[DATA] }     @{$self->{"History"}{$transaction}} ]   ;
2092
}
2093
 
2094
 
2095
sub LastIMAPCommand {
2096
      my @a = map { $_->[DATA] } @{$_[0]->{"History"}{$_[1]||$_[0]->Transaction}};
2097
	return shift @a;
2098
}
2099
 
2100
 
2101
sub History {
2102
      my @a = map { $_->[DATA] } @{$_[0]->{"History"}{$_[1]||$_[0]->Transaction}};
2103
	shift @a;
2104
	return wantarray ? @a : \@a ;
2105
 
2106
}
2107
 
2108
sub Escaped_results {
2109
	my @a;
2110
	foreach  my $line (@{$_[0]->{"History"}{$_[1]||$_[0]->Transaction}} ) {
2111
		if (  defined($line) and $_[0]->_is_literal($line) ) { 
2112
			$line->[DATA] =~ s/([\\\(\)"\x0d\x0a])/\\$1/g ;
2113
			push @a, qq("$line->[DATA]");
2114
		} else {
2115
      			push @a, $line->[DATA] ;
2116
		}
2117
	}
2118
	# $a[0] is the ALWAYS the command ; I make sure of that in _imap_command
2119
	shift @a;	
2120
	return wantarray ? @a : \@a ;
2121
}
2122
 
2123
sub Unescape {
2124
	shift @_ if $_[1];
2125
	my $whatever = shift;
2126
	$whatever =~ s/\\([\\\(\)"\x0d\x0a])/$1/g if defined $whatever;
2127
	return $whatever;
2128
}
2129
 
2130
sub logout {
2131
	my $self = shift;
2132
	my $string = "LOGOUT";
2133
	$self->_imap_command($string) ; 
2134
	$self->{Folders} = undef;
2135
	$self->{_IMAP4REV1} = undef;
2136
	eval {$self->Socket->close if defined($self->Socket)} ; 
2137
	$self->{Socket} = undef;
2138
	$self->State(Unconnected);
2139
	return $self;
2140
}
2141
 
2142
sub folders {
2143
        my $self = shift;
2144
	my $what = shift ;
2145
        return wantarray ?      @{$self->{Folders}} :
2146
                                $self->{Folders} 
2147
                if ref($self->{Folders}) and !$what;
2148
 
2149
        my @folders ;  
2150
	my @list = $self->list(undef,( $what? "$what" . $self->separator($what) . "*" : undef ) );
2151
	push @list, $self->list(undef, $what) if $what and $self->exists($what) ;
2152
	# my @list = 
2153
	# foreach (@list) { $self->_debug("Pushing $_\n"); }
2154
	my $m;
2155
 
2156
	for ($m = 0; $m < scalar(@list); $m++ ) {
2157
		# $self->_debug("Folders: examining $list[$m]\n");
2158
 
2159
		if ($list[$m] && $list[$m]  !~ /\x0d\x0a$/ ) {
2160
			$self->_debug("folders: concatenating $list[$m] and " . $list[$m+1] . "\n") ;
2161
			$list[$m] .= $list[$m+1] ;
2162
			$list[$m+1] = "";	
2163
			$list[$m] .= "\x0d\x0a" unless $list[$m] =~ /\x0d\x0a$/;
2164
		}
2165
 
2166
 
2167
 
2168
		push @folders, $1||$2 
2169
			if $list[$m] =~
2170
                        /       ^\*\s+LIST               # * LIST
2171
                                \s+\([^\)]*\)\s+         # (Flags)
2172
                                (?:"[^"]*"|NIL)\s+	 # "delimiter" or NIL
2173
                                (?:"([^"]*)"|(.*))\x0d\x0a$  # Name or "Folder name"
2174
                        /ix;
2175
		#$folders[-1] = '"' . $folders[-1] . '"' 
2176
		#	if $1 and !$self->exists($folders[-1]) ;
2177
		# $self->_debug("folders: line $list[$m]: 1=$1 and 2=$2\n");
2178
        } 
2179
 
2180
        # for my $f (@folders) { $f =~ s/^\\FOLDER LITERAL:://;}
2181
	my @clean = (); my %memory = ();
2182
	foreach my $f (@folders) { push @clean, $f unless $memory{$f}++ }
2183
        $self->{Folders} = \@clean unless $what;
2184
 
2185
        return wantarray ? @clean : \@clean ;
2186
}
2187
 
2188
 
2189
sub exists {
2190
	my ($self,$what) = (shift,shift);
2191
	return $self if $self->STATUS($self->Massage($what),"(MESSAGES)");
2192
	return undef;
2193
}
2194
 
2195
# Updated to handle embedded literal strings
2196
sub get_bodystructure {
2197
	my($self,$msg) = @_;
2198
	unless ( eval {require Mail::IMAPClient::BodyStructure ; 1 } ) {
2199
		$self->LastError("Unable to use get_bodystructure: $@\n");
2200
		return undef;
2201
	}
2202
	my @out = $self->fetch($msg,"BODYSTRUCTURE");
2203
	my $bs = "";
2204
	my $output = grep(	
2205
		/BODYSTRUCTURE \(/i,  @out	 # Wee! ;-)
2206
	); 
2207
	if ( $output =~ /\r\n$/ ) {
2208
		eval { $bs = Mail::IMAPClient::BodyStructure->new( $output )};  
2209
	} else {
2210
		$self->_debug("get_bodystructure: reassembling original response\n");
2211
		my $start = 0;
2212
		foreach my $o (@{$self->{"History"}{$self->Transaction}}) {
2213
			next unless $self->_is_output_or_literal($o);
2214
			$self->_debug("o->[DATA] is ".$o->[DATA]."\n");
2215
			next unless $start or 
2216
				$o->[DATA] =~ /BODYSTRUCTURE \(/i and ++$start;	  # Hi, vi! ;-)
2217
			if ( length($output) and $self->_is_literal($o) ) {
2218
				my $data = $o->[DATA];
2219
				$data =~ s/"/\\"/g;
2220
				$data =~ s/\(/\\\(/g;
2221
				$data =~ s/\)/\\\)/g;
2222
				$output .= '"'.$data.'"';
2223
			} else {
2224
				$output .= $o->[DATA] ;
2225
			}
2226
			$self->_debug("get_bodystructure: reassembled output=$output<END>\n");
2227
		}
2228
		eval { $bs = Mail::IMAPClient::BodyStructure->new( $output )};  
2229
	}
2230
	$self->_debug("get_bodystructure: msg $msg returns this ref: ". 
2231
		( $bs ? " $bs" : " UNDEF" ) 
2232
		."\n");
2233
	return $bs;
2234
}
2235
 
2236
# Updated to handle embedded literal strings 
2237
sub get_envelope {
2238
	my($self,$msg) = @_;
2239
	unless ( eval {require Mail::IMAPClient::BodyStructure ; 1 } ) {
2240
		$self->LastError("Unable to use get_envelope: $@\n");
2241
		return undef;
2242
	}
2243
	my @out = $self->fetch($msg,"ENVELOPE");
2244
	my $bs = "";
2245
	my $output = grep(	
2246
		/ENVELOPE \(/i,  @out	 # Wee! ;-)
2247
	); 
2248
	if ( $output =~ /\r\n$/ ) {
2249
		eval { 
2250
		 $bs = Mail::IMAPClient::BodyStructure::Envelope->new($output)
2251
		};
2252
	} else {
2253
		$self->_debug("get_envelope: " .
2254
			"reassembling original response\n");
2255
		my $start = 0;
2256
		foreach my $o (@{$self->{"History"}{$self->Transaction}}) {
2257
			next unless $self->_is_output_or_literal($o);
2258
			$self->_debug("o->[DATA] is ".$o->[DATA]."\n");
2259
			next unless $start or 
2260
				$o->[DATA] =~ /ENVELOPE \(/i and ++$start;
2261
				# Hi, vi! ;-)
2262
			if ( length($output) and $self->_is_literal($o) ) {
2263
				my $data = $o->[DATA];
2264
				$data =~ s/"/\\"/g;
2265
				$data =~ s/\(/\\\(/g;
2266
				$data =~ s/\)/\\\)/g;
2267
				$output .= '"'.$data.'"';
2268
			} else {
2269
				$output .= $o->[DATA] ;
2270
			}
2271
			$self->_debug("get_envelope: " .
2272
				"reassembled output=$output<END>\n");
2273
		}
2274
		eval { 
2275
		  $bs=Mail::IMAPClient::BodyStructure::Envelope->new($output)
2276
		};  
2277
	}
2278
	$self->_debug("get_envelope: msg $msg returns this ref: ". 
2279
		( $bs ? " $bs" : " UNDEF" ) 
2280
		."\n");
2281
	return $bs;
2282
}
2283
 
2284
=begin obsolete
2285
 
2286
sub old_get_envelope {
2287
	my($self,$msg) = @_;
2288
	unless ( eval {require Mail::IMAPClient::BodyStructure ; 1 } ) {
2289
		$self->LastError("Unable to use get_envelope: $@\n");
2290
		return undef;
2291
	}
2292
	my $bs = "";
2293
	my @out = $self->fetch($msg,"ENVELOPE");
2294
	my $output = grep(	
2295
		/ENVELOPE \(/i,  @out	 # Wee! ;-)
2296
	); 
2297
	if ( $output =~ /\r\n$/ ) {
2298
		eval { $bs = Mail::IMAPClient::BodyStructure::Envelope->new( $output )};  
2299
	} else {
2300
		$self->_debug("get_envelope: reassembling original response\n");
2301
		my $start = 0;
2302
		foreach my $o (@{$self->{"History"}{$self->Transaction}}) {
2303
			next unless $self->_is_output_or_literal($o);
2304
			$self->_debug("o->[DATA] is ".$o->[DATA]."\n");
2305
			next unless $start or 
2306
				$o->[DATA] =~ /ENVELOPE \(/i and ++$start;	  # Hi, vi! ;-)
2307
			if ( length($output) and $self->_is_literal($o) ) {
2308
				my $data = $o->[DATA];
2309
				$data =~ s/"/\\"/g;
2310
				$data =~ s/\(/\\\(/g;
2311
				$data =~ s/\)/\\\)/g;
2312
				$output .= '"'.$data.'"';
2313
			} else {
2314
				$output .= $o->[DATA] ;
2315
			}
2316
		}
2317
		$self->_debug("get_envelope: reassembled output=$output<END>\n");
2318
		eval { $bs = Mail::IMAPClient::BodyStructure->new( $output )};  
2319
	}
2320
	$self->_debug("get_envelope: msg $msg returns this ref: ". 
2321
		( $bs ? " $bs" : " UNDEF" ) 
2322
		."\n");
2323
	return $bs;
2324
}
2325
 
2326
=end obsolete
2327
 
2328
=cut
2329
 
2330
 
2331
sub fetch {
2332
 
2333
	my $self = shift;
2334
	my $what = shift||"ALL";
2335
	#ref($what) and $what = join(",",@$what);	
2336
	if ( $what eq 'ALL' ) {
2337
		$what = $self->Range($self->messages );
2338
	} elsif (ref($what) or $what =~ /^[,:\d]+\w*$/)  {
2339
		$what = $self->Range($what);	
2340
	}
2341
	$self->_imap_command( ( $self->Uid ? "UID " : "" ) .
2342
				"FETCH $what" . ( @_ ? " " . join(" ",@_) : '' )
2343
	) 	 					or return undef;
2344
	return wantarray ? 	$self->History($self->Count) 	: 
2345
                              [ map { $_->[DATA] } @{$self->{'History'}{$self->Count}} ];
2346
 
2347
}
2348
 
2349
 
2350
sub fetch_hash {
2351
	my $self = shift;
2352
	my $hash = ref($_[-1]) ? pop @_ : {};
2353
	my @words = @_;
2354
	for (@words) { 
2355
		s/([\( ])FAST([\) ])/${1}FLAGS INTERNALDATE RFC822\.SIZE$2/i  ;
2356
		s/([\( ])FULL([\) ])/${1}FLAGS INTERNALDATE RFC822\.SIZE ENVELOPE BODY$2/i  ;
2357
	}
2358
	my $msgref = scalar($self->messages);
2359
	my $output = scalar($self->fetch($msgref,"(" . join(" ",@_) . ")")) 
2360
	; #	unless grep(/\b(?:FAST|FULL)\b/i,@words);
2361
	my $x;
2362
	for ($x = 0;  $x <= $#$output ; $x++) {
2363
		my $entry = {};
2364
		my $l = $output->[$x];
2365
		if ($self->Uid) {	
2366
			my($uid) = $l =~ /\((?:.* )?UID (\d+).*\)/i;
2367
			next unless $uid;
2368
			if ( exists $hash->{$uid} ) {
2369
				$entry = $hash->{$uid} ;
2370
			} else {
2371
				$hash->{$uid} ||= $entry;
2372
			}
2373
		} else {
2374
			my($mid) = $l =~ /^\* (\d+) FETCH/i;
2375
			next unless $mid;
2376
			if ( exists $hash->{$mid} ) {
2377
				$entry = $hash->{$mid} ;
2378
			} else {
2379
				$hash->{$mid} ||= $entry;
2380
			}
2381
		}
2382
 
2383
		foreach my $w (@words) {
2384
		   if ( $l =~ /\Q$w\E\s*$/i ) {
2385
			$entry->{$w} = $output->[$x+1];
2386
			$entry->{$w} =~ s/(?:\x0a?\x0d)+$//g;
2387
			chomp $entry->{$w};
2388
		   } else {
2389
			$l =~ /\( 	    # open paren followed by ... 
2390
				(?:.*\s)?   # ...optional stuff and a space
2391
				\Q$w\E\s    # escaped fetch field<sp>
2392
				(?:"	    # then: a dbl-quote
2393
				  (\\.|   # then bslashed anychar(s) or ...
2394
				   [^"]+)   # ... nonquote char(s)
2395
				"|	    # then closing quote; or ...
2396
				\(	    # ...an open paren
2397
				  (\\.|     # then bslashed anychar or ...
2398
				   [^\)]+)  # ... non-close-paren char
2399
				\)|	    # then closing paren; or ...
2400
				(\S+))	    # unquoted string
2401
				(?:\s.*)?   # possibly followed by space-stuff
2402
				\)	    # close paren
2403
			/xi;
2404
			$entry->{$w}=defined($1)?$1:defined($2)?$2:$3;
2405
		   }
2406
		}
2407
	}
2408
	return wantarray ? %$hash : $hash;
2409
}
2410
sub AUTOLOAD {
2411
 
2412
	my $self = shift;
2413
	return undef if $Mail::IMAPClient::AUTOLOAD =~ /DESTROY$/;
2414
	delete $self->{Folders}  ;
2415
	my $autoload = $Mail::IMAPClient::AUTOLOAD;
2416
	$autoload =~ s/.*:://;
2417
	if (	
2418
			$^W
2419
		and	$autoload =~ /^[a-z]+$/
2420
		and	$autoload !~ 
2421
				/^	(?:
2422
						store	 |
2423
						copy	 |
2424
						subscribe|
2425
						create	 |
2426
						delete	 |
2427
						close	 |
2428
						expunge
2429
					)$
2430
				/x 
2431
	) {
2432
		carp 	"$autoload is all lower-case. " .
2433
			"May conflict with future methods. " .
2434
			"Change method name to be mixed case or all upper case to ensure " .
2435
			"upward compatability"
2436
	}
2437
	if (scalar(@_)) {
2438
		my @a = @_;
2439
		if (	
2440
			$autoload =~ 
2441
				/^(?:subscribe|delete|myrights)$/i
2442
		) {
2443
			$a[-1] = $self->Massage($a[-1]) ;
2444
		} elsif (	
2445
			$autoload =~ 
2446
				/^(?:create)$/i
2447
		) {
2448
			$a[0] = $self->Massage($a[0]) ;
2449
		} elsif (
2450
			$autoload =~ /^(?:store|copy)$/i
2451
		) {
2452
			$autoload = "UID $autoload"
2453
				if $self->Uid;
2454
		} elsif (
2455
			$autoload =~ /^(?:expunge)$/i and defined($_[0])
2456
		) {
2457
			my $old;
2458
			if ( $_[0] ne $self->Folder ) {
2459
				$old = $self->Folder; $self->select($_[0]); 
2460
			} 	
2461
			my $succ = $self->_imap_command(qq/$autoload/) ;
2462
			$self->select($old);
2463
			return undef unless $succ;
2464
			return wantarray ? 	$self->History($self->Count) 	: 
2465
                                              map {$_->[DATA]}@{$self->{'History'}{$self->Count}}     ;
2466
 
2467
		}
2468
		$self->_debug("Autoloading: $autoload " . ( @a ? join(" ",@a):"" ) ."\n" )
2469
			if $self->Debug;
2470
		return undef 
2471
			unless $self->_imap_command(
2472
			 	qq/$autoload/ .  ( @a ? " " . join(" ",@a) : "" )
2473
			)  ;
2474
	} else {
2475
		$self->Folder(undef) if $autoload =~ /^(?:close)/i ; 
2476
		$self->_imap_command(qq/$autoload/) or return undef;
2477
	}
2478
	return wantarray ? 	$self->History($self->Count) 	: 
2479
                              [map {$_->[DATA] } @{$self->{'History'}{$self->Count}}] ;
2480
 
2481
}
2482
 
2483
sub rename {
2484
    my $self = shift;
2485
    my ($from, $to) = @_;
2486
    local($_);
2487
    if ($from =~ /^"(.*)"$/) {
2488
	$from = $1 unless $self->exists($from);
2489
        $from =~ s/"/\\"/g;
2490
    }
2491
    if ($to =~ /^"(.*)"$/) {
2492
	$to = $1 unless $self->exists($from) and $from =~ /^".*"$/;
2493
        $to =~ s/"/\\"/g;
2494
    }
2495
    $self->_imap_command(qq(RENAME "$from" "$to")) or return undef;
2496
    return $self;
2497
}
2498
 
2499
sub status {
2500
 
2501
	my $self = shift;
2502
	my $box = shift ;  
2503
	return undef unless defined($box);
2504
	$box = $self->Massage($box);
2505
	my @pieces = @_;
2506
	$self->_imap_command("STATUS $box (". (join(" ",@_)||'MESSAGES'). ")") or return undef;
2507
	return wantarray ? 	$self->History($self->Count) 	: 
2508
                              [map{$_->[DATA]}@{$self->{'History'}{$self->Count}}];
2509
 
2510
}
2511
 
2512
 
2513
# Can take a list of messages now.
2514
# If a single message, returns array or ref to array of flags
2515
# If a ref to array of messages, returns a ref to hash of msgid => flag arr
2516
# See parse_headers for more information
2517
# 2000-03-22 Adrian Smith (adrian.smith@ucpag.com)
2518
 
2519
sub flags {
2520
	my $self = shift;
2521
	my $msgspec = shift;
2522
	my $flagset = {};
2523
	my $msg;
2524
	my $u_f = $self->Uid;
2525
 
2526
	# Determine if set of messages or just one
2527
	if (ref($msgspec) eq 'ARRAY' ) {
2528
		$msg = $self->Range($msgspec) ;
2529
	} elsif ( !ref($msgspec) ) 	{
2530
		$msg = $msgspec;
2531
		if ( scalar(@_) ) {
2532
			$msgspec = $self->Range($msg) ;
2533
			$msgspec += $_ for (@_);
2534
			$msg = $msgspec;
2535
		}
2536
	} elsif ( ref($msgspec) =~ /MessageSet/ ) {
2537
		if ( scalar(@_) ) {
2538
			$msgspec += $_ for @_;
2539
		}
2540
	} else {
2541
		$self->LastError("Invalid argument passed to fetch.\n");
2542
		return undef;
2543
	}
2544
 
2545
	# Send command
2546
	unless ( $self->fetch($msg,"FLAGS") ) {
2547
		return undef;
2548
	}
2549
 
2550
	# Parse results, setting entry in result hash for each line
2551
 	foreach my $resultline ($self->Results) {
2552
		$self->_debug("flags: line = '$resultline'\n") ;
2553
		if (	$resultline =~ 
2554
			/\*\s+(\d+)\s+FETCH\s+	# * nnn FETCH 
2555
			 \(			# open-paren
2556
			 (?:\s?UID\s(\d+)\s?)?	# optional: UID nnn <space>
2557
			 FLAGS\s?\((.*)\)\s?	# FLAGS (\Flag1 \Flag2) <space>
2558
			 (?:\s?UID\s(\d+))?	# optional: UID nnn
2559
			 \) 			# close-paren
2560
			/x
2561
		) {
2562
			{ local($^W=0);
2563
			 $self->_debug("flags: line = '$resultline' " .
2564
			   "and 1,2,3,4 = $1,$2,$3,$4\n") 
2565
			 if $self->Debug;
2566
			}
2567
			my $mailid = $u_f ? ( $2||$4) : $1;
2568
			my $flagsString = $3 ;
2569
			my @flags = map { s/\s+$//; $_ } split(/\s+/, $flagsString);
2570
			$flagset->{$mailid} = \@flags;
2571
		}
2572
	}
2573
 
2574
	# Did the guy want just one response? Return it if so
2575
	unless (ref($msgspec) ) {
2576
		my $flagsref = $flagset->{$msgspec};
2577
		return wantarray ? @$flagsref : $flagsref;
2578
	}
2579
 
2580
	# Or did he want a hash from msgid to flag array?
2581
	return $flagset;
2582
}
2583
 
2584
# parse_headers modified to allow second param to also be a
2585
# reference to a list of numbers. If this is a case, the headers
2586
# are read from all the specified messages, and a reference to
2587
# an hash of mail numbers to references to hashes, are returned.
2588
# I found, with a mailbox of 300 messages, this was
2589
# *significantly* faster against our mailserver (< 1 second
2590
# vs. 20 seconds)
2591
#
2592
# 2000-03-22 Adrian Smith (adrian.smith@ucpag.com)
2593
 
2594
sub parse_headers {
2595
	my($self,$msgspec,@fields) = @_;
2596
	my(%fieldmap) = map { ( lc($_),$_ )  } @fields;
2597
	my $msg; my $string; my $field;
2598
 
2599
	# Make $msg a comma separated list, of messages we want
2600
        if (ref($msgspec) eq 'ARRAY') {
2601
		#$msg = join(',', @$msgspec);
2602
		$msg = $self->Range($msgspec);
2603
	} else {
2604
		$msg = $msgspec;
2605
	}
2606
 
2607
	if ($fields[0] 	=~ 	/^[Aa][Ll]{2}$/ 	) { 
2608
 
2609
		$string = 	"$msg body" . 
2610
		# use ".peek" if Peek parameter is a) defined and true, 
2611
		# 	or b) undefined, but not if it's defined and untrue:
2612
 
2613
		( 	defined($self->Peek) 		? 
2614
			( $self->Peek ? ".peek" : "" ) 	: 
2615
			".peek" 
2616
		) .  "[header]" 			; 
2617
 
2618
	} else {
2619
		$string	= 	"$msg body" .
2620
		# use ".peek" if Peek parameter is a) defined and true, or 
2621
		# b) undefined, but not if it's defined and untrue:
2622
 
2623
		( defined($self->Peek) 			? 
2624
			( $self->Peek ? ".peek" : "" ) 	: 
2625
			".peek" 
2626
		) .  "[header.fields ("	. join(" ",@fields) 	. ')]' ;
2627
	}
2628
 
2629
	my @raw=$self->fetch(	$string	) or return undef;
2630
 
2631
	my $headers = {};	# hash from message ids to header hash
2632
	my $h = 0;		# reference to hash of current msgid, or 0 between msgs
2633
 
2634
        for my $header (map { split(/(?:\x0d\x0a)/,$_) } @raw) {
2635
                local($^W) = undef;
2636
                if ( $header =~ /^\*\s+\d+\s+FETCH\s+\(.*BODY\[HEADER(?:\]|\.FIELDS)/i) {
2637
                        if ($self->Uid) {
2638
                                if ( my($msgid) = $header =~ /UID\s+(\d+)/ ) {
2639
                                        $h = {};
2640
                                        $headers->{$msgid} = $h;
2641
                                } else {
2642
                                        $h = {};
2643
                                }
2644
                        } else {
2645
                                if ( my($msgid) = $header =~ /^\*\s+(\d+)/ ) {
2646
                                        #start of new message header:
2647
                                        $h = {};
2648
                                        $headers->{$msgid} = $h;
2649
                                }
2650
                        }
2651
                }
2652
                next if $header =~ /^\s+$/;
2653
 
2654
                # ( for vi
2655
                if ($header =~ /^\)/) {           # end of this message
2656
                        $h = 0;                   # set to be between messages
2657
                        next;
2658
                }
2659
                # check for '<optional_white_space>UID<white_space><UID_number><optional_white_space>)'
2660
                # when parsing headers by UID.
2661
                if ($self->Uid and my($msgid) = $header =~ /^\s*UID\s+(\d+)\s*\)/) {
2662
                        $headers->{$msgid} = $h;        # store in results against this message
2663
                        $h = 0;                 	# set to be between messages
2664
                        next;
2665
                }
2666
 
2667
		if ($h != 0) {			  # do we expect this to be a header?
2668
               		my $hdr = $header;
2669
               		chomp $hdr;
2670
               		$hdr =~ s/\r$//;   
2671
               		if ($hdr =~ s/^(\S+):\s*//) { 
2672
                       		$field = exists $fieldmap{lc($1)} ? $fieldmap{lc($1)} : $1 ;
2673
                       		push @{$h->{$field}} , $hdr ;
2674
               		} elsif ($hdr =~ s/^.*FETCH\s\(.*BODY\[HEADER\.FIELDS.*\)\]\s(\S+):\s*//) { 
2675
                       		$field = exists $fieldmap{lc($1)} ? $fieldmap{lc($1)} : $1 ;
2676
                       		push @{$h->{$field}} , $hdr ;
2677
               		} elsif ( ref($h->{$field}) eq 'ARRAY') {
2678
 
2679
					$hdr =~ s/^\s+/ /;
2680
                       			$h->{$field}[-1] .= $hdr ;
2681
               		}
2682
		}
2683
	}
2684
	my $candump = 0;
2685
	if ($self->Debug) {
2686
		eval {
2687
			require Data::Dumper;
2688
			Data::Dumper->import;
2689
		};
2690
		$candump++ unless $@;
2691
	}
2692
	# if we asked for one message, just return its hash,
2693
	# otherwise, return hash of numbers => header hash
2694
	# if (ref($msgspec) eq 'ARRAY') {
2695
	if (ref($msgspec) ) {
2696
		#_debug $self,"Structure from parse_headers:\n", 
2697
		#	Dumper($headers) 
2698
		#	if $self->Debug;
2699
		return $headers;
2700
	} else {
2701
		#_debug $self, "Structure from parse_headers:\n", 
2702
		#	Dumper($headers->{$msgspec}) 
2703
		#	if $self->Debug;
2704
		return $headers->{$msgspec};
2705
	}
2706
}
2707
 
2708
sub subject { return $_[0]->get_header($_[1],"Subject") }
2709
sub date { return $_[0]->get_header($_[1],"Date") }
2710
sub rfc822_header { get_header(@_) }
2711
 
2712
sub get_header {
2713
	my($self , $msg, $header ) = @_;
2714
	my $val = 0;
2715
	eval { $val = $self->parse_headers($msg,$header)->{$header}[0] };
2716
	return defined($val)? $val : undef;
2717
}
2718
 
2719
sub recent_count {
2720
	my ($self, $folder) = (shift, shift);
2721
 
2722
	$self->status($folder, 'RECENT') or return undef;
2723
 
2724
	chomp(my $r = ( grep { s/\*\s+STATUS\s+.*\(RECENT\s+(\d+)\s*\)/$1/ }
2725
			$self->History($self->Transaction)
2726
	)[0]);
2727
 
2728
	$r =~ s/\D//g;
2729
 
2730
	return $r;
2731
}
2732
 
2733
sub message_count {
2734
 
2735
	my ($self, $folder) = (shift, shift);
2736
	$folder ||= $self->Folder;
2737
 
2738
	$self->status($folder, 'MESSAGES') or return undef;
2739
        foreach my $result  (@{$self->{"History"}{$self->Transaction}}) {
2740
              return $1 if $result->[DATA] =~ /\(MESSAGES\s+(\d+)\s*\)/ ;
2741
        }
2742
 
2743
	return undef;
2744
 
2745
}
2746
 
2747
{
2748
for my $datum (
2749
                qw(     recent seen
2750
                        unseen messages
2751
                 )
2752
) {
2753
        no strict 'refs';
2754
        *$datum = sub {
2755
		my $self = shift;
2756
		#my @hits;
2757
 
2758
		#my $hits = $self->search($datum eq "messages" ? "ALL" : "$datum")
2759
		#	 or return undef;
2760
		#print "Received $hits from search and array context flag is ",
2761
		#	wantarry,"\n";
2762
		#if ( scalar(@$hits) ) {
2763
		#	return wantarray ? @$hits : $hits ;
2764
		#}
2765
		return $self->search($datum eq "messages" ? "ALL" : "$datum") ;
2766
 
2767
 
2768
        };
2769
}
2770
}
2771
{
2772
for my $datum (
2773
                qw(     sentbefore 	sentsince 	senton
2774
			since 		before 		on
2775
                 )
2776
) {
2777
	no strict 'refs';
2778
	*$datum = sub {
2779
 
2780
		my($self,$time) = (shift,shift);
2781
 
2782
		my @hits; my $imapdate;
2783
		my @mnt  =      qw{ Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec};
2784
 
2785
		if ( $time =~ /\d\d-\D\D\D-\d\d\d\d/ ) {
2786
			$imapdate = $time;
2787
		} elsif ( $time =~ /^\d+$/ ) {
2788
			my @ltime = localtime($time);
2789
			$imapdate = sprintf(	"%2.2d-%s-%4.4d", 
2790
						$ltime[3], $mnt[$ltime[4]], $ltime[5] + 1900);
2791
		} else {
2792
			$self->LastError("Invalid date format supplied to '$datum' method.");
2793
			return undef;
2794
		}
2795
		$self->_imap_command( ($self->Uid ? "UID " : "") . "SEARCH $datum $imapdate")
2796
			or return undef;
2797
		my @results =  $self->History($self->Count)     ;
2798
 
2799
		for my $r (@results) {
2800
 
2801
		       chomp $r;
2802
		       $r =~ s/\r$//;
2803
		       $r =~ s/^\*\s+SEARCH\s+//i or next;
2804
		       push @hits, grep(/\d/,(split(/\s+/,$r)));
2805
			_debug $self, "Hits are now: ",join(',',@hits),"\n" if $self->Debug;
2806
		}
2807
 
2808
		return wantarray ? @hits : \@hits;
2809
	}
2810
}
2811
}
2812
 
2813
sub or {
2814
 
2815
	my $self = shift ;
2816
	my @what = @_; 
2817
	my @hits;
2818
 
2819
	if ( scalar(@what) < 2 ) {
2820
		$self->LastError("Invalid number of arguments passed to or method.\n");
2821
		return undef;
2822
	}
2823
 
2824
	my $or = "OR " . $self->Massage(shift @what);
2825
	$or .= " " . $self->Massage(shift @what);
2826
 
2827
 
2828
	for my $w ( @what ) {
2829
		my $w = $self->Massage($w) ;
2830
		$or = "OR " . $or . " " . $w ;
2831
	}
2832
 
2833
	$self->_imap_command( ($self->Uid ? "UID " : "") . "SEARCH $or")
2834
		or return undef;
2835
	my @results =  $self->History($self->Count)     ;
2836
 
2837
	for my $r (@results) {
2838
 
2839
	       chomp $r;
2840
	       $r =~ s/\r$//;
2841
	       $r =~ s/^\*\s+SEARCH\s+//i or next;
2842
	       push @hits, grep(/\d/,(split(/\s+/,$r)));
2843
		_debug $self, "Hits are now: ",join(',',@hits),"\n" 
2844
				if $self->Debug;
2845
	}
2846
 
2847
	return wantarray ? @hits : \@hits;
2848
}
2849
 
2850
#sub Strip_cr {
2851
#	my $self = shift;
2852
 
2853
#	my $in = $_[0]||$self ;
2854
 
2855
#	$in =~ s/\r//g  ;
2856
 
2857
#	return $in;
2858
#}
2859
 
2860
 
2861
sub disconnect { $_[0]->logout }
2862
 
2863
 
2864
sub search {
2865
 
2866
	my $self = shift;
2867
	my @hits;
2868
	my @a = @_;
2869
	$@ = "";
2870
	# massage?
2871
	$a[-1] = $self->Massage($a[-1],1) 
2872
		if scalar(@a) > 1 and !exists($SEARCH_KEYS{uc($a[-1])}); 
2873
	$self->_imap_command( ( $self->Uid ? "UID " : "" ) . "SEARCH ". join(' ',@a)) 
2874
			 or return undef;
2875
	my $results =  $self->History($self->Count) ;
2876
 
2877
 
2878
	for my $r (@$results) {
2879
	#$self->_debug("Considering the search result line: $r");			
2880
               chomp $r;
2881
               $r =~ s/\r\n?/ /g;
2882
               $r =~ s/^\*\s+SEARCH\s+(?=.*\d.*)// or next;
2883
               my @h = grep(/^\d+$/,(split(/\s+/,$r)));
2884
	       push @hits, @h if scalar(@h) ; # and grep(/\d/,@h) );
2885
 
2886
	}
2887
 
2888
	$self->{LastError}="Search completed successfully but found no matching messages\n"
2889
		unless scalar(@hits);
2890
 
2891
	if ( wantarray ) {
2892
		return @hits;
2893
	} else {
2894
		if ($self->Ranges) {
2895
			#print STDERR "Fetch: Returning range\n";
2896
			return scalar(@hits) ? $self->Range(\@hits) : undef;
2897
		} else {
2898
			#print STDERR "Fetch: Returning ref\n";
2899
			return scalar(@hits) ? \@hits : undef;
2900
		}
2901
	}
2902
}
2903
 
2904
sub thread {
2905
	# returns a Thread data structure
2906
	#
2907
	# $imap->thread($algorythm, $charset, @search_args);
2908
	my $self = shift;
2909
 
2910
	my $algorythm     = shift;
2911
	   $algorythm   ||= $self->has_capability("THREAD=REFERENCES") ? "REFERENCES" : "ORDEREDSUBJECT";
2912
	my $charset 	  = shift;
2913
	   $charset 	||= "UTF-8";
2914
 
2915
	my @a = @_;
2916
 
2917
	$a[0]||="ALL" ;
2918
	my @hits;
2919
	# massage?
2920
 
2921
	$a[-1] = $self->Massage($a[-1],1) 
2922
		if scalar(@a) > 1 and !exists($SEARCH_KEYS{uc($a[-1])}); 
2923
	$self->_imap_command( ( $self->Uid ? "UID " : "" ) . 
2924
				"THREAD $algorythm $charset " . 
2925
				join(' ',@a)
2926
	) or return undef;
2927
	my $results =  $self->History($self->Count) ;
2928
 
2929
	my $thread = "";
2930
	for my $r (@$results) {
2931
		#$self->_debug("Considering the search result line: $r");			
2932
               	chomp $r;
2933
               	$r =~ s/\r\n?/ /g;
2934
               	if ( $r =~ /^\*\s+THREAD\s+/ ) {
2935
			eval { require "Mail/IMAPClient/Thread.pm" }
2936
				or ( $self->LastError($@), return undef);
2937
			my $parser = Mail::IMAPClient::Thread->new();
2938
			$thread = $parser->start($r) ;
2939
		} else {
2940
			next;
2941
		}
2942
	       	#while ( $r =~ /(\([^\)]*\))/ ) { 
2943
		#	push @hits, [ split(/ /,$1) ] ;
2944
		#}
2945
	}
2946
 
2947
	$self->{LastError}="Thread search completed successfully but found no matching messages\n"
2948
		unless ref($thread);
2949
	return $thread ||undef;
2950
 
2951
	if ( wantarray ) {
2952
 
2953
		return @hits;
2954
	} else {
2955
		return scalar(@hits) ? \@hits : undef;
2956
	}
2957
}
2958
 
2959
 
2960
 
2961
 
2962
sub delete_message {
2963
 
2964
	my $self = shift;
2965
	my $count = 0;
2966
	my @msgs = ();
2967
	for my $arg (@_) {
2968
		if (ref($arg) eq 'ARRAY') {
2969
			push @msgs, @{$arg};
2970
		} else {
2971
			push @msgs, split(/\,/,$arg);
2972
		}
2973
	}
2974
 
2975
 
2976
	$self->store(join(',',@msgs),'+FLAGS.SILENT','(\Deleted)') and $count = scalar(@msgs);
2977
 
2978
	return $count;
2979
}
2980
 
2981
sub restore_message {
2982
 
2983
	my $self = shift;
2984
	my @msgs = ();
2985
	for my $arg (@_) {
2986
		if (ref($arg) eq 'ARRAY') {
2987
			push @msgs, @{$arg};
2988
		} else {
2989
			push @msgs, split(/\,/,$arg);
2990
		}
2991
	}
2992
 
2993
 
2994
	$self->store(join(',',@msgs),'-FLAGS','(\Deleted)') ;
2995
	my $count = grep(
2996
			/
2997
				^\*			# Start with an asterisk
2998
				\s\d+			# then a space then a number
2999
				\sFETCH			# then a space then the string 'FETCH'
3000
				\s\(			# then a space then an open paren :-) 
3001
				.*			# plus optional anything
3002
				FLAGS			# then the string "FLAGS"
3003
				.*			# plus anything else
3004
				(?!\\Deleted)		# but never "\Deleted"
3005
			/x,
3006
			$self->Results
3007
	);
3008
 
3009
 
3010
	return $count;
3011
}
3012
 
3013
 
3014
sub uidvalidity {
3015
 
3016
	my $self = shift; my $folder = shift;
3017
 
3018
	my $vline = (grep(/UIDVALIDITY/i, $self->status($folder, "UIDVALIDITY")))[0];
3019
 
3020
	my($validity) = $vline =~ /\(UIDVALIDITY\s+([^\)]+)/;
3021
 
3022
	return $validity;
3023
}
3024
 
3025
# 3 status folder (uidnext)
3026
# * STATUS folder (UIDNEXT 290)
3027
 
3028
sub uidnext {
3029
 
3030
	my $self = shift; my $folder = $self->Massage(shift);
3031
 
3032
	my $line = (grep(/UIDNEXT/i, $self->status($folder, "UIDNEXT")))[0];
3033
 
3034
	my($uidnext) = $line =~ /\(UIDNEXT\s+([^\)]+)/;
3035
 
3036
	return $uidnext;
3037
}
3038
 
3039
sub capability {
3040
 
3041
	my $self = shift;
3042
 
3043
	$self->_imap_command('CAPABILITY') or return undef;
3044
 
3045
	my @caps = ref($self->{CAPABILITY}) 		? 
3046
			keys %{$self->{CAPABILITY}} 	: 
3047
			map { split } 
3048
				grep (s/^\*\s+CAPABILITY\s+//, 
3049
				$self->History($self->Count));
3050
 
3051
	unless ( exists $self->{CAPABILITY} ) { 
3052
		for (@caps) { 
3053
			$self->{CAPABILITY}{uc($_)}++ ;
3054
			if (/=/) {
3055
				my($k,$v)=split(/=/,$_) ;
3056
				$self->{uc($k)} = uc($v) ;
3057
			}
3058
		} 
3059
	}
3060
 
3061
 
3062
	return wantarray ? @caps : \@caps;
3063
}
3064
 
3065
sub has_capability {
3066
	my $self = shift;
3067
	$self->capability;
3068
	local($^W)=0;
3069
	return $self->{CAPABILITY}{uc($_[0])};
3070
}
3071
 
3072
sub imap4rev1 {
3073
	my $self = shift;
3074
	return exists($self->{_IMAP4REV1}) ?  
3075
		$self->{_IMAP4REV1} : 
3076
		$self->{_IMAP4REV1} = $self->has_capability(IMAP4REV1) ;
3077
}
3078
 
3079
sub namespace {
3080
	# Returns a (reference to a?) nested list as follows:
3081
	# [ 
3082
	#  [
3083
	#   [ $user_prefix,  $user_delim  ] (,[$user_prefix2  ,$user_delim  ], [etc,etc] ),
3084
	#  ],
3085
	#  [
3086
	#   [ $shared_prefix,$shared_delim] (,[$shared_prefix2,$shared_delim], [etc,etc] ),
3087
	#  ],
3088
	#  [
3089
	#   [$public_prefix, $public_delim] (,[$public_prefix2,$public_delim], [etc,etc] ),
3090
	#  ],
3091
	# ] ;
3092
 
3093
	my $self = shift;
3094
	unless ( $self->has_capability("NAMESPACE") ) {
3095
			my $error = $self->Count . " NO NAMESPACE not supported by " . $self->Server ;
3096
			$self->LastError("$error\n") ;
3097
			$self->_debug("$error\n") ;
3098
			$@ = $error;
3099
			carp "$@" if $^W;
3100
			return undef;
3101
	}
3102
	my $namespace = (map({ /^\* NAMESPACE (.*)/ ? $1 : () } @{$self->_imap_command("NAMESPACE")->Results}))[0] ;
3103
	$namespace =~ s/\x0d?\x0a$//;
3104
	my($personal,$shared,$public) = $namespace =~ m#
3105
		(NIL|\((?:\([^\)]+\)\s*)+\))\s
3106
		(NIL|\((?:\([^\)]+\)\s*)+\))\s
3107
		(NIL|\((?:\([^\)]+\)\s*)+\))
3108
	#xi;
3109
 
3110
	my @ns = ();
3111
	$self->_debug("NAMESPACE: pers=$personal, shared=$shared, pub=$public\n");
3112
	push @ns, map {
3113
		$_ =~ s/^\((.*)\)$/$1/;
3114
		my @pieces = m#\(([^\)]*)\)#g;
3115
		$self->_debug("NAMESPACE pieces: " . join(", ",@pieces) . "\n");
3116
		my $ref = [];
3117
		foreach my $atom (@pieces) {
3118
			push @$ref, [ $atom =~ m#"([^"]*)"\s*#g ] ;
3119
		}
3120
		$_ =~ /^NIL$/i ? undef : $ref;
3121
	} ( $personal, $shared, $public) ;
3122
	return wantarray ? @ns : \@ns;
3123
}
3124
 
3125
# Contributed by jwm3
3126
sub internaldate {
3127
        my $self = shift;
3128
        my $msg  = shift;
3129
        $self->_imap_command( ( $self->Uid ? "UID " : "" ) . "FETCH $msg INTERNALDATE") or return undef;
3130
        my $internalDate = join("", $self->History($self->Count));
3131
        $internalDate =~ s/^.*INTERNALDATE "//si;
3132
        $internalDate =~ s/\".*$//s;
3133
        return $internalDate;
3134
}
3135
 
3136
sub is_parent {
3137
	my ($self, $folder) = (shift, shift);
3138
	# $self->_debug("Checking parentage ".( $folder ? "for folder $folder" : "" )."\n");
3139
        my $list = $self->list(undef, $folder)||"NO NO BAD BAD";
3140
	my $line = '';
3141
 
3142
        for (my $m = 0; $m < scalar(@$list); $m++ ) {
3143
		#$self->_debug("Judging whether or not $list->[$m] is fit for parenthood\n");
3144
		return undef 
3145
		  if $list->[$m] =~ /NoInferior/i;       # let's not beat around the bush!
3146
 
3147
                if ($list->[$m]  =~ s/(\{\d+\})\x0d\x0a$// ) {
3148
                        $list->[$m] .= $list->[$m+1];
3149
                        $list->[$m+1] = "";
3150
                }
3151
 
3152
	    	$line = $list->[$m]
3153
                        if $list->[$m] =~
3154
                        /       ^\*\s+LIST              # * LIST
3155
                                \s+\([^\)]*\)\s+            # (Flags)
3156
                                "[^"]*"\s+              # "delimiter"
3157
                                (?:"([^"]*)"|(.*))\x0d\x0a$  # Name or "Folder name"
3158
                        /x;
3159
	}	
3160
	if ( $line eq "" ) {
3161
		$self->_debug("Warning: separator method found no correct o/p in:\n\t" .
3162
			join("\t",@list)."\n");
3163
	}
3164
	my($f) = $line =~ /^\*\s+LIST\s+\(([^\)]*)\s*\)/ if $line;
3165
	return  1 if $f =~ /HasChildren/i ;
3166
	return 0 if $f =~ /HasNoChildren/i ;
3167
	unless ( $f =~ /\\/) {		# no flags at all unless there's a backslash
3168
		my $sep = $self->separator($folder);
3169
		return 1 if scalar(grep /^${folder}${sep}/, $self->folders);
3170
		return 0;
3171
	}
3172
}
3173
 
3174
sub selectable {my($s,$f)=@_;return grep(/NoSelect/i,$s->list("",$f))?0:1;}
3175
 
3176
sub append_string {
3177
 
3178
        my $self = shift;
3179
        my $folder = $self->Massage(shift);
3180
 
3181
	my $text = shift;
3182
	$text =~ s/\x0d?\x0a/\x0d\x0a/g;
3183
 
3184
	my($flags,$date) = (shift,shift);
3185
 
3186
	if (defined($flags)) {
3187
		$flags =~ s/^\s+//g;
3188
		$flags =~ s/\s+$//g;
3189
	}
3190
 
3191
	if (defined($date)) {
3192
		$date =~ s/^\s+//g;
3193
		$date =~ s/\s+$//g;
3194
	}
3195
 
3196
	$flags = "($flags)"  if $flags and $flags !~ /^\(.*\)$/ ;
3197
	$date  = qq/"$date"/ if $date  and $date  !~ /^"/ 	;
3198
 
3199
        my $clear = $self->Clear;
3200
 
3201
        $self->Clear($clear)
3202
                if $self->Count >= $clear and $clear > 0;
3203
 
3204
	my $count 	= $self->Count($self->Count+1);
3205
 
3206
        my $string = 	  "$count APPEND $folder "  	  . 
3207
			( $flags ? "$flags " : "" 	) . 
3208
			( $date ? "$date " : "" 	) . 
3209
			"{" . length($text)  . "}\x0d\x0a" ;
3210
 
3211
        $self->_record($count,[ $self->_next_index($count), "INPUT", "$string\x0d\x0a" ] );
3212
 
3213
	# Step 1: Send the append command.
3214
 
3215
	my $feedback = $self->_send_line("$string");
3216
 
3217
	unless ($feedback) {
3218
		$self->LastError("Error sending '$string' to IMAP: $!\n");
3219
		return undef;
3220
	}
3221
 
3222
	my ($code, $output) = ("","");	
3223
 
3224
	# Step 2: Get the "+ go ahead" response
3225
	until ( $code ) {
3226
		$output = $self->_read_line or return undef;	
3227
		foreach my $o (@$output) { 
3228
 
3229
			$self->_record($count,$o);	# $o is already an array ref
3230
			next unless $self->_is_output($o);
3231
 
3232
                      ($code) = $o->[DATA] =~ /(^\+|^\d*\s*NO|^\d*\s*BAD)/i ;
3233
 
3234
                      if ($o->[DATA] =~ /^\*\s+BYE/i) {
3235
                              $self->LastError("Error trying to append string: " . 
3236
						$o->[DATA]. "; Disconnected.\n");
3237
                              $self->_debug("Error trying to append string: " . $o->[DATA]. 
3238
					"; Disconnected.\n");
3239
                              carp("Error trying to append string: " . $o->[DATA] ."; Disconnected") if $^W;
3240
				$self->State(Unconnected);
3241
 
3242
                      } elsif ( $o->[DATA] =~ /^\d*\s*(NO|BAD)/i ) { # i and / transposed!!!
3243
                              $self->LastError("Error trying to append string: " . $o->[DATA]  . "\n");
3244
                              $self->_debug("Error trying to append string: " . $o->[DATA] . "\n");
3245
                              carp("Error trying to append string: " . $o->[DATA]) if $^W;
3246
				return undef;
3247
			}
3248
		}
3249
	}	
3250
 
3251
	$self->_record($count,[ $self->_next_index($count), "INPUT", "$text\x0d\x0a" ] );
3252
 
3253
	# Step 3: Send the actual text of the message:
3254
        $feedback = $self->_send_line("$text\x0d\x0a");
3255
 
3256
        unless ($feedback) {
3257
                $self->LastError("Error sending append msg text to IMAP: $!\n");
3258
                return undef;
3259
        }
3260
	$code = undef;			# clear out code
3261
 
3262
	# Step 4: Figure out the results:
3263
        until ($code) {
3264
                $output = $self->_read_line or return undef;
3265
              $self->_debug("Append results: " . map({ $_->[DATA] } @$output) . "\n" )
3266
			if $self->Debug;
3267
                foreach my $o (@$output) {
3268
			$self->_record($count,$o); # $o is already an array ref
3269
 
3270
                      ($code) = $o->[DATA] =~ /^(?:$count|\*) (OK|NO|BAD)/im  ;
3271
 
3272
                      if ($o->[DATA] =~ /^\*\s+BYE/im) {
3273
				$self->State(Unconnected);
3274
                              $self->LastError("Error trying to append: " . $o->[DATA] . "\n");
3275
                              $self->_debug("Error trying to append: " . $o->[DATA] . "\n");
3276
                              carp("Error trying to append: " . $o->[DATA] ) if $^W;
3277
			}
3278
			if ($code and $code !~ /^OK/im) {
3279
                              $self->LastError("Error trying to append: " . $o->[DATA] . "\n");
3280
                              $self->_debug("Error trying to append: " . $o->[DATA] . "\n");
3281
                              carp("Error trying to append: " . $o->[DATA] ) if $^W;
3282
				return undef;
3283
			}
3284
        	}
3285
	}
3286
 
3287
      my($uid) = join("",map { $_->[TYPE] eq "OUTPUT" ? $_->[DATA] : () } @$output ) =~ m#\s+(\d+)\]#;
3288
 
3289
        return defined($uid) ? $uid : $self;
3290
}
3291
sub append {
3292
 
3293
        my $self = shift;
3294
	# now that we're passing thru to append_string we won't massage here
3295
        # my $folder = $self->Massage(shift); 
3296
        my $folder = shift;
3297
 
3298
	my $text = join("\x0d\x0a",@_);
3299
	$text =~ s/\x0d?\x0a/\x0d\x0a/g;
3300
	return $self->append_string($folder,$text);
3301
}
3302
 
3303
sub append_file {
3304
 
3305
        my $self 	= shift;
3306
        my $folder 	= $self->Massage(shift);
3307
	my $file 	= shift; 
3308
	my $control 	= shift || undef;
3309
	my $count 	= $self->Count($self->Count+1);
3310
 
3311
 
3312
	unless ( -f $file ) {
3313
		$self->LastError("File $file not found.\n");
3314
		return undef;
3315
	}
3316
 
3317
	my $fh = IO::File->new($file) ;
3318
 
3319
	unless ($fh) {
3320
		$self->LastError("Unable to open $file: $!\n");
3321
		$@ = "Unable to open $file: $!" ;
3322
		carp "unable to open $file: $!" if $^W;
3323
		return undef;
3324
	}
3325
 
3326
	my $bare_nl_count = scalar grep { /^\x0a$|[^\x0d]\x0a$/} <$fh>;
3327
 
3328
	seek($fh,0,0);
3329
 
3330
        my $clear = $self->Clear;
3331
 
3332
        $self->Clear($clear)
3333
                if $self->Count >= $clear and $clear > 0;
3334
 
3335
	my $length = ( -s $file ) + $bare_nl_count;
3336
 
3337
        my $string = "$count APPEND $folder {" . $length  . "}\x0d\x0a" ;
3338
 
3339
        $self->_record($count,[ $self->_next_index($count), "INPUT", "$string" ] );
3340
 
3341
	my $feedback = $self->_send_line("$string");
3342
 
3343
	unless ($feedback) {
3344
		$self->LastError("Error sending '$string' to IMAP: $!\n");
3345
		close $fh;
3346
		return undef;
3347
	}
3348
 
3349
	my ($code, $output) = ("","");	
3350
 
3351
	until ( $code ) {
3352
		$output = $self->_read_line or close $fh, return undef;	
3353
		foreach my $o (@$output) {
3354
			$self->_record($count,$o);		# $o is already an array ref
3355
                      ($code) = $o->[DATA] =~ /(^\+|^\d+\sNO|^\d+\sBAD)/i; 
3356
                      if ($o->[DATA] =~ /^\*\s+BYE/) {
3357
                              carp $o->[DATA] if $^W;
3358
				$self->State(Unconnected);
3359
				close $fh;
3360
				return undef ;
3361
                      } elsif ( $o->[DATA]=~ /^\d+\s+(NO|BAD)/i ) {
3362
                              carp $o->[DATA] if $^W;
3363
				close $fh;
3364
				return undef;
3365
			}
3366
		}
3367
	}	
3368
 
3369
	{ 	# Narrow scope
3370
		# Slurp up headers: later we'll make this more efficient I guess
3371
		local $/ = "\x0d\x0a\x0d\x0a"; 
3372
		my $text = <$fh>;
3373
		$text =~ s/\x0d?\x0a/\x0d\x0a/g;
3374
		$self->_record($count,[ $self->_next_index($count), "INPUT", "{From file $file}" ] ) ;
3375
		$feedback = $self->_send_line($text);
3376
 
3377
		unless ($feedback) {
3378
			$self->LastError("Error sending append msg text to IMAP: $!\n");
3379
			close $fh;
3380
			return undef;
3381
		}
3382
		_debug $self, "control points to $$control\n" if ref($control) and $self->Debug;
3383
		$/ = 	ref($control) ?  "\x0a" : $control ? $control : 	"\x0a";	
3384
		while (defined($text = <$fh>)) {
3385
			$text =~ s/\x0d?\x0a/\x0d\x0a/g;
3386
			$self->_record(	$count,
3387
					[ $self->_next_index($count), "INPUT", "{from $file}\x0d\x0a" ] 
3388
			);
3389
			$feedback = $self->_send_line($text,1);
3390
 
3391
			unless ($feedback) {
3392
				$self->LastError("Error sending append msg text to IMAP: $!\n");
3393
				close $fh;
3394
				return undef;
3395
			}
3396
		}
3397
		$feedback = $self->_send_line("\x0d\x0a");
3398
 
3399
		unless ($feedback) {
3400
			$self->LastError("Error sending append msg text to IMAP: $!\n");
3401
			close $fh;
3402
			return undef;
3403
		}
3404
	} 
3405
 
3406
	# Now for the crucial test: Did the append work or not?
3407
	($code, $output) = ("","");	
3408
 
3409
	my $uid = undef;	
3410
	until ( $code ) {
3411
		$output = $self->_read_line or return undef;	
3412
		foreach my $o (@$output) {
3413
			$self->_record($count,$o);		# $o is already an array ref
3414
                      $self->_debug("append_file: Deciding if " . $o->[DATA] . " has the code.\n") 
3415
				if $self->Debug;
3416
                      ($code) = $o->[DATA]  =~ /^\d+\s(NO|BAD|OK)/i; 
3417
			# try to grab new msg's uid from o/p
3418
                      $o->[DATA]  =~ m#UID\s+\d+\s+(\d+)\]# and $uid = $1; 
3419
                      if ($o->[DATA] =~ /^\*\s+BYE/) {
3420
                              carp $o->[DATA] if $^W;
3421
				$self->State(Unconnected);
3422
				close $fh;
3423
				return undef ;
3424
                      } elsif ( $o->[DATA]=~ /^\d+\s+(NO|BAD)/i ) {
3425
                              carp $o->[DATA] if $^W;
3426
				close $fh;
3427
				return undef;
3428
			}
3429
		}
3430
	}	
3431
	close $fh;
3432
 
3433
	if ($code !~ /^OK/i) {
3434
		return undef;
3435
	}
3436
 
3437
 
3438
        return defined($uid) ? $uid : $self;
3439
}
3440
 
3441
 
3442
sub authenticate {
3443
 
3444
        my $self 	= shift;
3445
        my $scheme 	= shift;
3446
        my $response 	= shift;
3447
 
3448
	$scheme   ||= $self->Authmechanism;
3449
	$response ||= $self->Authcallback;
3450
        my $clear = $self->Clear;
3451
 
3452
        $self->Clear($clear)
3453
                if $self->Count >= $clear and $clear > 0;
3454
 
3455
	my $count 	= $self->Count($self->Count+1);
3456
 
3457
 
3458
        my $string = "$count AUTHENTICATE $scheme";
3459
 
3460
        $self->_record($count,[ $self->_next_index($self->Transaction), 
3461
				"INPUT", "$string\x0d\x0a"] );
3462
 
3463
	my $feedback = $self->_send_line("$string");
3464
 
3465
	unless ($feedback) {
3466
		$self->LastError("Error sending '$string' to IMAP: $!\n");
3467
		return undef;
3468
	}
3469
 
3470
	my ($code, $output);	
3471
 
3472
	until ($code) {
3473
		$output = $self->_read_line or return undef;	
3474
		foreach my $o (@$output) {
3475
			$self->_record($count,$o);	# $o is a ref
3476
			($code) = $o->[DATA] =~ /^\+(.*)$/ ;
3477
			if ($o->[DATA] =~ /^\*\s+BYE/) {
3478
				$self->State(Unconnected);
3479
				return undef ;
3480
			}
3481
		}
3482
	}	
3483
 
3484
        return undef if $code =~ /^BAD|^NO/ ;
3485
 
3486
        if ('CRAM-MD5' eq $scheme && ! $response) {
3487
          if ($Mail::IMAPClient::_CRAM_MD5_ERR) {
3488
            $self->LastError($Mail::IMAPClient::_CRAM_MD5_ERR);
3489
            carp $Mail::IMAPClient::_CRAM_MD5_ERR if $^W;
3490
          } else {
3491
            $response = \&_cram_md5;
3492
          }
3493
        }
3494
 
3495
        $feedback = $self->_send_line($response->($code, $self));
3496
 
3497
        unless ($feedback) {
3498
                $self->LastError("Error sending append msg text to IMAP: $!\n");
3499
                return undef;
3500
        }
3501
 
3502
	$code = ""; 	# clear code
3503
        until ($code) {
3504
                $output = $self->_read_line or return undef;
3505
		foreach my $o (@$output) {
3506
                	$self->_record($count,$o);	# $o is a ref
3507
			if ( ($code) = $o->[DATA] =~ /^\+ (.*)$/ ) {
3508
				$feedback = $self->_send_line($response->($code,$self));
3509
				unless ($feedback) {
3510
					$self->LastError("Error sending append msg text to IMAP: $!\n");
3511
					return undef;
3512
				}
3513
				$code = "" ;		# Clear code; we're still not finished
3514
			} else {
3515
				$o->[DATA] =~ /^$count (OK|NO|BAD)/ and $code = $1;
3516
				if ($o->[DATA] =~ /^\*\s+BYE/) {
3517
					$self->State(Unconnected);
3518
					return undef ;
3519
				}
3520
			}
3521
		}
3522
        }
3523
 
3524
        $code =~ /^OK/ and $self->State(Authenticated) ;
3525
        return $code =~ /^OK/ ? $self : undef ;
3526
 
3527
}
3528
 
3529
# UIDPLUS response from a copy: [COPYUID (uidvalidity) (origuid) (newuid)]
3530
sub copy {
3531
 
3532
	my($self, $target, @msgs) = @_;
3533
 
3534
	$target = $self->Massage($target);
3535
	if ( $self->Ranges ) {
3536
		@msgs = ($self->Range(@msgs));
3537
	} else {
3538
		@msgs   = sort { $a <=> $b } map { ref($_)? @$_ : split(',',$_) } @msgs;
3539
	}
3540
 
3541
	$self->_imap_command( 
3542
	  ( 	$self->Uid ? "UID " : "" ) . 
3543
		"COPY " . 
3544
		( $self->Ranges ? $self->Range(@msgs) : 
3545
		join(',',map { ref($_)? @$_ : $_ } @msgs)) . 
3546
		" $target"
3547
	) 			or return undef		;
3548
	my @results =  $self->History($self->Count) 	;
3549
 
3550
	my @uids;
3551
 
3552
	for my $r (@results) {
3553
 
3554
               chomp $r;
3555
               $r =~ s/\r$//;
3556
               $r =~ s/^.*\[COPYUID\s+\d+\s+[\d:,]+\s+([\d:,]+)\].*/$1/ or next;
3557
               push @uids, ( $r =~ /(\d+):(\d+)/ ? $1 ... $2 : split(/,/,$r) ) ;
3558
 
3559
	}
3560
 
3561
	return scalar(@uids) ? join(",",@uids) : $self;
3562
}
3563
 
3564
sub move {
3565
 
3566
	my($self, $target, @msgs) = @_;
3567
 
3568
	$self->create($target) and $self->subscribe($target) 
3569
		unless $self->exists($target);
3570
 
3571
	my $uids = $self->copy($target, map { ref($_) =~ /ARRAY/ ? @{$_} : $_ } @msgs) 
3572
		or return undef;
3573
 
3574
	$self->delete_message(@msgs) or carp $self->LastError;
3575
 
3576
	return $uids;
3577
}
3578
 
3579
sub set_flag {
3580
	my($self, $flag, @msgs) = @_;
3581
	if ( ref($msgs[0]) =~ /ARRAY/ ) { @msgs = @{$msgs[0]} };
3582
	$flag =~ /^\\/ or $flag = "\\" . $flag 
3583
		if $flag =~ /^(Answered|Flagged|Deleted|Seen|Draft)$/i;
3584
	if ( $self->Ranges ) {
3585
		$self->store( $self->Range(@msgs), "+FLAGS.SILENT (" . $flag . ")" );
3586
	} else {
3587
		$self->store( join(",",@msgs), "+FLAGS.SILENT (" . $flag . ")" );
3588
	}
3589
}
3590
 
3591
sub see {
3592
	my($self, @msgs) = @_;
3593
	if ( ref($msgs[0]) =~ /ARRAY/ ) { @msgs = @{$msgs[0]} };
3594
	$self->set_flag('\\Seen', @msgs);
3595
}
3596
 
3597
sub mark {
3598
	my($self, @msgs) = @_;
3599
	if ( ref($msgs[0]) =~ /ARRAY/ ) { @msgs = @{$msgs[0]} };
3600
	$self->set_flag('\\Flagged', @msgs);
3601
}
3602
 
3603
sub unmark {
3604
	my($self, @msgs) = @_;
3605
	if ( ref($msgs[0]) =~ /ARRAY/ ) { @msgs = @{$msgs[0]} };
3606
	$self->unset_flag('\\Flagged', @msgs);
3607
}
3608
 
3609
sub unset_flag {
3610
	my($self, $flag, @msgs) = @_;
3611
	if ( ref($msgs[0]) =~ /ARRAY/ ) { @msgs = @{$msgs[0]} };
3612
	$flag =~ /^\\/ or $flag = "\\" . $flag 
3613
		if $flag =~ /^(Answered|Flagged|Deleted|Seen|Draft)$/i;
3614
	$self->store( join(",",@msgs), "-FLAGS.SILENT (" . $flag . ")" );
3615
}
3616
 
3617
sub deny_seeing {
3618
	my($self, @msgs) = @_;
3619
	if ( ref($msgs[0]) =~ /ARRAY/ ) { @msgs = @{$msgs[0]} };
3620
	$self->unset_flag('\\Seen', @msgs);
3621
}
3622
 
3623
sub size {
3624
 
3625
	my ($self,$msg) = @_;
3626
	# return undef unless fetch is successful
3627
	my @data = $self->fetch($msg,"(RFC822.SIZE)");
3628
	return undef unless defined($data[0]);
3629
	my($size) = grep(/RFC822\.SIZE/,@data);
3630
 
3631
	$size =~ /RFC822\.SIZE\s+(\d+)/;
3632
 
3633
	return $1;
3634
}
3635
 
3636
sub getquotaroot {
3637
	my $self = shift;
3638
	my $what = shift;
3639
	$what = ( $what ? $self->Massage($what) : "INBOX" ) ;
3640
	$self->_imap_command("getquotaroot $what") or return undef;
3641
	return $self->Results;
3642
}
3643
 
3644
sub getquota {
3645
	my $self = shift;
3646
	my $what = shift;
3647
	$what = ( $what ? $self->Massage($what) : "user/$self->{User}" ) ;
3648
	$self->_imap_command("getquota $what") or return undef;
3649
	return $self->Results;
3650
}
3651
 
3652
sub quota 	{
3653
	my $self = shift;
3654
	my ($what) = shift||"INBOX";
3655
	$self->_imap_command("getquota $what")||$self->getquotaroot("$what");
3656
	return (	map { s/.*STORAGE\s+\d+\s+(\d+).*\n$/$1/ ? $_ : () } $self->Results
3657
	)[0] ;
3658
}
3659
 
3660
sub quota_usage 	{
3661
	my $self = shift;
3662
	my ($what) = shift||"INBOX";
3663
	$self->_imap_command("getquota $what")||$self->getquotaroot("$what");
3664
	return (	map { s/.*STORAGE\s+(\d+)\s+\d+.*\n$/$1/ ? $_ : () } $self->Results
3665
	)[0] ;
3666
}
3667
sub Quote {
3668
	my($class,$arg) = @_;
3669
	return $class->Massage($arg,NonFolderArg);
3670
}
3671
 
3672
sub Massage {
3673
	my $self= shift;
3674
	my $arg = shift;
3675
	my $notFolder = shift;
3676
	return unless $arg;
3677
	my $escaped_arg = $arg; $escaped_arg =~ s/"/\\"/g;
3678
	$arg 	= substr($arg,1,length($arg)-2) if $arg =~ /^".*"$/
3679
                and ! ( $notFolder or $self->STATUS(qq("$escaped_arg"),"(MESSAGES)"));
3680
 
3681
	if ($arg =~ /["\\]/) {
3682
		$arg = "{" . length($arg) . "}\x0d\x0a$arg" ;
3683
	} elsif ($arg =~ /\s|[{}()]/) {
3684
		$arg = qq("${arg}") unless $arg =~ /^"/;
3685
	} 
3686
 
3687
	return $arg;
3688
}
3689
 
3690
sub unseen_count {
3691
 
3692
	my ($self, $folder) = (shift, shift);
3693
	$folder ||= $self->Folder;
3694
	$self->status($folder, 'UNSEEN') or return undef;
3695
 
3696
	chomp(	my $r = ( grep 
3697
			  { s/\*\s+STATUS\s+.*\(UNSEEN\s+(\d+)\s*\)/$1/ }
3698
			  $self->History($self->Transaction)
3699
			)[0]
3700
	);
3701
 
3702
	$r =~ s/\D//g;
3703
	return $r;
3704
}
3705
 
3706
 
3707
 
3708
# Status Routines:
3709
 
3710
 
3711
sub Status            { $_[0]->State                           ;       }
3712
sub IsUnconnected     { ($_[0]->State == Unconnected)  ? 1 : 0 ;       }
3713
sub IsConnected       { ($_[0]->State >= Connected)    ? 1 : 0 ;       }
3714
sub IsAuthenticated   { ($_[0]->State >= Authenticated)? 1 : 0 ;       }
3715
sub IsSelected        { ($_[0]->State == Selected)     ? 1 : 0 ;       }               
3716
 
3717
 
3718
# The following private methods all work on an output line array.
3719
# _data returns the data portion of an output array:
3720
sub _data {   defined $_[1] and ref $_[1] and defined $_[1]->[TYPE] or return undef; $_[1]->[DATA]; }
3721
 
3722
# _index returns the index portion of an output array:
3723
sub _index {  defined $_[1] and ref $_[1] and defined $_[1]->[TYPE] or return undef; $_[1]->[INDEX]; }
3724
 
3725
# _type returns the type portion of an output array:
3726
sub _type {  defined $_[1] and ref $_[1] and defined $_[1]->[TYPE] or return undef; $_[1]->[TYPE]; }
3727
 
3728
# _is_literal returns true if this is a literal:
3729
sub _is_literal { defined $_[1] and ref $_[1] and defined $_[1]->[TYPE] and $_[1]->[TYPE] eq "LITERAL" };
3730
 
3731
# _is_output_or_literal returns true if this is an 
3732
#  	output line (or the literal part of one):
3733
sub _is_output_or_literal { 
3734
              defined $_[1] and ref $_[1] and defined $_[1]->[TYPE] and 
3735
			($_[1]->[TYPE] eq "OUTPUT" || $_[1]->[TYPE] eq "LITERAL") 
3736
};
3737
 
3738
# _is_output returns true if this is an output line:
3739
sub _is_output { defined $_[1] and ref $_[1] and defined $_[1]->[TYPE] and $_[1]->[TYPE] eq "OUTPUT" };
3740
 
3741
# _is_input returns true if this is an input line:
3742
sub _is_input { defined $_[1] and ref $_[1] and defined $_[1]->[TYPE] and $_[1]->[TYPE] eq "INPUT" };
3743
 
3744
# _next_index returns next_index for a transaction; may legitimately return 0 when successful.
3745
sub _next_index { 
3746
      defined(scalar(@{$_[0]->{'History'}{$_[1]||$_[0]->Transaction}}))       ? 
3747
		scalar(@{$_[0]->{'History'}{$_[1]||$_[0]->Transaction}}) 		: 0 
3748
};
3749
 
3750
sub _cram_md5 {
3751
  my ($code, $client) = @_;
3752
  my $hmac = Digest::HMAC_MD5::hmac_md5_hex(MIME::Base64::decode($code),
3753
                                            $client->Password());
3754
  return MIME::Base64::encode($client->User() . " $hmac");
3755
}
3756
 
3757
 
3758
 
3759
sub Range {
3760
	require "Mail/IMAPClient/MessageSet.pm";
3761
	my $self = shift;
3762
	my $targ = $_[0];
3763
	#print "Arg is ",ref($targ),"\n";
3764
	if (@_ == 1 and ref($targ) =~ /Mail::IMAPClient::MessageSet/ ) {
3765
		return $targ;
3766
	}
3767
	my $range = Mail::IMAPClient::MessageSet->new(@_);
3768
	#print "Returning $range :",ref($range)," == $range\n";
3769
	return $range;
3770
}
3771
 
3772
my $not_void = 1;