File Coverage

blib/lib/IMAP/Client.pm
Criterion Covered Total %
statement 242 815 29.6
branch 123 516 23.8
condition 31 150 20.6
subroutine 28 99 28.2
pod 65 88 73.8
total 489 1668 29.3


line stmt bran cond sub pod time code
1             # IMAP::Client -low-level advanced IMAP manipulation w/ referral support
2             #
3             # Copyright (c) 2005 Brenden Conte , All Rights Reserved
4             #
5              
6 9     9   225200 use strict;
  9         27  
  9         366  
7 9     9   48 use warnings;
  9         22  
  9         397  
8             #use diagnostics;
9              
10              
11             package IMAP::Client;
12              
13 9     9   10060 use IO::Socket::INET;
  9         291062  
  9         82  
14 9     9   21474 use IO::Socket::SSL;
  9         837500  
  9         112  
15 9     9   11039 use MIME::Base64;
  9         8623  
  9         614  
16 9     9   12634 use URI::imap;
  9         183491  
  9         372  
17 9     9   114 use URI::Escape;
  9         18  
  9         633  
18              
19 9     9   54 use Exporter;
  9         20  
  9         48667  
20              
21              
22             $|=1;
23              
24             our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
25              
26             @ISA = qw( Exporter );
27             $VERSION = "0.13";
28             @EXPORT = qw ();
29             @EXPORT_OK = qw();
30             %EXPORT_TAGS = ();
31              
32             # Create Class variables
33             my %Instances;
34             my @SERVER_RESPONSES = ('exists', 'recent'); # constant value
35             my $server_response_callback = undef;
36             my $ID;
37              
38             =pod
39              
40             =head1 NAME
41              
42             IMAP::Client - Advanced manipulation of IMAP services w/ referral support
43              
44             =head1 SYNOPSIS
45              
46             use IMAP::Client
47            
48             my $imap = new IMAP::Client($server);
49             unless (ref $imap) {
50             die "Failed to create object: $imap\n";
51             }
52             (or)
53             my $imap = new IMAP::Client();
54             $imap->connect(PeerAddr => $server,
55             ConnectMethod => 'SSL STARTTLS PLAIN',
56             )
57             or die "Unable to connect to [$server]: ".$imap->error();
58              
59             $imap->onfail('ERROR');
60             $imap->errorstyle('STACK');
61             $imap->debuglevel(1);
62             $imap->capability_checking(1);
63            
64             sub showstats ($) {
65             my $resp = shift;
66             foreach my $attr (keys %{$resp}) {
67             print "$attr: $resp->{$attr}\n";
68             }
69             }
70             $imap->register_mailbox_update(\&showstats);
71              
72             $imap->authenticate($user,$pass)
73             or die "Unable to authenticate as $user ".$imap->error()."\n";
74             (or)
75             $imap->authenticate($user,$pass,$authas_user)
76             or die "Unable to authenticate as $user on behalf of $authas_user: ".$imap->error()."\n";
77              
78             $imap->id() or die $imap->error();
79             $imap->capability() or die $imap->error();
80             $imap->noop() or die $imap->error();
81              
82             FIXME: more examples here
83              
84             =head1 IMPORTANT! READ THIS FIRST IF YOU ARE UPGRADING FROM PRE-0.10 TO 0.10 OR ABOVE!
85              
86             As of IMAP::Client 0.10, the "_active_server" mechanism has been removed, replaced instead by a class-wide monitoring of objects. This means that if you have any code that utilizes the active_server functionality (using more than one connection in an instance of IMAP::Client), you will need to change your code to create seperate instances for each connection.
87              
88             Unfortunately, backward compatibility could not be maintained with this change. However since tracking is now behind-the-scenes, this style should be the final one.
89              
90             =head1 DESCRIPTION
91              
92             This module was created as a low-level inteface to any IMAP server. It was built to be a 'clear box' solution to working with an IMAP environment. The idea is that anything an IMAP client should be able to do, and any information available via the IMAP specs, should be available to a client interface and user. This way, the full strength of the IMAP protocol and data can be utilized, ideally in the most network-efficient mannger possible, rather than being contrained only to a subset of commands or data-limited responses. If the server says it, the client should be able to see it.
93              
94             This module also takes steps to be able to handle anticipated situations for the user rather than forcing a per-implementation behavior for such expected events, such as referrals. IMAP::Client will fully support referrals, and will transparently handle them for whatever command is issued to them (so long as the referral s for anonymous or the same user with the same password - a new user or different password would require a new username/password to be obtained. As of 0.01, this is not supported, however the framework is down.
95              
96             This module also tries to follow the various RFCs for IMAPrev1 communications very closely, enforcing client-side responsabilities where appropriate. The complete lists of RFCs referenced for this module include:
97            
98             =over 4
99              
100             =item * RFC 3501 - INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1 (partial)
101              
102             =item * RFC 2086 - IMAP4 ACL extension (0.01)
103              
104             =item * RFC 2087 - IMAP4 QUOTA extension (0.01)
105              
106             =item * RFC 2088 - IMAP4 non-synchronizing literals (0.01)
107              
108             =item * RFC 2177 - IMAP4 IDLE command (Not supported yet)
109              
110             =item * RFC 2192 - IMAP4 URL Scheme (0.01)
111              
112             =item * RFC 2193 - IMAP4 Mailbox Referrals (0.01 [Partial])
113              
114             =item * RFC 2342 - IMAP4 Namespace (Not directly supported yet)
115              
116             =item * RFC 2359 - IMAP4 UIDPLUS extension (Partial in 0.01 - UID EXPUNGE check ok, need COPYUID and APPENDUID support)
117              
118             =item * RFC 2971 - IMAP4 ID extension (0.01)
119              
120             =item * RFC 3348 - IMAP4 Child Mailbox Extention (Not directly supported yet)
121              
122             =item * RFC 3502 - IMAP MULTIAPPEND extention (Not directly supported yet)
123              
124             =item * RFC 3516 - Binary Content Extention (Not directly supported yet)
125              
126             =item * RFC 3691 - Internet Message Access Protocol (IMAP) UNSELECT command (Not directly supported yet)
127              
128             =back
129              
130             In addition, the following drafts functionalities are also included. While functionality is included for these drafts (because a server is using them), drafts expire after 6 months, and thus functionality from the server side may be spotty at best.
131              
132             =over 4
133              
134             =item * draft-ietf-imapext-annotate-15 - IMAP ANNOTATE Extension (Not directly supported yet)
135              
136             =item * draft-daboo-imap-annotatemore-08 - IMAP ANNOTATEMORE Extension (Partial in 0.12 - GETANNOTATION works)
137              
138             =back
139              
140              
141             =head1 DEFINITIONS
142              
143             =over 4
144              
145             =item * sequence set - A comma-seperated list of numbers and number ranges. A number range is in the format number:number, such as "2:4", meaning messages 2 through 4.
146              
147             =back
148              
149             =head1 METHODS - SUBINTERFACE
150              
151             These are the lowest-level functions for use by the program. These offer the most raw access for interacting with an IMAP service, short of doing it manually.
152              
153             =cut
154              
155             ########## Internal, Undocumented Support functions ##########
156             # First things first...
157             sub dprint ($$$) {
158 20     20 0 40 my ($self, $debugbit,$string) = @_;
159            
160             # Error checking - make sure the debugbit is only 1 bit
161 20 50 33     107 unless ($debugbit && !($debugbit & ($debugbit - 1))) {
162 0         0 warn "Debugbit passed into dprint is not a single bit! (String = [$string])\n";
163             }
164            
165 20 50       66 if ($self->{DEBUG} & $debugbit) {
166 0         0 print STDERR $string;
167             }
168             };
169              
170              
171             # RFC3501 specifies a valid tag contains any char other than...
172             my $RESP_SPECIALS = '\]';
173             my $ATOM_SPECIALS = '\(\)\{ ';
174             my $LIST_WILDCARDS = '\%\*';
175             my $QUOTED_SPECIALS = '\"\\\\';
176             my $VALID_TAG = "[^".$RESP_SPECIALS.$ATOM_SPECIALS.$LIST_WILDCARDS.$QUOTED_SPECIALS.'+'."]";
177             sub ok_response(@) {
178 150 100   150 0 2017 return (($_[$#_] =~ /^$VALID_TAG+?\s*OK\s+/) ? 1 : 0);
179             }
180             sub continue_response(@) {
181 28 100   28 0 143 return(($_[$#_] =~ /^\+ /) ? 1 : 0);
182             }
183             sub untagged_response(@) {
184 39 100   39 0 328 return(($_[$#_] =~ /^\* (?!BAD)/) ? 1 : 0);
185             }
186             sub untagged_ok_response(@) {
187 28 100   28 0 148 return (($_[$#_] =~ /^\*\s*OK\s+/) ? 1 : 0);
188             }
189             sub failure_response(@) {
190 28 100   28 0 351 return (($_[$#_] =~ /^(?:$VALID_TAG|\*)+?\s*(BAD|NO)\s+/) ? 1 : 0);
191             }
192              
193             sub is_sequence_set($) {
194 0 0   0 0 0 return (($_[0] =~ /^(?:(?:\d+|\d+\:\d+)(?:\,\d+|\d+\:\d+)*|\d+:\*|\*)$/) ? 1 : 0);
195             }
196             sub sequencify (@){ # preserves ordering over compression
197 10     10 0 18 my $string;
198 10         13 my ($start,$end);
199 10         49 foreach my $number (@_) {
200 43 100 50     153 ($start = $end = $number and next) unless ($start); # first entry;
201 33 50       60 if ($start) {
202 33 100       112 if ($end+1 == $number) {
203 17         29 $end = $number;
204             } else {
205 16 100       48 $string .= ($start == $end) ? "$start," : "$start:$end,";
206 16         34 $start = $end = $number;
207             }
208             }
209             }
210 10 100       33 $string .= ($start == $end) ? "$start" : "$start:$end"; # last entry
211 10         53 return($string);
212             }
213              
214             sub throw_error($$) {
215 0     0 0 0 my ($self,$error) = @_;
216 0         0 $error =~ s/^(.*?)\s*\r?\n?$/$1/;
217 0   0     0 my $newerror = $error || "Unknown/Generic error";
218              
219 0 0       0 if ($self->{onfail} eq 'error') {
    0          
220 0 0 0     0 if (($self->{errorstyle} eq 'stack') && (!$self->{error_read})) {
221 0         0 $self->{error} .= "\n". $newerror;
222             } else {
223 0         0 $self->{error} = $newerror;
224             }
225 0         0 $self->{error_read} = 0;
226 0         0 return (undef);
227             } elsif ($self->{onfail} eq 'abort') {
228 0         0 print STDERR $newerror."\n";
229 0         0 exit(-1);
230             } else {
231 0         0 print STDERR "INTERNAL ERROR: Unknown failure handler string [",$self->{onfail},"], aborting...\n";
232 0         0 exit(-1);
233             }
234             }
235             sub parse_select_examine(@) {
236 0 0   0 0 0 return() unless $_[0];
237 0         0 my %ret;
238 0         0 my ($_t, $_v); # t is the TITLE (or label/tag), and v is the VALUE of t.
239 0         0 foreach my $line (@_) {
240 0 0       0 if (ok_response($line)) { # done
    0          
    0          
    0          
    0          
241 0         0 my ($perm) = $line =~ /\[([\w-]+)\]/;
242 0         0 $ret{OK} = $perm;
243             } elsif (($_t,$_v) = $line =~ /(\w+)\s*\((.*)\)/) { # flags: TITLE (\F \T...)
244 0         0 $_v =~ s/\\//g;
245 0         0 $ret{$_t} = $_v;
246             } elsif (($_t,$_v) = $line =~ /\[(\w+)\s*(\d+)\]/) { # title-num: [TITLE #]
247 0         0 $ret{$_t} = $_v;
248             } elsif (($_v, $_t) = $line =~ /(\d+)\s*(\w+)/) { # num-title: # TITLE
249 0         0 $ret{$_t} = $_v;
250             } elsif (($_t, $_v) = $line =~ /NO\s+\[(.*?)\]\s+(.*)$/) { # NO [TITLE] VALUE (usually for ALERTs)
251 0         0 $ret{$_t} = $_v;
252             } else {
253 0         0 warn "Unknown tagless response(): $line\n";
254             }
255             }
256 0         0 return(%ret);
257             }
258             sub parse_list_lsub(@) {
259 2     2 0 15346 my @result = @_;
260 2         4 my @list;
261 2         31 foreach my $line (@result) {
262 117 100       245 next if (ok_response($line));
263 115         827 my ($flags,$reference,$mailbox) = $line =~ /^\*\s+LIST\s+\((.*?)\)\s+\"(.*?)\"\s+\"?(.*?)\"?\r\n$/;
264 115         410 my %hash = (FLAGS => $flags, REFERENCE => $reference, MAILBOX => $mailbox);
265 115         319 push @list, \%hash;
266             }
267 2 50       9 @list = () unless ($list[0]);
268 2         55 return(@list);
269             }
270              
271             # from http://www.perl.com/pub/a/2002/08/22/exegesis5.html?page=5
272             our $parens;
273             # $parens = qr/
274             # \( # Match a literal '('
275             # (?: # Start a non-capturing group
276             # (?> # Never backtrack through...
277             # [^()] + # Match a non-paren (repeatedly)
278             # ) # End of non-backtracking region
279             # | # Or
280             # (??{$parens}) # Recursively match entire pattern
281             # )* # Close group and match repeatedly
282             # \) # Match a literal ')'
283             # /x;
284             $parens = qr/\((?:(?>[^()]+)|(??{$parens}))*\)/s; # nested paren matcher
285             my $nparens = qr/\((?:(?>[^()]+)|(??{$parens}))*\)|NIL/s;
286             my $string = '\"[^\"]+\"';
287             my $nstring = "(?:$string|NIL)";
288             my $number = '\d+';
289             sub quote_once ($) {
290 0     0 0 0 my $string = shift;
291 0 0       0 $string = "\"$string\"" unless ($string =~ /^\".*\"$/); # Quote if its not already quoted
292 0         0 return($string);
293             }
294             sub dequote($) {
295 173 50   173 0 538 return(undef) unless $_[0];
296 173 100       344 if ($_[0] eq "NIL") { return undef; }
  7         25  
297 166         569 my ($base) = $_[0] =~ /^\"(.*)\"/;
298 166   66     739 return($base || $_[0]);
299             }
300             sub debracket($) {
301 4 50   4 0 9 return(undef) unless $_[0];
302 4 50       11 if ($_[0] eq "NIL") { return undef; }
  0         0  
303 4         15 my ($base) = $_[0] =~ /^\<(.*)\>/;
304 4   33     23 return($base || $_[0]);
305             }
306             sub address($) {
307 28     28 1 226 my $rawlist = shift;
308 28         26 my @addresses;
309 28 100       142 if ($rawlist eq "NIL") { return undef; }
  12         42  
310 16         86 $rawlist =~ s/^\((.*)\)$/$1/;
311 16         133 foreach my $address (split (/\)\(/,$rawlist)) {
312 16         233 my ($name,$relay,$mailbox,$host) = $address =~/^\(?($nstring) ($nstring) ($nstring) ($nstring)\)?$/;
313 16 50       40 next unless ($name);
314 16 100       27 push @addresses,((dequote($name)) ? dequote($name).' ' : '').'<'.
    50          
315             dequote($mailbox).(($relay ne 'NIL') ? '%'.dequote($relay) : '').
316             '@'.dequote($host).'>';
317             }
318 16         213 return(join(',',@addresses));
319             }
320             sub parse_parameters($);
321             sub parse_parameters($) { # Parse parameter sequences, including w/ nested parens
322 69     69 0 98 my $parameters = shift @_;
323 69 100       124 return $parameters unless $parameters; # returns both undefs and empty strings ('')
324 68 100       321 return $parameters if (substr($parameters,0,1) ne '(');
325 29         198 $parameters =~ s/^\((.*)\)$/$1/;
326 29         95 my %hash;
327 29         192 while ($parameters) {
328 41         2414 my ($key,$value,$more) = $parameters =~ /^\s?\"(.*?)\" ($nstring|$parens)(.*)$/g;
329 41         113 $parameters = $more;
330 41   100     83 $value = dequote($value) || '';
331 41         84 $hash{uc($key)} = parse_parameters($value);
332             }
333 29         93 return(\%hash);
334             }
335             sub parse_envelope($) {
336 4     4 0 9 my $value = shift;
337 4         6 my %ret;
338             my $_t;
339 4         722 my ($date,$subject,$from,$sender,$replyto,$to,$cc,$bcc,$inreplyto,$messageid) = $value =~/^\(($string) ($nstring) ($nparens) ($nparens) ($nparens) ($nparens) ($nparens) ($nparens) ($nparens) ($nstring)\)$/;
340              
341 4 50       18 $ret{'DATE'} = $_t if ($_t = dequote($date));
342 4 50       12 $ret{'SUBJECT'} = $_t if ($_t = dequote($subject));
343 4 50       15 $ret{'FROM'} = $_t if ($_t = address($from));
344 4 50       9 $ret{'SENDER'} = $_t if ($_t = address($sender));
345 4 50       12 $ret{'REPLYTO'} = $_t if ($_t = address($replyto));
346 4 50       8 $ret{'TO'} = $_t if ($_t = address($to));
347 4 50       10 $ret{'CC'} = $_t if ($_t = address($cc));
348 4 50       8 $ret{'BCC'} = $_t if ($_t = address($bcc));
349 4 50       10 $ret{'INREPLYTO'} = $_t if ($_t = address($inreplyto));
350 4 50       12 $ret{'MESSAGEID'} = $_t if ($_t = debracket(dequote($messageid)));
351 4         22 return(\%ret);
352             }
353             # recursive function for building body hash
354              
355             sub parse_body_structure ($$);
356             sub parse_body_structure ($$) {
357 16     16 0 274 my $structure = shift;
358 16         21 my $level = shift;
359 16 50       36 return undef unless $structure;
360              
361 16         18 my $entry=1;
362 16         17 my %ret;
363             my $substruct;
364 16         484 while ((($substruct,$structure) = $structure =~ /^($parens)(.*)$/)) {
365             # printf("DEBUG[$level]: Deciding fate of [%.80s...] ".(($structure)?"":'')."\n",$substruct);# if ($self->{DEBUG});
366              
367             #body-type-mpart/body-type-message
368 26 100       9052 if (my @results = $substruct =~ /^\((?:($parens+) ($string)|\"MESSAGE\" \"RFC822\" ($nparens) ($nstring) ($nstring) ($string) ($number) ($parens) ($parens) ($number))(?: ($nparens)(?: ($nparens)(?: ($nparens|$string)(?: ($nstring)(?:( $string| $number| $parens)+)?)?)?)?)?\)$/) {
    50          
369 8         22 my ($body1, $subtype, $parameters, $id, $description, $encoding, $size, $envelope, $body2, $lines, $ext_parameters, $dsp, $lang, $loc, @extentions) = @results;
370             # body1 and body2 will never both contain something (its an XOR relationship), so we can just use (body1 || body2) for 'the active body'
371             # print "DEBUG[$level]: Processing body-type-" . (($parameters) ? "message" : "mpart") . " [$entry]\n";# if ($self->{DEBUG});
372             # print "DEBUG[$level]: subtype = $subtype\n";
373 8 50       16 if ($body1) { # ONLY create a new level if there is more than one entity on this (or next) level
374             # print "DEBUG[$level]: Diving one level deeper\n";# if ($self->{DEBUG});
375 8   33     11 $ret{$entry}=\%{{(parse_body_structure($body1||$body2,$level+1))}};
  8         43  
376             # print "DEBUG[$level]: rose back - above saved in [$entry]\n";# if ($self->{DEBUG});
377             } else {
378             # print "DEBUG[$level]: applying at same-level\n";# if ($self->{DEBUG});
379             # print "DEBUG[$level]: Parsing envelope\n";# if ($self->{DEBUG} && $parameters);
380 0   0     0 my %body_step = parse_body_structure($body1||$body2,$level+1);
381             # print "DEBUG[$level]: collapsing above into current [$entry]\n";# if ($self->{DEBUG});
382 0         0 my $new_entry = $entry;
383 0         0 foreach my $key (keys %body_step) {
384 0 0       0 if ($key =~ /^\d+$/) {
385             # print "DEBUG[$level]: Storing a level higher [$entry+($key-1)]=$body_step{$key} at this level\n";# if ($self->{DEBUG});
386 0         0 $ret{$entry+($key-1)} = $body_step{$key}; #mv to local lvl
387 0         0 $new_entry = $entry+($key-1);
388             } else {
389             # print "DEBUG[$level]: Storing mpart [$key]=[$body_step{$key}] in [$entry]\n";# if ($self->{DEBUG});
390 0         0 $ret{$entry}->{$key} = $body_step{$key};
391             }
392             }
393 0         0 $entry = $new_entry;
394             }
395 8 50       22 my %envelope = parse_envelope($envelope) if ($envelope);
396 8 50       21 $ret{$entry}->{'ENVELOPE'} = \%envelope if (%envelope); #$fetch->...->{header}->{?}
397             # apply local-entry stuff here, after {$entry} has been 'ovewritten' above
398 8 50       21 $ret{$entry}->{'CONTENTTYPE'} = "MULTIPART/".dequote($subtype) if ($subtype); # the only topic that is applied one level above
399 8 50 33     24 $ret{$entry}->{'PARAMETERS'} = parse_parameters($parameters) if ($parameters && ($parameters ne 'NIL'));
400 8 50 33     20 $ret{$entry}->{'ID'} = $id if ($id && ($id ne 'NIL'));
401 8 50 33     19 $ret{$entry}->{'DESCRIPTION'} = $description if ($description && ($description ne 'NIL'));
402 8 50       16 $ret{$entry}->{'ENCODING'} = $encoding if ($encoding);
403 8 50       17 $ret{$entry}->{'SIZE'} = $size if ($size);
404 8 50       17 $ret{$entry}->{'LINES'} = $lines if ($lines);
405 8 50 66     30 $ret{$entry}->{'DISPOSITION'} = parse_perameters($dsp) if ($dsp && ($dsp ne 'NIL'));
406 8 50 66     26 $ret{$entry}->{'LANGUAGE'} = parse_parameters($lang) if ($lang && ($lang ne 'NIL'));
407 8 50 33     31 $ret{$entry}->{'LOCATION'} = $loc if ($loc && ($loc ne 'NIL'));
408 8 100 66     33 $ret{$entry}->{'EXT_PARAMETERS'} = parse_parameters($ext_parameters) if ($ext_parameters && ($ext_parameters ne 'NIL'));
409             # WARNING: custom extentions currently ignored
410            
411             }
412            
413            
414             #body-type-text/body-type-basic/body-type-msg (media)
415             elsif (my ($type, $subtype, $parameters, $id, $description, $encoding, $size, $lines, $md5, $dsp, $lang, $loc, @extentions) = $substruct =~ /^\(($string) ($string) ($nparens) ($nstring) ($nstring) ($string) ($number)(?: ($number))?(?: ($nstring)(?: ($nparens)(?: ($nparens|$string)(?: ($nstring)(?:( $string| $number| $parens)+)?)?)?)?)?\)$/) {
416             # hash the parameters
417             # print "DEBUG[$level]: Processing body-type-text/basic [$entry]\n";# if ($self->{DEBUG});
418 18         22 my %t_ret;
419 18         37 $t_ret{'CONTENTTYPE'} = dequote($type).'/'.dequote($subtype);
420 18         47 $t_ret{'PARAMETERS'} = parse_parameters($parameters);
421 18 50       48 $t_ret{'ID'} = $id if ($id ne 'NIL');
422 18 50 33     84 $t_ret{'DESC'} = $description if ($description && ($description ne 'NIL'));
423 18         33 $t_ret{'ENCODING'} = dequote($encoding);
424 18         92 $t_ret{'SIZE'} = $size;
425 18 100       64 $t_ret{'LINES'} = $lines if ($lines);
426 18 50 66     59 $t_ret{'MD5'} = $md5 if ($md5 && ($md5 ne 'NIL'));
427 18 100 100     104 $t_ret{'DISPOSITION'} = parse_parameters($dsp) if ($dsp && ($dsp ne 'NIL'));
428 18 50 66     59 $t_ret{'LANGUAGE'} = parse_parameters($lang) if ($lang && ($lang ne 'NIL'));
429 18 50 33     43 $t_ret{'LOCATION'} = $loc if ($loc && ($loc ne 'NIL'));
430 18         53 $ret{$entry} = \%t_ret;
431             }
432            
433             else { #unknown (error)
434 0         0 die("Unknown structure in parse_body_structure: [$substruct]");
435             }
436 26         814 $entry++;
437             }
438 16 100       39 if ($level == 0) {
439             # print "DEBUG[$level]: Returning final result\n";# if ($self->{DEBUG});
440             # FIXME: DIRTY, DIRTY HACK - return self only if no children to children- otherwise return level 1
441 8 100       25 if ($ret{1}->{1}) {
442 6         8 return(%{$ret{1}});
  6         42  
443             } else {
444 2         14 return(%ret);
445             }
446             } else {
447             # print "DEBUG[$level]: Returning\n";# if ($self->{DEBUG});
448 8         53 return(%ret);
449             }
450             }
451              
452             sub parse_fetch($@) {
453 4     4 0 67653 my ($self, @resp) = @_;
454              
455             ### Parse out fetch response into internal structure
456             # Load up hash with fetch results (one entry per * tag FETCH)
457 4         10 my %fetchsets;
458 4         34 my $msgid = -1;
459 4         11 foreach my $line (@resp) {
460 9 100       117 if ($line =~ /^\* (\d+) FETCH \(/gs) {
    100          
461 4         14 $msgid = $1;
462 4         20 $fetchsets{$msgid} .= $line;
463             } elsif (ok_response($line)) {
464 4         54 $msgid = -1; # We Found the OK - This should be the end of the command
465             } else {
466 1 50       7 $self->throw_error("INTERNAL ERROR: No msgid set, but still trying to build fetchsets\n") if ($msgid < 0);
467 1         5 $fetchsets{$msgid} .= $line;
468             }
469             }
470              
471 4         34 $self->dprint(0x02, "-- parse_fetch: Fetch count: ".scalar(keys %fetchsets)."\n");
472 4         4 my %results;
473              
474             #$self->dprint(0x02, "-- parse_fetch: evaluating [$fetchset]\n");
475              
476             # find FETCH lines and process results
477 4         145 foreach my $msgid (keys %fetchsets) {
478 4         107 $self->dprint(0x02, "-- parse_fetch: Parsing FETCH response [$fetchsets{$msgid}]\n");
479 4         8 my %ret;
480 4         11 my $len = length($msgid)+10; # get length of just-extracted line
481 4         30 $fetchsets{$msgid} = substr($fetchsets{$msgid}, $len); # remove fetch line from the remainder of the FETCH response
482 4         21 $self->dprint(0x02, "-- parse_fetch: FETCH response after FETCH line removed: [$fetchsets{$msgid}]\n");
483             # Break into hash (unfortunately, we can't do it inline regexp, since thre is a 32765 char limit on {min,max}. grr)
484 4         7 my %result_entries;
485 4         15 while ($fetchsets{$msgid}) {
486 25 50       171 my ($key, $length) = ($fetchsets{$msgid} =~ /^(BODY|BODYSTRUCTURE|ENVELOPE|FLAGS|INTERNALDATE|UID|RFC822.SIZE|BODY\[.*?\](?:\<\d+\>)?|RFC822(?:\.TEXT|\.HEADER)?) (?:\{(\d+)\}\r?\n?)?/gis) or return($self->throw_error("INTERNAL ERROR: unable to find keys in [".substr($fetchsets{$msgid},0,20)."...]"));
487 25 100       112 $fetchsets{$msgid} = substr($fetchsets{$msgid},(length($key)+1)+(($length) ? length($length)+4 : 0)); # trim newly found entries
488 25 100       37 if ($length) {
489 1         6 $result_entries{$key}{'length'} = $length;
490 1         5 $result_entries{$key}{'value'} = substr($fetchsets{$msgid},0,$length); # Save length of value
491              
492 1 50       7 if (length($result_entries{$key}{'value'}) < $length) {
493 0         0 return($self->throw_error("INTERNAL ERROR: unable to get [$length] length of fetchset [".length($fetchsets{$msgid})." available]"));
494             }
495 1         3 $fetchsets{$msgid} = substr($fetchsets{$msgid},$length); # trim length of message
496             } else { # no length, just a value
497 24 50       1194 ($result_entries{$key}{'value'}) = ($fetchsets{$msgid} =~ /^($parens|$nstring|$number)/gis)
498             or return($self->throw_error("INTERNAL ERROR: No value in [".substr($fetchsets{$msgid},0,20)."]"));
499 24         162 $fetchsets{$msgid} = substr($fetchsets{$msgid},length($result_entries{$key}{'value'}));
500             }
501 25 50       186 $fetchsets{$msgid} =~ s/^[\)\r\n\s]*// if ($fetchsets{$msgid}); # Remove all 'unessesary trailers' (varies depending on end-of-command, end-of-line, etc)
502             }
503              
504              
505             # Ok, we have our entries for this msgid - store them
506 4         18 foreach my $key (keys %result_entries) {
507 25 100 100     243 if ($key eq "FLAGS") { #list of flags
    100          
    50          
    50          
    100          
    100          
    100          
508 4         29 $result_entries{$key}{'value'} =~ s/^\((.*)\)/$1/; # deparenthesize
509 4         22 my @flags = split(/ /,$result_entries{$key}{'value'}); # split flags to list
510 4         12 $ret{$key}=\@flags;
511             } elsif ($key =~ /^BODY\[(.*)\](?:\<(\d+)\>)?$/) {
512 1         5 my ($section, $offset) = ($1, $2); # save selection id, offset
513 1 50       5 unless ($ret{'BODY'}) { my %newhash; $ret{'BODY'} = \%newhash; } # if no accompanying BODY[STRCUTURE]
  1         2  
  1         3  
514 1         2 my $hashptr = $ret{'BODY'}; # set up hash pointer
515 1         4 foreach my $next (split(/\./,$section)) { # split on . for each level
516 1 50       3 unless ($hashptr->{$next}) { # if a BODYSTRUCTURE or BODY does not acompany the BODY[]
517 1         3 my %newhash;
518 1         3 $hashptr->{$next} = \%newhash; # create structure depth
519             }
520 1         4 $hashptr = $hashptr->{$next}; # dive one level deeper
521             }
522 1         4 $hashptr->{'BODY'} = $result_entries{$key}{'value'};
523 1   50     5 $hashptr->{'BODYSIZE'} = $result_entries{$key}{'length'} || 0;
524 1 50       5 $hashptr->{'OFFSET'} = $offset if $offset;
525             } elsif ($key eq "RFC822") {
526 0         0 my ($headers, $text) = split(/\r\n\r\n/,$result_entries{$key}{'value'});
527 0         0 $ret{'RFC822'}->{'HEADERS'} = $headers;
528 0         0 $ret{'RFC822'}->{'TEXT'} = $text;
529             } elsif (my ($token) = ($key =~ /^RFC822.(.+)$/)) {
530 0         0 $ret{'RFC822'}->{$token} = $result_entries{$key}{'value'};
531             } elsif ($key eq "INTERNALDATE") {
532 4         38 $result_entries{$key}{'value'} =~ s/\"([^\"]+)\"/$1/; # remove quotes
533 4         13 $ret{$key} = $result_entries{$key}{'value'};
534             } elsif ($key eq "ENVELOPE") {
535 4         21 $ret{$key} = parse_envelope($result_entries{$key}{'value'});
536             } elsif (($key eq "BODY") || ($key eq "BODYSTRUCTURE")) {
537 8         31 my %body = parse_body_structure($result_entries{$key}{'value'},0);
538 8         38 $ret{$key} = \%body;
539             } else {
540 4         18 $ret{$key}=$result_entries{$key}{'value'};
541             }
542             }
543 4 50       25 die "*****************************WARNING: ret is empty!************************\n" unless (%ret);
544 4         28 $results{$msgid} = \%ret;
545             }
546 4 50       60 die "*****************************WARNING: results are empty!************************\n" unless (%results);
547 4         62 return(%results);
548             }
549             sub extract_body($$) {
550 0     0 0 0 my ($text,$length) = @_;
551 0         0 $length =~ s/^\{(\d+)\}$/$1/; # extract body length
552 0         0 my ($body) = ($text =~ /^\r\n(.{$length})/s); # extract length of body
553 0         0 return($body);
554             }
555             sub parse_search (@) {
556 10     10 0 10219 my (@resp) = @_;
557 10         18 my @results = ();
558             # find SEARCH line and process results
559 10         36 foreach my $line (@resp) {
560 12 100       83 next unless ($line =~ s/^\*\s+SEARCH\s+([\d+\s]+)\s*\r\n$/$1/);
561 8         32 @results = split(/ /,$line);
562 8         14 last; # theres only 1 line
563             }
564 10 100       53 return(wantarray ? @results : @results ? sequencify(@results) : undef );
    100          
565             }
566 9     9   123 use re 'eval';
  9         23  
  9         132467  
567              
568             sub fill_permissions($) {
569 0     0 0 0 my $hash = shift;
570             # map short answers to long
571 0 0       0 $hash->{'lookup'} = 1 if ($hash->{'l'});
572 0 0       0 $hash->{'list'} = 1 if ($hash->{'l'});
573 0 0       0 $hash->{'read'} = 1 if ($hash->{'r'});
574 0 0       0 $hash->{'seen'} = 1 if ($hash->{'s'});
575 0 0       0 $hash->{'write'} = 1 if ($hash->{'w'});
576 0 0       0 $hash->{'insert'} = 1 if ($hash->{'i'});
577 0 0       0 $hash->{'post'} = 1 if ($hash->{'p'});
578 0 0       0 $hash->{'create'} = 1 if ($hash->{'c'});
579 0 0       0 $hash->{'delete'} = 1 if ($hash->{'d'});
580 0 0       0 $hash->{'admin'} = 1 if ($hash->{'a'});
581 0 0       0 $hash->{'administer'} = 1 if ($hash->{'a'});
582            
583             # And vice versa
584 0 0 0     0 $hash->{'l'} = 1 if ($hash->{'lookup'} || $hash->{'list'});
585 0 0       0 $hash->{'r'} = 1 if ($hash->{'read'});
586 0 0       0 $hash->{'s'} = 1 if ($hash->{'seen'});
587 0 0       0 $hash->{'w'} = 1 if ($hash->{'write'});
588 0 0       0 $hash->{'i'} = 1 if ($hash->{'insert'});
589 0 0       0 $hash->{'p'} = 1 if ($hash->{'post'});
590 0 0       0 $hash->{'c'} = 1 if ($hash->{'create'});
591 0 0       0 $hash->{'d'} = 1 if ($hash->{'delete'});
592 0 0 0     0 $hash->{'a'} = 1 if ($hash->{'admin'} || $hash->{'administer'});
593            
594 0         0 return($hash);
595             }
596             sub parse_quota($$) {
597 2     2 0 1270 my ($localroot,$resp) = @_;
598 2         2 my @resp = @{$resp};
  2         35  
599 2         3 my %quota;
600            
601 2         5 foreach my $line (@resp) {
602 5 100       202 if (my @resources = ($line =~ /^\* QUOTA $localroot ($parens+)\r\n$/)) {
    100          
603 2         3 foreach my $resource (@resources) {
604 2         10 my ($topic, $values) = ($resource =~ /^\((\w+) (\d+ \d+)\)$/);
605 2 50       6 if (defined $topic) {
606 2         8 my @numbers = split(/ /,$values);
607 2         10 $quota{$topic} = \@numbers;
608             }
609             }
610             } elsif (my ($ref) = ($line =~ /^\* QUOTAROOT $localroot (.*)\r\n$/)) {
611 1         3 $quota{'ROOT'} = $ref;
612 1         4 $localroot = $ref;
613             }
614             }
615 2         14 return(%quota);
616             }
617              
618              
619             ########## Raw communications functions ##########
620              
621             =pod
622            
623             =over 4
624            
625             =item B
626              
627             Sends the string argument provided to the server. A tag is automatically prepended to the command.
628              
629             =cut
630            
631             sub imap_send ($$) {
632 0     0 1 0 my ($self,$string) = @_;
633             # return($self->throw_error("No servers defined for [$string]")) unless $self->{'server'};
634 0         0 my $server = $self->{'server'};
635              
636 0         0 my $tag = sprintf("%04i",++$self->{tag});
637 0         0 chomp($string);
638              
639 0         0 $self->dprint(0x01, ">> $tag $string\r\n");
640 0         0 print $server "$tag $string\r\n";
641              
642 0         0 $self->{tag} = $tag;
643             }
644              
645             =pod
646              
647             =item B
648              
649             Send the string argument provided to the server B. This is needed for some operations.
650              
651             =cut
652              
653             sub imap_send_tagless ($$) {
654 0     0 1 0 my ($self,$string) = @_;
655             # return($self->throw_error("No servers defined for [$string]")) unless $self->{'server'};
656 0         0 my $server = $self->{'server'};
657              
658 0         0 $self->dprint(0x01, ">> $string\r\n");
659 0         0 print $server "$string\r\n";
660             }
661              
662             =pod
663              
664             =item B
665              
666             Accept responses from the server until the previous tag is encountered, at which point return the entire response. CAUTION: can cause your program to hang if a tagged response isn't given. For example, if the command expects more input and issues a '+' response, and waits for input, this function will never return.
667              
668             =cut
669              
670             # FIXME: A timeout option would be nice...
671             sub imap_receive($) {
672 0     0 1 0 my ($self) = @_;
673             # return($self->throw_error("No servers defined for receiving")) unless $self->{'server'};
674 0         0 my (@r, $_t);
675 0         0 do {
676 0         0 $_t = $self->{'server'}->getline;
677 0         0 $self->dprint(0x01, "<< $_t");
678 0         0 push (@r, $_t);
679             } until ($_t =~ /^$self->{tag}/);
680 0 0       0 if ($#r < 1) {
681 0         0 return ($r[0]);
682             } else {
683 0         0 return(@r);
684             }
685             }
686              
687             =pod
688              
689             =item B
690              
691             Accept a line of response, regardless if a tag is provided. Misuse of this function can cause the module to be confused, especially if it received a tagged response and imap_receive() is subsequently used before another imap_send(). Applicable use of this function would be to read an (expected) untagged '+ go ahead' response. If a tagged response is (unexpectedly) received, such as in a NO or BAD response, an imap_send() must be used before the next imap_receive(). You have been warned.
692              
693             =cut
694              
695             sub imap_receive_tagless($) {
696 0     0 1 0 my ($self) = @_;
697 0         0 my $_t;
698 0         0 $_t = $self->{'server'}->getline;
699 0 0       0 $self->dprint(0x01, "<< $_t") if ($_t);
700 0         0 return ($_t);
701             }
702              
703             ########## Object functions ###########
704             =pod
705              
706             =back
707              
708             =head1 METHODS - INTERFACE
709              
710             These are the methods for manipulating the object or retrieving information from the object.
711              
712             =over 4
713              
714             =cut
715              
716             ##### Constructor
717             =pod
718              
719             =item B
720              
721             =item B
722              
723             Creates a new IMAP object. You can optionally specify the server to connect to, using default parameters (see connect() for details). On success, an IMAP object reference is returned - on failure, a string is returned detailing the error.
724              
725             =cut
726              
727             sub new ($){
728 8     8 1 178 my $proto = shift; #ignore first arg
729 8         24 my $self = {};
730 8         153 bless $self = {
731             'ID' => $ID++,
732             'name' => '', # the DNS/IP address to match
733             'server' => '', # Actual socket
734             'tag' => '*',
735             'error' => '',
736             'error_read' => 1,
737             'DEBUG' => 0,
738             'onfail' => 'error',
739             'errorstyle' => 'stack',
740             'capability' => '',
741             'capabilities' => {},
742             'capability_checking' => 1,
743             'user' => '',
744             'auth' => '',
745             }, $proto;
746            
747 8         95 $Instances{$self->{'ID'}} = \$self;
748             # If a server was supplied, try to connect to it
749 8 50       38 if (my $server = shift) {
750 0 0       0 if ($self->connect(PeerAddr => $server)) {
751 0         0 return($self);
752             } else {
753 0         0 my $err = $self->{error};
754 0         0 undef $self;
755 0         0 return($err);
756             }
757             } else {
758 8         33 return($self);
759             }
760             }
761             sub DESTROY { # Undocumented: Not to be directly called
762 0     0   0 my $self = shift;
763 0 0       0 if ($Instances{$self->{'ID'}}) {
764 0         0 $self->disconnect(); # FIXME: Probably not nessesary
765 0         0 delete $Instances{$self->{'ID'}};
766 0         0 return;
767             }
768            
769             # If we reach here, we didn't find ourself, which is a seirous problem
770 0         0 warn "ERROR: Could not find self in Instances upon DESTROY - Something is seriously wrong!\n";
771 0         0 foreach my $key (keys %Instances) {
772 0 0       0 warn "PANIC DUMP: Instance name: ".(($Instances{$key}->{'name'}) ? $Instances{$key}->{'name'} : "(none)")."\n";
773             }
774 0         0 die "DUMP COMPLETE: Aborting...\n\n";
775             }
776              
777             ##### Object manipulation functions
778             =pod
779              
780             =item B
781              
782             Set the debug level. Debug levels are set on a bitmask, and all debug output is to STDERR. Valid bits are as follows:
783              
784             =over 4
785              
786             =item B<0x01 - Communications dump>
787              
788             =over 2
789              
790             This will output all IMAP communications, with >> showing the sent data, and << showing the received data.
791              
792             =back
793              
794             =item B<0x02 - Fetch-parsing dump>
795              
796             =over 2
797              
798             This dumps a *lot* of data about the parsing of a fetch statement.
799              
800             =back
801              
802             = item B<0x04 - Annotations dump>
803              
804             =over 2
805              
806             This will print debugging information about processing getannotations and setannotations\n";
807              
808             =back
809              
810             =back
811              
812             =cut
813              
814             sub debuglevel($$) {
815 0     0 1 0 my ($self,$level) = @_;
816 0 0       0 if ($level >= 0) {
817 0         0 $self->{DEBUG} = $level;
818             } else {
819 0         0 return($self->throw_error("Invalid value [$level] for debuglevel: valid values are 0 and above"));
820             }
821             }
822              
823             =pod
824              
825             =item B
826              
827             Tell the object what to do when a command fails, either in the object, or if a !OK response is received (i.e. NO or BAD). Valid values are 'ERROR' (return undef, set error()) or 'ABORT' (abort, printing error to stderr). Default is ERROR. Values are case insensitive.
828              
829             =cut
830              
831             sub onfail($$) {
832 0     0 1 0 my ($self,$action) = @_;
833 0         0 $action = lc($action);
834 0 0 0     0 if (($action eq 'error') || ($action eq 'abort')){
835 0         0 $self->{onfail} = $action;
836             } else {
837 0         0 return($self->throw_error("Invalid value [$action] for onfail: valid values are 'ERROR' or 'ABORT'"));
838             }
839             }
840              
841             =pod
842              
843             =item B
844              
845             Controls how errors are handled when onfail is 'ERROR'. Valid values are 'LAST', for only storing the last error, or 'STACK', where all errors are saved until the next call to error(). STACK is most useful for those programs that tend to call nested functions, and finding where a program is truly failed (so the last error doesn't erase the original error that caused the problem). Default is 'STACK'
846              
847             =cut
848              
849             sub errorstyle($$) {
850 0     0 1 0 my ($self,$action) = @_;
851 0         0 $action = lc($action);
852 0 0 0     0 if (($action eq 'last') || ($action eq 'stack')){
853 0         0 $self->{errorstyle} = $action;
854             } else {
855 0         0 return($self->throw_error("Invalid value [$action] for errorstyle: valid values are 'LAST' or 'STACK'"));
856             }
857             }
858              
859             =pod
860              
861             =item B
862              
863             Prints the last error encountered by the imap object. If you executed a command and received an undef in response, this is where the error description can be found.
864              
865             =cut
866            
867             sub error($) {
868 0     0 1 0 my ($self) = @_;
869 0         0 $self->{error_read} = 1;
870 0         0 return ($self->{error});
871             }
872              
873             =pod
874              
875             =item B
876              
877             Enable or disable capability checking for those commands that support it. If enabled, a supported command will first check to see that the appropriate atom, as specified in the command's respective RFC, appears in the capability string. If it does not, the command will not be sent to the server, but immediately fail. If disabled, all commands will assume any required atom exists, and the command will be sent to the server.
878              
879             Any valid 'true' value will enable the checking, while any 'false' value will disable it.
880              
881             =cut
882            
883             sub capability_checking($$) {
884 0     0 1 0 my ($self, $value) = @_;
885 0         0 $self->{capability_checking} = $value;
886             }
887              
888             ##### _imap_command
889             =pod
890              
891             =item B<_imap_command($command, $arguments, <$continuation>, ...)>
892              
893             Execute an IMAP command, wait for and return the response. The function accepts the command as the first argument, arguments to that command as the second argument, followed by continuation responses for if/when the server issues a '+' response, such as '+ go ahead'. If there are less continuations specified than the server requests, the command will fail in the normal manner. If there are more continuations specified than the server requests, a warning is printed, however the response is parsed as usual - if it was OK then the command will be considered successful.
894              
895             The function returns the server response on success, and undef on failure, setting error().
896              
897             =cut
898              
899             sub _imap_command ($$@) {
900 0     0   0 my ($self,$command, @argset) = @_;
901 0         0 my @fullresp;
902 0         0 my $i=0;
903 0 0       0 return($self->throw_error("No servers defined for [$command][".join('][',@argset)."]")) unless $self->{'server'};
904              
905 0 0       0 $self->imap_send(($argset[0]) ? "$command $argset[0]" : $command);
906            
907 0         0 my ($datasize,$datasizemax) = (0,0);
908 0         0 while (1) {
909 0         0 my $resp = $self->imap_receive_tagless();
910 0 0       0 if ($resp) {
911 0         0 push(@fullresp,$resp);
912              
913             # First, check to see if we are in the middle of a size-based continuation response
914 0 0       0 if ($datasizemax > 0) {
915 0         0 $datasize += length($resp) + 1;
916             # >= because we could get termination chars (like a ')n', for instance) in the last line.
917 0 0       0 if ($datasize >= $datasizemax) {
918 0         0 ($datasize,$datasizemax) = (0,0);
919             }
920 0         0 next; # next line
921             }
922              
923             # First, check if this is a multi-line response
924 0 0       0 if (untagged_response($resp)) {
925 0 0       0 if ($resp =~ /\{(\d+)\}\s*$/) {
926 0         0 $datasizemax = $1;
927             }
928 0         0 next;
929             }
930            
931 0 0       0 if (ok_response($resp)) {
    0          
    0          
    0          
932 0 0       0 if ($i != $#argset) {
933 0         0 print STDERR "WARNING: Only ", $i+1 ," arguments of ", $#argset+1 ," used before successful response in [$command] command\n";
934             }
935             # Check for 'server responses' as defined in RFC3501, Section 7.
936 0 0       0 if ($server_response_callback) {
937 0         0 my %actual_responses;
938 0         0 foreach my $line (@fullresp) {
939 0 0       0 if (my ($_v, $_t) = $line =~ /(\d+)\s*(\w+)/) { # num-title: # TITLE
940 0         0 foreach my $attr (@SERVER_RESPONSES) { # We only match pre-approved values (FIXME: Is this what we want?)
941 0 0       0 if (lc($_t) eq lc($attr)) {
942 0         0 $actual_responses{$attr} = $_v;
943             }
944             }
945             }
946             }
947 0         0 $server_response_callback->(\%actual_responses); # Run the callback with the values
948             }
949             # Return the results
950 0         0 return(@fullresp);
951             } elsif (continue_response($resp)) {
952 0 0       0 if ($i < $#argset) {
953 0         0 $self->imap_send_tagless($argset[++$i]);
954             } else {
955 0         0 return($self->throw_error("$command failed: Server wanted more continuations than the ".$#argset." provided"));
956             }
957             } elsif (failure_response($resp)) {
958 0         0 return($self->throw_error("$command failed: @fullresp"));
959             } elsif (untagged_response($resp)) {
960             # This is just to avoid having untagged responses flagged as 'unrecognised'
961             } else {
962             # unrecognized response - put in any times its ok for this to happen in the unless statement.
963 0 0 0     0 unless ((lc($command) eq 'fetch') || (lc($command) eq 'uid fetch')) {
964 0         0 return($self->throw_error("INTERNAL ERROR: _IMAP_COMMAND - Unrecognized response from $command: @fullresp"));
965             }
966             }
967             } else {
968             # We got nothing back.... ? we must have been disconnected
969 0         0 $self->disconnect();
970 0         0 return($self->throw_error("Disconnected\n"));
971             }
972             }
973            
974             # finish reading command, since we're out of arguments
975             # my @resp = $self->imap_receive();
976            
977             # (ok_response(@resp)) ?
978             # return(@resp) :
979             # return($self->throw_error("$command failed: @resp"));
980             }
981              
982              
983             ##### connect
984             =pod
985              
986             =item B
987              
988             Connect to the supplied IMAP server. Inerits all the options of IO::Socket::SSL (and thusly, IO::Socket::INET), and adds the following custom options:
989              
990             =over 4
991              
992             =item ConnectMethod
993              
994             =over 2
995              
996             Sets the priority of the login methods via a space seperated priority-ordered list. Valid methods are 'SSL', 'STARTTLS', and 'PLAIN'. The default is to try loggin in via SSL, then connecting to the standard IMAP port and negotiating STARTTLS. 'PLAIN' is entirly unencrypted, and is not a default option - it must be specified if desired.
997              
998             The 'STARTTLS' method uses the starttls() command to negotiate the insecure connection to a secure status - it is functionally equivlant to using the 'PLAIN' method and subsequently calling starttls() within the program.
999              
1000             =back
1001              
1002             =item IMAPSPort
1003              
1004             =over 2
1005              
1006             Set the IMAPS port to use when connecting via the SSL method (default 993).
1007              
1008             =back
1009              
1010             =item IMAPPort
1011              
1012             =over 2
1013              
1014             Set the IMAP port to use when connecting via the STARTTLS or PLAIN methods (default 143).
1015              
1016             =back
1017              
1018             =back
1019              
1020             The error logs are cleared during a connection attempt, since (re)connecting essentially is a new session, and any previous errors cannot have any relation the current operation. Also, the act of finding the proper method of connecting can generate internal errors that are of no concern (as long as it ultimately connects). Should the connection fail, the error() log will contain the appropriate errors generated while attempting to connect. Should the connection succeed, the error log will be clear.
1021              
1022             Returns 1 on success, and undef on failure, setting error().
1023              
1024             =cut
1025              
1026             sub connect($%) {
1027 0     0 1 0 my ($self, %args) = @_;
1028 0 0       0 $self->throw_error("No arguments supplied to connect") unless (%args);
1029 0 0       0 my @methods = ($args{ConnectMethod}) ? split(' ',$args{ConnectMethod}) : qw(SSL STARTTLS);
1030 0         0 my $connected;
1031             my $errorstr;
1032 0         0 my $server;
1033 0         0 foreach my $method (@methods) {
1034 0         0 my @resp;
1035 0 0       0 if ($method eq "SSL") {
    0          
    0          
1036 0   0     0 $args{PeerPort} = $args{IMAPSPort} || 993;
1037 0 0       0 unless ($server = new IO::Socket::SSL(%args)) {
1038 0         0 $errorstr .= "SSL Attempt: ". IO::Socket::SSL::errstr() ."\n";
1039 0         0 next;
1040             }
1041             } elsif ($method eq 'STARTTLS') {
1042 0   0     0 $args{PeerPort} = $args{IMAPPort} || 143;
1043 0 0       0 unless ($server = new IO::Socket::INET(%args)) {
1044 0         0 $errorstr .= "STARTTLS Attempt: Unable to connect: $@\n";
1045 0         0 next;
1046             }
1047             } elsif ($method eq 'PLAIN') {
1048 0   0     0 $args{PeerPort} = $args{IMAPPort} || 143;
1049 0 0       0 unless ($server = new IO::Socket::INET(%args)) {
1050 0         0 $errorstr .= "PLAIN Attempt: Unable to connect: $@\n";
1051 0         0 next;
1052             }
1053             }
1054             # Execute a command to verify we're connected - some servers will accept a connection
1055             # but immediately dump the connection if something isn't supported (i.e. Exchange and
1056             # connecting to Non-ssl when SSL is required by the server)
1057            
1058 0         0 $self->{'server'} = $server;
1059 0         0 @resp = $self->imap_receive_tagless(); # collect welcome
1060 0 0 0     0 if ($resp[0] && untagged_ok_response(@resp) && ok_response($self->noop())) {
      0        
1061             # Post-processing
1062 0 0       0 if ($method eq 'STARTTLS') {
1063 0 0       0 if ($self->starttls(%args)) {
1064 0         0 $connected = 'ok';
1065 0         0 last;
1066             } else {
1067 0         0 $errorstr .= "STARTTLS Attempt: ".$self->error()."\n";
1068 0         0 next;
1069             }
1070             } else {
1071 0         0 $connected = 'ok';
1072             }
1073             } else {
1074 0         0 $errorstr .= "$method attempt: Connection dropped upon connect\n";
1075             }
1076             }
1077            
1078 0 0       0 if (!$connected) {
1079 0         0 chop($errorstr); # clip the tailing newline: we print errors without them
1080 0         0 return ($self->throw_error($errorstr));
1081             } else {
1082 0         0 $self->{'server'} = $server;
1083 0         0 $self->error; # clear error logs
1084             }
1085            
1086 0         0 return(1);
1087             }
1088              
1089             =pod
1090              
1091             =item B
1092              
1093             Disconnect from the server. This command can safely be used on an already-disconnected server.
1094              
1095             =cut
1096              
1097             sub disconnect($) {
1098 0     0 1 0 my ($self) = @_;
1099              
1100 0 0       0 if ($self->{'server'}) { # If we're still connected to something...
1101 0         0 $self->{'server'}->close(); # close connection
1102 0         0 undef $self->{'server'}; # remove server
1103 0         0 undef $self->{'name'}; # clear name
1104 0         0 undef $self->{'auth'}; # clear authentication info
1105 0         0 $self->{'tag'} = '*'; # Reset tag to * (for welcome message)
1106             }
1107             }
1108              
1109             =pod
1110              
1111             =item B
1112              
1113             This function sets up a callback function for when the IMAP server sends back a Server Response in accordance with RFC3501 Section 7.3, which stiuplates that while select()ed or examine()ing a mailbox, updates as to the mailbox content can be sent back after any command as tagless responses.
1114              
1115             The subfunction will be passed one hash argument. The hash argument will contain keys that represent the data type (EXISTS, RECENT), and their respective values will be the values returned by the server.
1116              
1117             If no function is registered via this method, or this method is called with an 'undef' argument, no special action will be taken should these server responses be encountered by the library.
1118              
1119             =cut
1120              
1121             sub register_mailbox_update($$) {
1122 0     0 1 0 my ($self, $callback_func) = @_;
1123              
1124             # Test to make sure we have a sub reference (or undef to clear the callback)
1125 0 0 0     0 return($self->throw_error("Argument to register_mailbox_update is not a CODE reference\n"))
1126             unless ((ref($callback_func) eq 'CODE') || (!defined $callback_func));
1127            
1128 0         0 $server_response_callback = $callback_func;
1129             }
1130              
1131             =pod
1132              
1133             =back
1134              
1135             =head1 METHODS - COMMANDS
1136              
1137             These are the standard IMAP commands from the IMAP::Client object. Methods return various structures, simple and complex, depending on the method. All methods return undef on failure, setting error(), barring an override via the onfail() function.
1138              
1139             =over 4
1140              
1141             =cut
1142              
1143             ###########################################################
1144             ########## rfc3501 (IMAP VERSION 4rev1) commands ##########
1145             ###########################################################
1146             # any state
1147              
1148             =pod
1149              
1150             =item B
1151              
1152             Request a listing of capabilities that the server supports. Note that the object caches the response of the capability() command for determining support of certain features.
1153              
1154             =cut
1155              
1156             sub capability() {
1157 0     0 1 0 my ($self) = @_;
1158            
1159 0 0       0 if ($self->{capability}) {
1160 0         0 return($self->{capability});
1161             }
1162 0         0 my @resp = $self->_imap_command("CAPABILITY", undef);
1163              
1164             # Cache the results if ok:
1165 0 0       0 if ($resp[0]) {
1166 0         0 $self->{capability} = @resp;
1167 0         0 my %abilities;
1168 0         0 foreach my $line (@resp) { # find the untagged capability line
1169 0 0       0 if (my ($capability) = $line =~ /^\*\s+CAPABILITY (.*)$/) {
1170 0         0 foreach my $caps (split(/ /,$capability)) {
1171 0         0 $abilities{$caps} = 1;
1172             }
1173 0         0 last;
1174             }
1175             }
1176 0         0 $self->{capabilities} = \%abilities;
1177             }
1178 0         0 return(@resp);
1179             }
1180              
1181             =pod
1182              
1183             =item B
1184              
1185             Issue a "No Operation" command - i.e. do nothing. Also used for idling and checking for state changes in the select()ed mailbox
1186              
1187             =cut
1188              
1189             sub noop() {
1190 0     0 1 0 my ($self) = @_;
1191 0         0 return($self->_imap_command("NOOP", undef));
1192             }
1193              
1194             =pod
1195              
1196             =item B
1197              
1198             Log the current user out and return This function will not work for multi-stage commands, such as those that issue a '+ go ahead' to indicate the continuation to send data.the connection to the unauthorized state.
1199              
1200             =cut
1201              
1202             sub logout() {
1203 0     0 1 0 my ($self) = @_;
1204 0         0 $self->{user} = '';
1205 0         0 $self->{auth} = '';
1206             # FIXME: untagged response BYE required by rfc - check for it?
1207 0         0 return($self->_imap_command("LOGOUT", undef));
1208             }
1209              
1210             # not authenticated state
1211              
1212             =pod
1213              
1214             =item B
1215              
1216             Issue a STARTTLS negotiation to secure the data connection. This function will call capability() twice - once before issuing the starttls() command to verify that the atom STARTTLS is listed as a capability(), and once after the sucessful negotiation, per RFC 3501 6.2.1. See capability() for unique rules on how this module handles capability() requests. Upon successful completion, the connection will be secured. Note that STARTTLS is not available if the connection is already secure (preivous sucessful starttls(), or connect() via SSL, for example).
1217              
1218             STARTTLS is checked in capability() regardless of the value of capability_checking().
1219              
1220             Any call arguments in %args are passed onto the underlying IO::Socket::SSL->start_SSL() function.
1221              
1222             This function returns 1 on success, since there is no output to return on success. Failures are treated normally.
1223              
1224             =cut
1225              
1226             sub starttls ($%){
1227 0     0 1 0 my ($self, %args) = @_;
1228              
1229 0 0       0 unless ($self->check_capability('STARTTLS')) {
1230 0         0 return($self->throw_error("STARTTLS not found in CAPABILITY"));
1231             }
1232 0         0 my @recv = $self->_imap_command("STARTTLS",undef);
1233 0         0 $self->dprint(0x01, "\n"); # compensation for lack of tapping into dump
1234 0   0     0 $args{SSL_version} ||= 'TLSv1';
1235 0 0       0 if (IO::Socket::SSL->start_SSL($self->{'server'}, %args)) {
1236             # per RFC 3501 - 6.2.1, we must re-establish the CAPABILITY of the server after STARTTLS
1237 0         0 $self->{capability} = '';
1238 0         0 @recv = $self->capability();
1239             } else {
1240 0         0 return($self->throw_error("STARTTLS Attempt: ".IO::Socket::SSL::errstr()))
1241             }
1242 0         0 return(@recv);
1243             }
1244              
1245             =pod
1246              
1247             =item B
1248              
1249             =item B
1250              
1251             =item B
1252              
1253             =item B
1254              
1255             Login in using the AUTHENTICATE mechanism. This mechanism supports authorization as someone other than the logged in user, if said user has permission to do so.
1256             authenticate() uses a one-line login sequence, while authenticate2() uses a multi-line login sequence. Both are provided for compatiblity reasons.
1257              
1258             OBSOLETE WARNING: In the future, this split-line behavior will be controlled by an object function, and authenticate() will be the only function.
1259              
1260             =cut
1261              
1262             sub authenticate($$$) { # One-line version of authentication
1263 0     0 1 0 my ($self,$login,$passwd,$autheduser) = @_;
1264 0         0 $self->error; # clear error logs
1265 0         0 $self->{user} = $login;
1266 0   0     0 $self->{auth} = $autheduser || $login;
1267 0 0       0 $autheduser='' unless (defined $autheduser);
1268 0         0 my $encoded = encode_base64("$autheduser\000$login\000$passwd");
1269 0         0 return($self->_imap_command("AUTHENTICATE","PLAIN $encoded"));
1270             }
1271             sub authenticate2($$$) { # Multi-line version of authentication
1272 0     0 1 0 my ($self,$login,$passwd,$autheduser) = @_;
1273 0         0 $self->error; # clear error logs
1274 0         0 $self->{user} = $login;
1275 0   0     0 $self->{auth} = $autheduser || $login;
1276 0 0       0 $autheduser='' unless (defined $autheduser);
1277 0         0 my $encoded = encode_base64("$autheduser\000$login\000$passwd");
1278 0         0 return($self->_imap_command("AUTHENTICATE","PLAIN","$encoded"));
1279             }
1280              
1281             =pod
1282              
1283             =item B
1284              
1285             Login using the basic LOGIN mechanism. Passwords are sent in the clear, and there is no third-party authorization support.
1286              
1287             =cut
1288              
1289             sub login ($$$) {
1290 0     0 1 0 my ($self,$username,$password) = @_;
1291 0         0 $self->{user} = $self->{auth} = $username;
1292 0         0 return($self->_imap_command("LOGIN","$username $password"));
1293             }
1294              
1295             # authenticated state
1296             =pod
1297              
1298             =item B
1299              
1300             Open a mailbox in read-write mode so that messages in the mailbox can be accessed. This function returns a hash of the valid tagless responses. According to RFC-3501, these responses include:
1301              
1302             =over 4
1303              
1304             =item * FLAGS ()
1305              
1306             =item * EXISTS
1307              
1308             =item * RECENT
1309              
1310             =item * OK [UNSEEN ]
1311              
1312             =item * OK [PERMANENTFLAGS ]
1313              
1314             =item * OK [UIDNEXT ]
1315              
1316             =back
1317              
1318             If the server supports an earlier version of the protocol than IMAPv4, the only flags required are FLAGS, EXISTS, and RECENT.
1319              
1320             Finally, hash responses will have an 'OK' key that will contain the current permissional status, either 'READ-WRITE' or 'READ-ONLY', if returned by the server. Returns an empty (undefined) hash on error.
1321              
1322             IMPORTANT! You should always check to see if an ALERT was issued. ALERTs should be relayed to the user if they exist!
1323              
1324             =cut
1325              
1326             sub select($$) {
1327 0     0 1 0 my ($self,$mailbox) = @_;
1328             # Don't add quotes around mailbox if they already exist
1329 0         0 return(parse_select_examine($self->_imap_command("SELECT", quote_once($mailbox))));
1330             }
1331              
1332             =pod
1333              
1334             =item B
1335              
1336             Identical to select(), except the mailbox is opened READ-ONLY. Returns an empty (unefined) hash on error.
1337              
1338             =cut
1339              
1340             sub examine($$) {
1341 0     0 1 0 my ($self,$mailbox) = @_;
1342 0         0 return(parse_select_examine($self->_imap_command("EXAMINE", quote_once($mailbox))));
1343             }
1344              
1345             =pod
1346              
1347             =item B
1348              
1349             Create a mailbox with the given name. Also assigns the properties given immediately upon creation. Valid properties are:
1350              
1351             =over 4
1352              
1353             =item * quota - A hash, with the keys being the type (aka STORAGE), and the values being the actual quota for that type, with size-based quotas given in kb
1354              
1355             =item * permissions - initial permissions for the mailbox owner(s) (equivalent to setacl), for if the owner needs different permissions than the server's default
1356              
1357             =back
1358              
1359             For example:
1360             $imap->create("asdfasdf",{quota=>{'STORAGE',50000}, permissions => 'lrws'})
1361              
1362             =cut
1363              
1364             sub create($@) {
1365 0     0 1 0 my ($self,$mailbox,$properties,$server) = @_;
1366              
1367             # Create mailbox
1368 0 0       0 if ($server) {
1369 0 0       0 return undef unless ($self->_imap_command("CREATE", quote_once($mailbox)." $server")); #err already thrown
1370             } else {
1371 0 0       0 return undef unless ($self->_imap_command("CREATE", quote_once($mailbox))); #err already thrown
1372             }
1373            
1374             # set quota (if needed)
1375 0 0       0 if ($properties->{quota}) {
1376 0         0 foreach my $type (keys %{$properties->{quota}}) {
  0         0  
1377 0 0       0 unless ($properties->{quota}->{$type} =~ /^\d+$/) {
1378 0         0 $self->throw_error("Quota second argument not numerical in create");
1379 0         0 goto fail;
1380             }
1381            
1382 0 0       0 unless ($self->setquota($mailbox,$type,$properties->{quota}->{$type})) {
1383 0         0 goto fail;
1384             }
1385             }
1386             }
1387              
1388             # set current owner(s) permissions (if needed)
1389 0 0       0 if ($properties->{permissions}) {
1390 0         0 my %owners = $self->getacl($mailbox);
1391 0         0 foreach my $owner (keys %owners) {
1392 0 0       0 unless ($self->setacl($mailbox,$owner,$self->buildacl($properties->{permissions}))) {
1393 0         0 goto fail;
1394             }
1395             }
1396             }
1397              
1398 0         0 return(1);
1399              
1400 0         0 fail:
1401             $self->setacl($mailbox,$self->{auth},$self->buildacl('all')); # cover cases where need explicit delete permission (as admin, for example)
1402 0 0       0 unless ($self->delete($mailbox)) {
1403 0         0 return($self->throw_error("Failed applying properties, and couldn't delete mailbox in recovery ****CLEANUP REQUIRED***"));
1404             }
1405 0         0 return($self->throw_error("Mailbox [$mailbox] creation aborted"));
1406             }
1407              
1408             =pod
1409              
1410             =item B
1411              
1412             Delete an existing mailbox with the given name.
1413             B: RFC notes that sub-mailboxes are not automatically deleted via this command.
1414              
1415             =cut
1416              
1417             sub delete($$) {
1418 0     0 1 0 my ($self,$mailbox) = @_;
1419 0         0 return($self->_imap_command("DELETE", quote_once($mailbox)));
1420             }
1421              
1422             =pod
1423              
1424             =item B
1425              
1426             Rename an existing mailbox from oldmailbox to newmailbox.
1427              
1428             =cut
1429              
1430             sub rename($$$) {
1431 0     0 1 0 my ($self,$oldmailbox,$newmailbox) = @_;
1432 0         0 return($self->_imap_command("RENAME", quote_once($oldmailbox).' '.quote_once($newmailbox)));
1433             }
1434              
1435             =pod
1436              
1437             =item B
1438              
1439             Subscribe the authorized user to the given mailbox
1440              
1441             =cut
1442              
1443             sub subscribe($$) {
1444 0     0 1 0 my ($self,$mailbox) = @_;
1445 0         0 return($self->_imap_command("SUBSCRIBE", quote_once($mailbox)));
1446             }
1447              
1448             =pod
1449              
1450             =item B
1451              
1452             Unsubscribe the authorized user from the given mailbox
1453              
1454             =cut
1455              
1456             sub unsubscribe($$) {
1457 0     0 1 0 my ($self,$mailbox) = @_;
1458 0         0 return($self->_imap_command("UNSUBSCRIBE", quote_once($mailbox)));
1459             }
1460              
1461             =pod
1462              
1463             =item B
1464              
1465             List all the local mailboxes the authorized user can see for the given mailbox from the given reference. Returns a listref of hashrefs, with each list entry being one result. Keys in the hashes include FLAGS, REFERENCE, and MAILBOX, and their returned values, respectivly.
1466              
1467             =cut
1468              
1469             sub list($$$) {
1470 0 0   0 1 0 warn "DEPRECIATED: list-returning IMAP::Client::list(): Array return values are depreciated and will be removed in future revisions! Instead, accept scalar list-reference\n" if (wantarray);
1471 0         0 my ($self,$reference,$mailbox) = @_;
1472 0         0 my @result = $self->_imap_command("LIST", quote_once($reference).' '.quote_once($mailbox));
1473 0 0       0 return(undef) unless ($result[0]);
1474 0         0 my @parsed_result = parse_list_lsub(@result);
1475 0 0       0 return((wantarray) ? @parsed_result : \@parsed_result);
1476             }
1477              
1478             =pod
1479              
1480             =item B
1481              
1482             List all the local subscriptions for the authorized user for the given mailbox from the given reference. Returns a listref of hashrefs, which each list entry being one result. Keys in the hashes include FLAGS, REFERENCE, and MAILBOX, and their returned values, respectivly.
1483              
1484             =cut
1485              
1486             sub lsub($$$) {
1487 0 0   0 1 0 warn "DEPRECIATED: list-returning IMAP::Client::lsub(): Array return values are depreciated and will be removed in future revisions! Convert code to accept scalar list-reference\n" if (wantarray);
1488 0         0 my ($self,$reference,$mailbox) = @_;
1489 0         0 my @result = $self->_imap_command("LSUB", quote_once($reference).' '.quote_once($mailbox));
1490 0 0       0 return(undef) unless ($result[0]);
1491 0         0 my @parsed_result = parse_list_lsub(@result);
1492 0 0       0 return((wantarray) ? @parsed_result : \@parsed_result);
1493             }
1494              
1495             =pod
1496              
1497             =item B
1498              
1499             Get the provided status items on the currently select()ed or examine()d mailbox. Each argument is a different status information item to query.
1500             According to RFC, the following tags are valid for status() queries: B, B, B, B, B. Since there may be future RFC declarations or custom tags for various servers, this module does not restrict to the above tags, but rather lets the server handle them appropriately (which may be a NO or BAD response).
1501              
1502             Upon successful completion, the return value will be a hash of the queried items and their returned values.
1503              
1504             =cut
1505              
1506             sub status($$@) {
1507 0     0 1 0 my ($self,$mailbox,@statuslist) = @_;
1508 0         0 my %results;
1509              
1510 0 0       0 unless (@statuslist) {
1511 0         0 return($self->throw_error("No status options to check in STATUS command"));
1512             }
1513 0         0 my $statusitems = '(';
1514 0         0 foreach my $status (@statuslist) {
1515 0         0 $statusitems .= "$status ";
1516             }
1517 0         0 chop($statusitems); # we don't want that trailing space
1518 0         0 $statusitems .= ')';
1519              
1520 0         0 my @resp = $self->_imap_command("STATUS", quote_once($mailbox)." $statusitems");
1521 0 0       0 return(undef) unless ($resp[0]);
1522            
1523             # find STATUS line and process results
1524 0         0 foreach my $line (@resp) {
1525 0 0       0 next unless ($line =~ s/^\*\s+STATUS\s+\S+\s+\((.*?)\)\r\n$/$1/);
1526 0         0 %results = split(/ /,$line); # thanks to the "key value key value" string
1527             }
1528            
1529 0         0 return(%results);
1530             }
1531              
1532              
1533             =pod
1534              
1535             =item B
1536              
1537             Append the given message to the given mailbox with the given flaglist. For information on the flaglist, see the buildflaglist() method.
1538              
1539             The append() method will do some housekeeping on any message that comes in - namely, it will ensure that all lines end in 'CRLF'. The reasoning is that the RFC strictly states that lines must end in 'CRLF', and most *nix files end with just 'LF' - therefore rather than force every program to muck with message inputs to ensure compatiblity, the append() method will ensure it for them.
1540              
1541             This 'CRLF' assurance is done for all commands - however its noted here because it also does it to the message itself in this method, potentially modifying data.
1542              
1543             Unless overridden, append will check for the LITERAL+ capability() atom, and use non-synchronizing literals if supported - otherwise, it will use the standard IMAP dialog.
1544              
1545             Upon successful execution, the return of this function depends on the type of variable receiving the data.
1546              
1547             =cut
1548              
1549             sub append ($$$) {
1550 0     0 1 0 my ($self,$mailbox,$message,$flaglist) = @_;
1551 0         0 my $flagstring = "";
1552              
1553             #use IO::File;
1554             #my $testfile = new IO::File "/tmp/test.txt", ">" or warn "Unable to open /tmp/test.txt\n";
1555             #print $testfile $message;
1556              
1557             # ensure newlines end in CRLF
1558 0         0 $message =~ s/((?
1559              
1560             #print $testfile $message;
1561             #$testfile->close();
1562              
1563 0         0 my $messagelen = length($message); # use the length of the *clean* message
1564              
1565 0 0       0 $flaglist = "()" unless $flaglist;
1566              
1567 0 0       0 my @result = ($self->check_capability('LITERAL+')) ? #non-synchronizing literals support
1568             $self->_imap_command("APPEND",quote_once($mailbox)." $flaglist {$messagelen+}\r\n$message") :
1569             $self->_imap_command("APPEND",quote_once($mailbox)." $flaglist {$messagelen}",$message);
1570            
1571 0 0       0 if ($self->check_capability('UIDPLUS')) {
1572            
1573             }
1574             }
1575              
1576             # selected state
1577              
1578             =pod
1579              
1580             =item B
1581              
1582             Request a checkpoint of the currently select()ed mailbox. The specific actions and responses by the server are on an implementation-dependant basis.
1583              
1584             =cut
1585              
1586             sub check() {
1587 0     0 1 0 my ($self) = @_;
1588 0         0 return($self->_imap_command("CHECK", undef));
1589             }
1590              
1591             =pod
1592              
1593             =item B
1594              
1595             Close the currently select()ed mailbox, expunge()ing any messages marked as \Deleted first. Unlike expunge(), this command does not return any untagged responses, and closes the mailbox upon completion.
1596              
1597             =cut
1598              
1599             sub close() {
1600 0     0 1 0 my ($self) = @_;
1601 0         0 return($self->_imap_command("CLOSE", undef));
1602             }
1603              
1604             =pod
1605              
1606             =item B
1607              
1608             Expunge() any messages marked as \Deleted from the currently select()ed mailbox. Will return untagged responses indicating which messages have been expunge()d.
1609              
1610             =cut
1611              
1612             sub expunge() {
1613 0     0 1 0 my ($self) = @_;
1614 0         0 return($self->_imap_command("EXPUNGE", undef));
1615             }
1616              
1617             =pod
1618              
1619             =item B)>
1620              
1621             Search for messages in the currently select()ed or examine()d mailbox matching the searchstring critera, where searchstring is a valid IMAP search query.See the end of this document for valid search terminology. The charset argument is optional - undef defaults to ASCII.
1622              
1623             This function returns a listref of sequence IDs that match the query when in list context, and a space-seperated list of sequence IDs if in scalar context. The scalar context allows nested calling within functions that require sequences, such as `fetch(search('RECENT'),undef,'FLAGS')`
1624              
1625             =cut
1626              
1627             sub search($$) {
1628 0     0 1 0 my ($self,$searchstring,$charset) = @_;
1629 0 0       0 return(parse_search($self->_imap_command("SEARCH", (($charset) ? "CHARSET $charset $searchstring" : $searchstring))));
1630             }
1631              
1632              
1633             =pod
1634            
1635             =item B
1636            
1637             Fetch message data. The first argument is a sequence set to retrieve.
1638              
1639             The second argument, the body hash ref, is designed to easily create the body section of the query, and takes the following arguments:
1640              
1641             =over 4
1642              
1643             =item * body
1644              
1645             Specify the section of the body to fetch(). undef is allowed, meaning no arguments to the BODY option, which will request the full message (unless a header is specified - see below).
1646              
1647             =item * offset
1648              
1649             Specify where in the body to start retrieving data, in octets. Default is from the beginning (0). If the offset is beyond the end of the data, an empty string will be returned. Must be specified with the length option.
1650              
1651             =item * length
1652              
1653             Specify how much data to retrieve starting from the offset, in octets. If the acutal data is less than the length specified, the acutal data will be returned. There is no default value, and thus must be specified if offset is used.
1654              
1655             =item * header
1656              
1657             Takes either 'ALL', 'MATCH', or 'NOT'. 'ALL' will return all the headers, regardless of the contents of headerfields. 'MATCH' will only return those headers that match one of the terms in headerfields, while 'NOT' will return only those headers that *do not* match any of the terms in the headerfields.
1658              
1659             =item * headerfields
1660              
1661             Used when header is 'MATCH' or 'NOT', it specifies the headers to use for comparison. This argument is a string of space-seperated terms.
1662              
1663             =item * peek
1664              
1665             When set to 1, uses the BODY.PEEK command instead of BODY, which preserves the \Seen state of the message
1666              
1667             =back
1668              
1669             A single hash reference may be supplied for a single body command. If multiple body commands are required, they must be passed inside an array reference (i.e. [\%hash, \%hash]).
1670              
1671             If an empty hashref is supplied as a \%body argument, it is interpreted as a BODY[] request.
1672              
1673             The third argument, other, is a string of space-seperated stand-alone data items. Valid items via RFC3501 include:
1674              
1675             =over 4
1676              
1677             =item * BODY
1678              
1679             The non-extensible form of BODYSTRUCTURE (not to be confused with the first argument - this command fetches structure, not content)
1680              
1681             =item * BODYSTRUCTURE
1682              
1683             The MIME-IMB body structure of the message.
1684              
1685             =item * ENVELOPE
1686              
1687             The RFC-2822 envelope structure of the message.
1688              
1689             =item * FLAGS
1690              
1691             The flags set for the message
1692              
1693             =item * INTERNALDATE
1694              
1695             The internal date of the message
1696              
1697             =item * RFC822, RFC822.HEADER, RFC822.SIZE, RFC822.TEXT
1698              
1699             RFC822, RFC822.HEADER, and RFC822.TEXT are equivilant to the similarly-named BODY options from the first argument, except for the format they return the results in (in this case, RFC-822). Except RFC822.HEADER, which is the RFC822 equivilant of BODY.PEEK[HEADER], there is no '.PEEK' alternative available, so the \Seen state may be altered. SIZE returns the RFC-822 size of the message and does not change the \Seen state.
1700              
1701             =item * UID
1702              
1703             The unique identifier for the message.
1704              
1705             =item * ALL
1706              
1707             equivalent to FLAGS, INTERNALDATE, RFC822(SIZE), ENVELOPE
1708              
1709             =item * FAST
1710              
1711             equivalent to FLAGS, INTERNALDATE RFC822(SIZE)
1712              
1713             =item * FULL
1714              
1715             equivalent to FLAGS, INTERNALDATE, RFC822(SIZE), ENVELOPE, BODY()
1716              
1717             =back
1718              
1719             The final argument, other, provides some basic option-sanity checking and assures that the options supplied are in the proper format.
1720              
1721             The return value is a hash of nested hashes. The first level of hashes represents the message id. The second and subsequent levels represents a level of multiparts, equivilant to the depth computed by the server and used for the body[] section retrievals. Particularly subject to nested hashing are the BODY and BODYSTRUCTURE commands. Commands used in the other argument typically are found on the base level of the hash: for example, UID and FLAGS would be found on the first level. Structure and BODY parts are found nested in their appropriate sections.
1722              
1723             This is a complex method for a data-rich command. Here are some examples to aid in your understanding:
1724              
1725             This command is equivilant to `xxx FETCH 1 (BODY[1] BODY.PEEK[HEADER.FIELDS (FROM SUBJECT TO DATE X-STATUS) RFC822.SIZE FLAGS]`:
1726             $imap->fetch(1,[{header=>'MATCH', headerfields=>'FROM SUBJECT TO DATE X-STATUS ', peek=>1},
1727             {body=>1, offset=>1024, length=>4000}],
1728             qw(RFC822.SIZE FLAGS));
1729              
1730              
1731             Please see the "Fetch Response Tutorial" at the bottom of this document.
1732            
1733             =cut
1734              
1735             sub fetch ($$@) {
1736 0     0 1 0 my ($self,$sequence,$bodies,@other) = @_;
1737              
1738 0 0       0 return($self->throw_error("Invalid sequence string: $sequence")) unless (is_sequence_set($sequence));
1739              
1740 0         0 my $fetchstring = $self->buildfetch($bodies,join(' ',@other));
1741 0 0       0 return($self->throw_error("Invalid fetch string: ")) unless ($fetchstring);
1742            
1743 0         0 return($self->parse_fetch($self->_imap_command("FETCH","$sequence $fetchstring")));
1744             }
1745              
1746              
1747             =pod
1748              
1749             =item B
1750              
1751             Set flags on a sequence set. For information on the flaglist, see the buildflaglist() method. See DEFINITIONS above for "sequence set".
1752              
1753             Operation is one of the following actions to take on the flags:
1754              
1755             =over 4
1756              
1757             =item * FLAGS - Replace the currently set flags of the message(s) with the flaglist.
1758              
1759             =item * +FLAGS - Add the flaglist flags to the currently set flags of the message(s).
1760              
1761             =item * -FLAGS - Remove the flaglist flags from the currently set flags of the message(s).
1762              
1763             =back
1764              
1765             Under normal circumstances, the command returns the new value of the flags as if a fetch() of those flags was done. You can append a .SILENT operation to any of the above commands to negate this behavior, and not have it return the new flag values.
1766              
1767             =over 4
1768              
1769             =item * FLAGS.SILENT - Equivalent to FLAGS, but without returning a new value
1770              
1771             =item * +FLAGS.SILENT - Equivalent to +FLAGS, but without returning a new value
1772              
1773             =item * -FLAGS.SILENT - Equivalent to -FLAGS, but without returning a new value
1774              
1775             NOTE ON SILENT OPTION: The server SHOULD send an untagged fetch() response if a change to a message's flags from an external source is observed. The intent is that the status of the flags is determinate without a race condition. In other words, .SILENT may still return important (unexpected) flag change information!
1776              
1777             =back
1778              
1779             =cut
1780              
1781             sub store ($$$$){
1782 0     0 1 0 my ($self,$sequence,$operation,$flaglist) = @_;
1783 0 0       0 return($self->throw_error("Invalid sequence string: $sequence")) unless (is_sequence_set($sequence));
1784 0         0 return($self->_imap_command("STORE", "$sequence $operation $flaglist"));
1785             }
1786              
1787             =pod
1788              
1789             =item B
1790              
1791             Copy a sequence set of messages to a mailbox. See DEFINITIONS above for "sequence set"
1792              
1793             =cut
1794              
1795             sub copy($$$) {
1796 0     0 1 0 my ($self,$sequence,$mailbox) = @_;
1797 0 0       0 return($self->throw_error("Invalid sequence string: $sequence")) unless (is_sequence_set($sequence));
1798 0         0 return($self->_imap_command("COPY", "$sequence $mailbox"));
1799             }
1800              
1801             # valid UID commands (RFC-3501): COPY FETCH STORE SEARCH
1802             # valid UID commands (RFC-2359): EXPUNGE
1803              
1804             =pod
1805              
1806             =item B
1807              
1808             Identical to the copy() command, except set is a UID set rather than a sequence set.
1809              
1810             =cut
1811              
1812             sub uidcopy($$$) {
1813 0     0 1 0 my ($self,$sequence,$mailbox) = @_;
1814 0 0       0 return($self->throw_error("Invalid sequence string: $sequence")) unless (is_sequence_set($sequence));
1815 0         0 return($self->_imap_command("UID COPY","$sequence $mailbox"));
1816             }
1817              
1818             =pod
1819              
1820             =item B
1821              
1822             Identical to the fetch() command, except set is a UID set rather than a sequence set.
1823              
1824             =cut
1825              
1826             sub uidfetch ($$@) {
1827 0     0 1 0 my ($self,$sequence,$bodies,@other) = @_;
1828 0 0       0 return($self->throw_error("Invalid sequence string: $sequence")) unless (is_sequence_set($sequence));
1829              
1830 0         0 my $fetchstring = $self->buildfetch($bodies,join(' ',@other));
1831 0 0       0 return($self->throw_error("Invalid fetch string: ")) unless ($fetchstring);
1832              
1833 0         0 push(@other,'UID');
1834 0         0 return($self->parse_fetch($self->_imap_command("UID FETCH","$sequence $fetchstring")));
1835             }
1836              
1837             =pod
1838              
1839             =item B
1840              
1841             Identical to the store() command, except set is a UID set rather than a sequence set.
1842              
1843             =cut
1844              
1845             #FIXME: does this need to be fixed (like store())?
1846             sub uidstore($$$$) {
1847 0     0 1 0 my ($self,$sequence,$operation,$flaglist) = @_;
1848 0 0       0 return($self->throw_error("Invalid sequence string: $sequence")) unless (is_sequence_set($sequence));
1849 0         0 return($self->_imap_command("UID STORE","$sequence $operation $flaglist"));
1850             }
1851              
1852             =pod
1853              
1854             =item B
1855              
1856             Identical to the expunge() command, except you can specify a set of messages to be expunged, rather than the entire mailbox, via a UID set. This function ensures the existance of the UIDPLUS atom in the capability() command.
1857              
1858             Note: At this time, the function does not implement the reccomendation in RFC2359, which suggestests that clients use alternate methods in selectivly expunging messages on servers that do not support UIDPLUS.
1859              
1860             =cut
1861              
1862             #FIXME This needs to implement the UIDEXPUNGE suggested alternate methods.
1863             sub uidexpunge($$) {
1864 0     0 1 0 my ($self,$sequence) = @_;
1865 0 0       0 return($self->throw_error("Invalid sequence string: $sequence")) unless (is_sequence_set($sequence));
1866 0 0       0 return($self->throw_error("UIDPLUS not supported for UID EXPUNGE command")) unless ($self->check_capability('UIDPLUS'));
1867 0         0 return($self->_imap_command("UID EXPUNGE","$sequence"));
1868             }
1869              
1870             =pod
1871              
1872             =item B
1873              
1874             Identical to the search() command, except the results are returned with UIDs instead of sequence IDs. See the end of this document for valid search terminology.
1875              
1876             =cut
1877              
1878             sub uidsearch($$) {
1879 0     0 1 0 my ($self,$searchstring) = @_;
1880 0         0 return(parse_search($self->_imap_command("UID SEARCH","$searchstring")));
1881             }
1882              
1883             # experimental/expansion commands
1884             #sub X{}
1885              
1886             ########## rfc2086 (IMAP4 ACL extention) commands ##########
1887              
1888             =pod
1889              
1890             =item B
1891              
1892             Modify the access control lists to set the provided permissions for the user on the mailbox, overwriting any previous access controls for the user. See the end of this document for a complete list of possible permissions for use in the permissions list.
1893              
1894             =cut
1895              
1896             sub setacl ($$$@) {
1897 0     0 1 0 my ($self,$mailbox,$user,@permissions) = @_;
1898 0         0 my $aclstring = $self->buildacl(@permissions);
1899 0         0 return($self->_imap_command("SETACL", quote_once($mailbox)." $user $aclstring"));
1900             };
1901              
1902             =pod
1903              
1904             =item B
1905              
1906             Remove all permissions for user on the mailbox's access control list.
1907              
1908             =cut
1909              
1910             sub deleteacl ($$$) {
1911 0     0 1 0 my ($self, $mailbox, $user) = @_;
1912 0         0 return($self->_imap_command("DELETEACL",quote_once($mailbox)." $user"));
1913             }
1914              
1915             =pod
1916              
1917             =item B
1918              
1919             Get the access control list for the supplied mailbox. Returns a two-level hash, with the first level consisting of userIDs, and the second level consisting of a hash of the permissions for the parent userID, in both short and long form.
1920              
1921             =cut
1922              
1923             sub getacl ($$) {
1924 0     0 1 0 my ($self, $mailbox) = @_;
1925 0         0 my @resp = $self->_imap_command("GETACL", quote_once($mailbox));
1926 0 0       0 return(()) unless ($resp[0]);
1927              
1928 0         0 my %permissions;
1929 0         0 foreach my $line (@resp) {
1930 0 0       0 if (my ($set) = ($line =~ /^\* ACL \"?$mailbox\"? (.*)\r\n$/i)) { #"
1931 0         0 my %_hash = split(/ /,$set); # split out user/perms set
1932 0         0 foreach my $user (keys %_hash) {
1933 0         0 my %_perms = map {$_ => 1} split(//,$_hash{$user});
  0         0  
1934             #fill_permissions(\%_perms);
1935 0         0 $permissions{$user} = \%_perms;
1936             }
1937             }
1938             }
1939            
1940 0         0 return(%permissions);
1941             }
1942              
1943              
1944             =pod
1945              
1946             =item B (not an official RFC2086 command)
1947              
1948             Modify the access control lists to add the specified permissions for the user on the mailbox. See the end of this document for a complete list of possible permissions for use in the permissions list.
1949              
1950             =cut
1951              
1952             sub grant ($$$@) {
1953 0     0 1 0 my ($self,$mailbox,$user,@permissions) = @_;
1954 0         0 my %acls = $self->getacl($mailbox,$user);
1955              
1956 0         0 return($self->setacl(quote_once($mailbox),$user,(@permissions, keys %{$acls{$user}})));
  0         0  
1957             }
1958              
1959             =pod
1960              
1961             =item B (not an official RFC2086 command)
1962              
1963             Modify the access control lists to remove the specified permissions for the user on the mailbox. If the end result is no permissions for the user, the user will be deleted from the acl list. See the end of this document for a complete list of possible permissions for use in the permissions list.
1964              
1965             =cut
1966              
1967             sub revoke ($$$@) {
1968 0     0 1 0 my ($self,$mailbox,$user,@permissions) = @_;
1969 0         0 my %acls = $self->getacl(quote_once($mailbox));
1970              
1971             # REMOVE @permissions from %acls
1972 0         0 my %remove = map {$_ => 1} split(//,$self->buildacl(@permissions));
  0         0  
1973             #fill_permissions(\%remove);
1974 0         0 foreach my $perm (keys %remove) {
1975 0         0 delete $acls{$user}->{$perm};
1976             }
1977              
1978 0 0       0 if (scalar(keys %{$acls{$user}}) == 0) {
  0         0  
1979 0         0 return($self->deleteacl($mailbox,$user));
1980             }
1981            
1982 0         0 return($self->setacl($mailbox,$user,(keys %{$acls{$user}})));
  0         0  
1983             }
1984              
1985             =pod
1986              
1987             =item B
1988              
1989             Get the list of access controls that may be granted to the supplied user for the supplied mailbox. Returns a hash populated with both short and long rights definitions for testing for the existance of a permision, like $hash{'list'}.
1990              
1991             =cut
1992              
1993             sub listrights($$$) {
1994 0     0 1 0 my ($self, $mailbox, $user) = @_;
1995 0         0 my @resp = $self->_imap_command("LISTRIGHTS", quote_once($mailbox)." $user");
1996 0 0       0 return(()) unless ($resp[0]);
1997            
1998 0         0 my %permissions;
1999 0         0 foreach my $line (@resp) {
2000 0 0       0 if (my ($permissionstring) = ($line =~ /^\* LISTRIGHTS $mailbox $user (.*)\r\n$/i)) {
2001 0         0 %permissions = map{ $_ => 1 } split(/ /,$permissionstring);
  0         0  
2002             }
2003             }
2004             #fill_permissions(\%permissions);
2005              
2006 0         0 return(%permissions);
2007             }
2008              
2009             =pod
2010              
2011             =item B
2012              
2013             Get the access control list information for the currently authorized user's access to the supplied mailbox. Returns a hash of the permissions available, in both short and long form.
2014              
2015             =cut
2016              
2017             sub myrights($$) {
2018 0     0 1 0 my ($self, $mailbox) = @_;
2019 0         0 my @resp = $self->_imap_command("MYRIGHTS", quote_once($mailbox));
2020 0 0       0 return(()) unless ($resp[0]);
2021              
2022 0         0 my %permissions;
2023 0         0 foreach my $line (@resp) {
2024 0 0       0 if (my ($permissionstring) = ($line =~ /^\* MYRIGHTS \"?$mailbox\"? (.*)\r\n$/i)) { #"
2025 0         0 %permissions = map {$_ => 1} split(//,$permissionstring);
  0         0  
2026             }
2027             }
2028 0         0 fill_permissions(\%permissions);
2029              
2030 0         0 return(%permissions);
2031             }
2032              
2033              
2034             ########## rfc2087 (IMAP4 QUOTA extention) commands ##########
2035              
2036             =pod
2037              
2038             =item B
2039              
2040             Set the quota on the mailbox. Type is the type of quota to specify, for example STORAGE. Sized-based quota is supplied in KB.
2041              
2042             =cut
2043              
2044             sub setquota($$$$) {
2045 0     0 1 0 my ($self,$mailbox,$type,$quota) = @_;
2046 0         0 return($self->_imap_command("SETQUOTA", quote_once($mailbox)." ($type $quota)"));
2047             }
2048              
2049             =pod
2050              
2051             =item B
2052              
2053             Get the quota for the supplied mailbox. The provided mailbox must be a quota root, and the authorized user might need to be an administrator, otherwise a "NO" reponse will be returned. getquotaroot() is likely the more applicable command for finding the current quota information on a mailbox. Quota is returned in a hash of lists: The hash elements correspond to the quota type (for example, STORAGE). The list consists of all numbers that corresponded to the quote type.
2054              
2055             For example, the RFC specifies that the STORAGE type returns the quota used in the first element, and the maximum quota in the second. Quota units corresponding to sizes are in KB.
2056              
2057             =cut
2058              
2059             sub getquota($$) {
2060 0     0 1 0 my ($self,$mailbox) = @_;
2061              
2062 0 0       0 return($self->throw_error("QUOTA not supported for GETQUOTA command")) unless ($self->check_capability('QUOTA'));
2063              
2064 0         0 my @resp = $self->_imap_command("GETQUOTA", quote_once($mailbox));
2065 0 0       0 return(()) unless ($resp[0]);
2066              
2067 0         0 my %quota = parse_quota($mailbox,\@resp);
2068             }
2069              
2070             =pod
2071              
2072             =item B
2073              
2074             Fetch the list of quotaroots and the quota for the provided mailbox. This command is idential to the getquota() command, except the query doesn't have to be at the quota root, since this command will find the quota root for the specified mailbox, then return the results based on the results of the find. Thus, there will be an extra hash item, 'ROOT', that specified what was used as the quota root. Quota units corresponding to sizes are in KB.
2075              
2076             =cut
2077              
2078             sub getquotaroot($$) {
2079 0     0 1 0 my ($self,$mailbox) = @_;
2080              
2081 0 0       0 return($self->throw_error("QUOTA not supported for GETQUOTAROOT command")) unless ($self->check_capability('QUOTA'));
2082              
2083 0         0 my @resp = $self->_imap_command("GETQUOTAROOT", quote_once($mailbox));
2084 0 0       0 return(undef) unless ($resp[0]);
2085              
2086 0         0 my %quota = parse_quota($mailbox,\@resp);
2087             }
2088              
2089              
2090              
2091             ########## rfc2193 (IMAP4 Mailbox Referrals) commands ##########
2092              
2093             =pod
2094              
2095             =item B
2096              
2097             List all the mailboxes the authorized user can see for the given mailbox from the given reference. This command lists both local and remote mailboxes, and can also be an indicator to the server that the client (you) supports referrals. Not reccomended if referrals are not supported by the overlying program.
2098              
2099             Returns a listref of hasherefs, one per element, where each hashes keys include FLAGS, REFERENCE, and MAILBOX.
2100              
2101             IMPORTANT: Referrals come in a "NO" response, so this command will fail even if responded to with a referral. The referral MUST be pulled out of the error(), and can then be parsed by the parse_referral() command if desired, to extract the important pieces for the clients used.
2102              
2103             Unless overridden, rlist will check for the MAILBOX-REFERRALS capability() atom before executing the command. If the capability is not advertised, the function will fail without sending the request to the server.
2104              
2105             =cut
2106              
2107             sub rlist($$$) {
2108 0 0   0 1 0 warn "DEPRECIATED: list-returning IMAP::Client::rlist(): Array return values are depreciated and will be removed in future revisions! Convert code to accept scalar list-reference\n" if (wantarray);
2109 0         0 my ($self,$reference,$mailbox) = @_;
2110 0 0       0 return($self->throw_error("MAILBOX-REFERRALS not supported for RLIST command")) unless ($self->check_capability('MAILBOX-REFERRALS'));
2111 0         0 my @result = $self->_imap_command("RLIST", quote_once($reference).' '.quote_once($mailbox));
2112 0 0       0 return(undef) unless ($result[0]);
2113 0         0 my @parsed_result = parse_list_lsub(@result);
2114 0 0       0 return((wantarray) ? @parsed_result : \@parsed_result);
2115             }
2116              
2117             =pod
2118              
2119             =item B
2120              
2121             List all the subscriptions for the authorized user for the given mailbox from the given reference. This command lists both local and remote subscriptions, and can also be an indicator to the server that the client (you) supports referrals. Not reccomended if referrals are not supported by the overlying program.
2122              
2123             Returns a listref of hasherefs, one result per element, where each hashes keys include FLAGS, REFERENCE, and MAILBOX.
2124              
2125             IMPORTANT: Referrals come in a "NO" response, so this command will fail even if responded to with a referral. The referral MUST be pulled out of the error(), and can then be parsed by the parse_referral() command if desired, to extract the important pieces for the clients used.
2126              
2127             Unless overridden, rlsub will check for the MAILBOX-REFERRALS capability() atom before executing the command. If the capability is not advertised, the function will fail without sending the request to the server.
2128              
2129             =cut
2130              
2131             sub rlsub($$$) {
2132 0 0   0 1 0 warn "DEPRECIATED: list-returning IMAP::Client::rlsub(): Array return values are depreciated and will be removed in future revisions! Convert code to accept scalar list-reference\n" if (wantarray);
2133 0         0 my ($self,$reference,$mailbox) = @_;
2134 0 0       0 return($self->throw_error("MAILBOX-REFERRALS not supported for RLSUB command")) unless ($self->check_capability('MAILBOX-REFERRALS'));
2135 0         0 my @result = $self->_imap_command("RLSUB", quote_once($reference).' '.quote_once($mailbox));
2136 0 0       0 return(undef) unless ($result[0]);
2137 0         0 my @parsed_result = parse_list_lsub(@result);
2138 0 0       0 return((wantarray) ? @parsed_result : \@parsed_result);
2139             }
2140              
2141              
2142             ########## rfc2177 (IMAP4 IDLE) command ##########
2143              
2144             =pod
2145              
2146             =item B
2147              
2148             Issue IDLE command, currently unimplemented.
2149              
2150             =cut
2151              
2152             sub idle {
2153             # This function is a little different, since instead of accumulating the response and returning it,
2154             # we acutally want to return the untagged responses in realtime without returning. Impossilbe? Nah...
2155             # FIXME: what to do, what to do....
2156 0     0 1 0 my ($self) = @_;
2157 0         0 return($self->throw_error("IDLE unimplemented"));
2158             }
2159              
2160             ########## rfc2971 (IMAP4 ID extention) command ##########
2161              
2162             =pod
2163              
2164             =item B
2165              
2166             Provide identifying information to the server, and have the server do the same to you. The client can request the server's information without sharing its own by supplying an undef perams argument. The information by both parties is useful for statistical or debugging purposes, but otherwise serves no other functional purpose.
2167              
2168             The perams arguemnt is a hash, since information is in a key-value format. Keys can be anything, but must be less than 30 characters in length, and values must be less than 1024 characters in length. There are a set keys defined by the RFC that are reccomended: These include:
2169              
2170             =over 4
2171              
2172             =item * name - Name of the program
2173              
2174             =item * version - Version number of the program
2175              
2176             =item * os - Name of the operating system
2177              
2178             =item * os-version - Version of the operating system
2179              
2180             =item * vendor - Vendor of the client/server
2181              
2182             =item * support-url - URL to contact for support
2183              
2184             =item * address - Postal address of contact/vendor
2185              
2186             =item * date - Date program was released, specified as a date-time in IMAP4rev1
2187              
2188             =item * command - Command used to start the program
2189              
2190             =item * arguments - Arguments supplied on the command line, if any
2191              
2192             =item * environment - Description of environment, i.e., UNIX environment List all the subscriptions for the authorized user for the given mailbox from the given reference.variables or Windows registry settings
2193              
2194             =back
2195              
2196             None of the keys are required - if the client wishes not to supply information for a key, the key is simply omitted. Not all clients support this extention: Support can be identified by using the capability() command, and verifying the atom "ID" is included in the server-supplied list.
2197              
2198             =cut
2199              
2200             sub id($%) {
2201 0     0 1 0 my ($self,%perams) = @_;
2202 0         0 my $peramlist;
2203              
2204 0 0       0 return($self->throw_error("ID not supported for ID command")) unless ($self->check_capability('ID'));
2205              
2206 0 0       0 if (%perams) {
2207 0         0 $peramlist = '(';
2208 0         0 foreach my $key (keys %perams) {
2209 0 0       0 if (length($key) > 30) { # defined in RFC section 3.3
2210 0         0 return ($self->throw_error("Client key [$key] too long: ".length($key)." bytes, max 30 bytes"));
2211             }
2212 0 0       0 if (length($perams{$key}) > 1024) {# defined in RFC section 3.3
2213 0         0 return($self->throw_error("Client value [$perams{$key}] too long: ".length($perams{$key}).", max 1024 bytes"));
2214             }
2215 0         0 $peramlist .= quote_once($key).' '.quote_once($perams{$key}).' ';
2216             }
2217 0         0 chop $peramlist; # rid ourselves of the last space
2218 0         0 $peramlist .= ')'; #overwrite last space with )
2219             } else {
2220 0         0 $peramlist = 'NIL';
2221             }
2222            
2223 0         0 return($self->_imap_command("ID",$peramlist));
2224             }
2225              
2226             ########## draft-ietf-imapext-annotate-15 ##########
2227              
2228             =pod
2229              
2230             =item B
2231              
2232             Retrieve annotations on a mailbox from the server. If the mailbox argument is empty, it will retrieve global server annotations instead.
2233              
2234             The entry specifier indicates which type of annotation you will retrieve. the "*" wildcard is valid for retrieving all annotations, while the "%" wildcard will match all text except the hierarchy delimiter '/'.
2235              
2236             As of draft-ietf-imapext-annotate-15, valid global entries are:
2237              
2238             =over 4
2239              
2240             =item * /comment
2241              
2242             Defines a comment or note associated with the server
2243              
2244             =item * /motd
2245              
2246             Defines a "message of the day" for the server (Read-Only)
2247              
2248             =item * /admin
2249              
2250             Indicates a method for contacting the server administrator (Read-Only)
2251              
2252             =item * /vendor/
2253              
2254             Defines the top-level of entries associated with the server as created by a particular product of some vendor. Vendor tokens are registered with IANA, using the ACAP [RFC2244] vendor subtree registry.
2255              
2256             =back
2257              
2258             ... and the mailbox entries are ...
2259              
2260             =over 4
2261              
2262             =item * /comment
2263              
2264             Defines a per-mailbox comment, connected with the specified mailbox
2265              
2266             =item * /sort
2267              
2268             Defines the default sort criteria [I-D.ietf-imapext-sort] to use when first displaying the mailbox contents to the user, or NIL if sorting is not required.
2269              
2270             =item * /thread
2271              
2272             Defines the default thread criteria [I-D.ietf-imapext-sort] to use when first displaying the mailbox contents to the user, or NIL if threading is not required. This takes precidence over the /sort annotation.
2273              
2274             =item * /check
2275              
2276             A true/false value that indicates whether this mailbox should be checked at regular intervals by the client.
2277              
2278             =item * /checkperiod
2279              
2280             if /check is true, this numberic value indicates a period of minutes that the client should check the mailbox.
2281              
2282             =item * /vendor/
2283              
2284             Identical to the global version, except they apply only to the specified mailbox.
2285              
2286             =back
2287              
2288             The attribute specifier indicates which part of the annotation you wish to receive. As of draft-ietf-imapext-annotate-15, valid global attributes are
2289              
2290             =over 4
2291              
2292             =item * /value
2293              
2294             String or binary data representing the value of the annotation. NIL can be stored to delete an annotation. Text value sshould use the utf-8 character set. Binary data uses the "literal8" syntax element [I-D.melnikov-imap-ext-abnf] to store and retreive such data.
2295              
2296             =item * /size
2297              
2298             the size, in octets, of the value. (Read-only)
2299              
2300             =item * /content-language
2301              
2302             Language used for the value. This SHOULD be set if the value stored is textual.
2303              
2304             =back
2305              
2306             In addition, all attributes have a '.priv' and a '.shared' suffix, meaning private and shared, respecitvly. If neither attribute suffix is specified, both will be retrieved (if allowed).
2307              
2308             Returns a nested hash reference with the mailbox name as the first layer, the entry as the second layer, the attribute name in the third layer.
2309              
2310             Structure example:
2311              
2312             =over 2
2313              
2314             $r{}->{}->{} = value
2315              
2316             =back
2317              
2318             =cut
2319              
2320             sub parse_annotation ($$$) {
2321 1     1 0 12 my ($resp,$mailbox,$self) = @_; # self last so its not seen by the user as a 'call'
2322              
2323             # Parse the results
2324 1         3 my %results = ($mailbox => undef);
2325 1         27 foreach my $line (@{$resp}) {
  1         3  
2326 5         22 $line =~ s/[\r\n]//gs; # Remove newlines
2327 5         60 my ($more) = $line =~ /\s*\*\s+ANNOTATION\s+\"?$mailbox\"?\s+(.*)$/; #"
2328 5         13 while ($more) {
2329 4         16 $self->dprint(0x04, "getannotation processing with [$more]\n");
2330 4         196 my ($entry, $attrset, $less) = $more =~ /\"?([^\"\s]+)\"?\s+($parens)(.*)/; #"
2331 4         27 $self->dprint(0x04, "getannotation found entry [$entry], attrset [$attrset], and less [$less]\n");
2332 4         10 my $attrs_href = parse_parameters($attrset);
2333              
2334 4         12 $results{$mailbox}->{$entry} = $attrs_href;
2335 4         15 $more = $less;
2336             }
2337             }
2338 1         7 return(%results);
2339             }
2340             sub getannotation($$$$) {
2341 0     0 1   my ($self, $mailbox, $entry, $attribute) = @_;
2342 0 0 0       unless ($self->check_capability('ANNOTATEMORE') || $self->check_capability('ANNOTATEMORE2')) {
2343 0           $self->throw_error("ANNOTATEMORE or ANNOTATEMORE2 not supported for GETANNOTATE command");
2344             }
2345            
2346             # Execute
2347 0           my @resp = $self->_imap_command("GETANNOTATION", quote_once($mailbox).' '.quote_once($entry).' '.quote_once($attribute));
2348 0 0         return(()) unless ($resp[0]);
2349            
2350 0           my %parse_annotation = parse_annotation(\@resp,$mailbox,$self);
2351            
2352 0           return (%parse_annotation)
2353             }
2354              
2355             =pod
2356              
2357             =item B
2358              
2359             Set annotations on a mailbox from the server. If the mailbox argument is empty, it will attempt to set global server annotations instead.
2360              
2361             For details on annotations and its arguments, see the getannotation() command.
2362              
2363             The setannotation() command only accepts annotations for one mailbox at a time - as a result, the setannotation() command accepts a mailbox argument and an attribute tree, rather than the entire annotation hash that getannotation returns.
2364              
2365             As a result, setannotation takes the same type of hash that getannotation returns, except starting at the mailbox level. For example, the hash must be in the form of
2366              
2367             =over 2
2368              
2369             $r{}->{} = value
2370              
2371             =back
2372              
2373             The the first level is for which tags to set, and the second level is what attributes those tags will have, while the value is the actual value to assign.
2374              
2375             One key difference between the getannotation() and setannotation() hashes is that the setannotation() mailbox can contain a wildcard - for example, setting 'INBOX.%' as the mailbox will add an annotation for all mailboxes at the top-level of the INBOX hierarchy.
2376              
2377             =cut
2378              
2379             sub setannotation($$$) {
2380 0     0 1   my ($self, $mailbox, $tagset) = @_;
2381 0 0 0       unless ($self->check_capability('ANNOTATEMORE') || $self->check_capability('ANNOTATEMORE2')) {
2382 0           $self->throw_error("ANNOTATEMORE or ANNOTATEMORE2 not supported for GETANNOTATE command");
2383             }
2384            
2385 0           my $tagset_str;
2386            
2387 0           foreach my $tag (keys %{$tagset}) {
  0            
2388 0           $tagset_str .= quote_once($tag) . ' (';
2389 0           foreach my $attribute (keys %{$tagset->{$tag}}) {
  0            
2390 0           $tagset_str .= quote_once($attribute).' '.quote_once($tagset->{$tag}->{$attribute}).' ';
2391             }
2392 0           chop($tagset_str);
2393 0           $tagset_str .= ') ';
2394             }
2395 0           chop($tagset_str);
2396            
2397            
2398 0           return($self->_imap_command("SETANNOTATION", quote_once($mailbox).' '.$tagset_str));
2399             }
2400              
2401             =pod
2402              
2403             =back
2404              
2405             =head1 METHODS - SUPPORT
2406              
2407             These are the support methods created to support the coder in creating some of the more complex and open-ended arguments for the above command methods.
2408              
2409             =over 4
2410              
2411             =cut
2412              
2413             ########## SUPPORT FUNCTIONS ############
2414              
2415             =pod
2416              
2417             =item B
2418              
2419             This function is provided for the ease of creating an appropriate aclstring. The set of arguments is the set of permissions to include in the aclstring. The following are the supported rights:
2420              
2421             =over 4
2422              
2423             =item * lookup, list, l - mailbox is visible to list()/lsub()/rlist()/rlsub() commands
2424              
2425             =item * read, r - select() the mailbox, perform check(), fetch(), PARTIAL?, search(), copy() from mailbox
2426              
2427             =item * seen, s - keep seen/unseen information across sessions (store() SEEN flag)
2428              
2429             =item * write, w - store() flags other than SEEN and DELETE
2430              
2431             =item * insert, i - perform append(), copy() into mailbox
2432              
2433             =item * post, p - send mail to submission address for mailbox
2434              
2435             =item * create, c - create() new sub-mailboxes
2436              
2437             =item * delete, d - store() DELETED flag, perform expunge()s
2438              
2439             =item * administer, admin, a - perform setacl() commands
2440              
2441             =item * all - all the above rights. Overrides all other commands
2442              
2443             =item * none - none of the above rights. This is the same as providing no arguments. Is overriden by any other supplied commands
2444              
2445             =item * 0-9 - implementation or site defined rights (nonstandard)
2446              
2447             =back
2448              
2449             =cut
2450              
2451             sub buildacl ($@) {
2452 0     0 1   my $aclstr='';
2453 0           my ($self, @acls) = @_;
2454 0           my %acllist;
2455 0           foreach my $aclset (@acls) {
2456 0           $aclset = lc($aclset);
2457 0           foreach my $acl (split(/ /,$aclset)) {
2458 0 0 0       if ($acl eq 'all') { # start with valid words
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0          
    0          
2459 0           push(@acls, qw(l r s w i p c d a 0 1 2 3 4 5 6 7 8 9));
2460             } elsif ($acl eq 'none') {
2461             # we silently accept 'none', which is the same as no options
2462             } elsif ($acl =~ /^[lrswipcda0123456789]{2,}$/){ # if it looks like a valid permissions string (2 or more),split and use
2463 0           push(@acls,split(//,$acl));
2464             } elsif (($acl eq 'l') || ($acl eq 'lookup') || ($acl eq 'list')) { # move on to individual permissions
2465 0           $acllist{'l'} = 1;
2466             } elsif (($acl eq 'r') || ($acl eq 'read')) {
2467 0           $acllist{'r'} = 1;
2468             } elsif (($acl eq 's') || ($acl eq 'seen')) {
2469 0           $acllist{'s'} = 1;
2470             } elsif (($acl eq 'w') || ($acl eq 'write')) {
2471 0           $acllist{'w'} = 1;
2472             } elsif (($acl eq 'i') || ($acl eq 'insert')) {
2473 0           $acllist{'i'} = 1;
2474             } elsif (($acl eq 'p') || ($acl eq 'post')) {
2475 0           $acllist{'p'} = 1;
2476             } elsif (($acl eq 'c') || ($acl eq 'create')) {
2477 0           $acllist{'c'} = 1;
2478             } elsif (($acl eq 'd') || ($acl eq 'delete')) {
2479 0           $acllist{'d'} = 1;
2480             } elsif (($acl eq 'a') || ($acl eq 'admin') || ($acl eq 'administer')) {
2481 0           $acllist{'a'} = 1;
2482             } elsif ($acl =~ /^\d$/) {
2483 0           $acllist{"$acl"} = 1;
2484             } else {
2485 0           return($self->throw_error("Invalid setacl option [$acl]"));
2486             }
2487             }
2488             }
2489            
2490             # compile into final string and return
2491 0           foreach my $key (keys %acllist) {
2492 0           $aclstr .= $key;
2493             }
2494 0           return($aclstr);
2495             }
2496              
2497             =pod
2498              
2499             =item B
2500              
2501             Builds a fetch query to get only the data you want. The first argument, the body hash ref, is designed to easily create the body section of the query, and takes the following arguments:
2502              
2503             =over 4
2504              
2505             =item * body
2506              
2507             Specify in a section of the body to fetch(). undef is allowed, meaning no arguments to the BODY option, which will request the full message (unless a header is specified - see below).
2508              
2509             =item * offset
2510              
2511             Specify where in the body to start retrieving data, in octets. Default is from the beginning (0). If the offset is beyond the end of the data, an empty string will be returned. Must be specified with the length option.
2512              
2513             =item * length
2514              
2515             Specify how much data to retrieve starting from the offset, in octets. If the acutal data is less than the length specified, the acutal data will be returned. There is no default value, and thus must be specified if offset is used.
2516              
2517             =item * header
2518              
2519             Takes either 'ALL', 'MATCH', or 'NOT'. 'ALL' will return all the headers, regardless of the contents of headerfields. 'MATCH' will only return those headers that match one of the terms in headerfields, while 'NOT' will return only those headers that *do not* match any of the terms in the headerfields.
2520              
2521             =item * headerfields
2522              
2523             Used when header is 'MATCH' or 'NOT', it specifies the headers to use for comparison. This argument is a string of space-seperated terms.
2524              
2525             =item * peek
2526              
2527             When set to 1, uses the BODY.PEEK command instead of BODY, which preserves the \Seen state of the message
2528              
2529             =back
2530              
2531             A single hash reference may be supplied for a single body command. If multiple body commands are required, they must be passed inside an array reference (i.e. [\%hash, \%hash]).
2532              
2533             If an empty hashref is supplied as a \%body argument, it is interpreted as a BODY[] request.
2534              
2535             The final argument, other, is a string of space-seperated stand-alone data items. Valid items via RFC3501 include:
2536              
2537             =over 4
2538              
2539             =item * BODY
2540              
2541             The non-extensible form of BODYSTRUCTURE (not to be confused with the first argument - this command fetches structure, not content)
2542              
2543             =item * BODYSTRUCTURE
2544              
2545             The MIME-IMB body structure of the message.
2546              
2547             =item * ENVELOPE
2548              
2549             The RFC-2822 envelope structure of the message.
2550              
2551             =item * FLAGS
2552              
2553             The flags set for the message
2554              
2555             =item * INTERNALDATE
2556              
2557             The internal date of the message
2558              
2559             =item * RFC822, RFC822.HEADER, RFC822.SIZE, RFC822.TEXT
2560              
2561             RFC822, RFC822.HEADER, and RFC822.TEXT are equivilant to the similarly-named BODY options from the first argument, except for the format they return the results in (in this case, RFC-822). There is no '.PEEK' available, so the \Seen state may be altered. SIZE returns the RFC-822 size of the message and does not change the \Seen state.
2562              
2563             =item * UID
2564              
2565             The unique identifier for the message.
2566              
2567             =item * ALL
2568              
2569             equivalent to FLAGS, INTERNALDATE, RFC822.SIZE, ENVELOPE
2570              
2571             =item * FAST
2572              
2573             equivalent to FLAGS, INTERNALDATE RFC822.SIZE
2574              
2575             =item * FULL
2576              
2577             equivalent to FLAGS, INTERNALDATE, RFC822.SIZE, ENVELOPE, BODY
2578              
2579             =back
2580              
2581             The final argument, other, provides some basic option-sanity checking and assures that the options supplied are in the proper format. For example, if a program has a list of options to use, a simple buildfetch(undef,join(' ',@args)) would manipulate the terms into a format suitable for a fetch() command. It is highly recommended to pass options through this function rather than appending a pre-formatted string to the functions output to ensure proper formatting.
2582              
2583             =cut
2584              
2585             sub buildfetch($$$) {
2586 0     0 1   my ($self,$bodies,$other) = @_;
2587              
2588 0           my $fetchstr = '(';
2589              
2590 0 0         if (ref($bodies) eq "HASH") { # convert a single hash ref arg to an array ref of 1
2591 0           $bodies = [$bodies];
2592             }
2593              
2594 0           foreach my $body (@{$bodies}) {
  0            
2595              
2596 0 0 0       if ((exists $body->{'offset'}) && !(exists $body->{'length'})) {
2597 0           return($self->throw_error("Length must be specified with offset"));
2598             }
2599            
2600 0           my $bodystr='';
2601 0 0         if ($body->{'header'}) {
2602 0 0         if (uc($body->{'header'}) eq 'ALL') {
    0          
    0          
2603 0           $bodystr .= 'HEADER ';
2604             } elsif (uc($body->{'header'}) eq 'MATCH') {
2605 0 0         return($self->throw_error("headerfields not defined for MATCH in buildfetch")) unless ($body->{headerfields});
2606 0           $bodystr .= 'HEADER.FIELDS ('.$body->{headerfields}.') ';
2607             } elsif (uc($body->{'header'}) eq 'NOT') {
2608 0 0         return($self->throw_error("headerfields not defined for NOT in buildfetch")) unless ($body->{headerfields});
2609 0           $bodystr .= 'HEADER.FIELDS.NOT ('.$body->{headerfields}.') ';
2610             }
2611             }
2612            
2613 0 0         $bodystr .= "$body->{body} " if ($body->{'body'});
2614 0 0         chop($bodystr) if $bodystr;
2615            
2616 0 0         $fetchstr .= "BODY" . (($body->{'peek'}) ? ".PEEK" : '') . "[$bodystr] ";
2617            
2618 0 0 0       if ($body->{'offset'} || $body->{'length'}) {
2619 0           chop($fetchstr);
2620 0   0       $fetchstr .= "<". ($body->{'offset'} || '0') . "." . $body->{'length'} . "> ";
2621             }
2622             }
2623              
2624 0 0         if ($other) {
2625 0           $other =~ s/^\(?(.*?)\)?$/$1/; # remove any surrounding parenthasies
2626 0           foreach my $item (split(/ /,$other)) {
2627 0           $item = uc($item);
2628 0 0         if ($item =~ /^(BODY|BODYSTRUCTURE|ENVELOPE|FLAGS|INTERNALDATE|UID|RFC822|RFC822\.HEADER|RFC822\.SIZE|RFC822\.TEXT|ALL|FAST|FULL)$/) {
2629 0           $fetchstr .= "$item ";
2630             } else {
2631 0           return($self->throw_error("Invalid buildfetch command: $item"));
2632             }
2633             }
2634             }
2635              
2636 0           chop($fetchstr);
2637 0           $fetchstr .= ')';
2638 0           return($fetchstr);
2639             }
2640              
2641              
2642             =pod
2643              
2644             =item B
2645              
2646             This function is provided for the ease of creating an appropriate status flags string. Simply provide it with a list of flags, and it will create a legal flags string for use in any append() command, store() command, or any other command that may require status flags.
2647              
2648             Since the RFCs don't explicity define valid flags, implementation dependant and custom flags may exist on any given service - therefore this function will blindly interpret any status flag you give it. The server may reject the subsequent command due to an invalid flag.
2649              
2650             =cut
2651              
2652             sub buildflaglist($@) {
2653 0     0 1   my ($self,@flags) = @_;
2654 0           my $flagstring;
2655             my %flaghash;
2656 0 0         if (@flags) {
2657 0           $flagstring = '(';
2658              
2659             # first normalize to one occurance of each argument
2660 0           foreach my $flag (@flags) {
2661 0           $flaghash{ucfirst(lc($flag))} = 1;
2662             }
2663              
2664             # add prefix '\' if nessesary
2665 0           foreach my $flag (keys(%flaghash)) {
2666 0           $flag =~ s/^(\w)/\\$1/;
2667 0           $flagstring .= "$flag ";
2668             }
2669 0           chop($flagstring);
2670 0           $flagstring .= ')';
2671             }
2672              
2673 0           return($flagstring);
2674             }
2675              
2676             =pod
2677              
2678             =item B
2679              
2680             This function returns a simple true/false answer to whether the supplied tag is found in the capability() list, indicating support for a certain feature. Note: capability() results are cached by the object, however if capability() has not been executed at least once, this will cause it to do so.
2681              
2682             =cut
2683              
2684             sub check_capability($$) {
2685 0     0 1   my ($self,$tag) = @_;
2686 0 0         return (1) unless ($self->{capability_checking}); # dont restrict if we're not checking capabilities
2687 0 0         $self->capability() unless ($self->{capability}); # get new capability string if we have yet to run it
2688 0 0         return(($self->{capabilities}->{$tag}) ? 1 : 0);
2689             }
2690              
2691             =pod
2692              
2693             =item B
2694              
2695             Given a referral line (the pre-cursory tag and 'NO' response are optional), this function will return a hash of the important information needed to connect to the referred server. Hash keys for connecting to a referred server include SCHEME, USER, AUTH, HOST, PORT. Other keys for use after successfull connection to the referred server include PATH, QUERY, UID, UIDVALIDITY, TYPE, SECTION. Others are possible if returned within the path as '/;key=value' format.
2696              
2697             =cut
2698              
2699             sub parse_referral() { #FIXME: needs wider testing
2700 0     0 1   my ($self) = @_;
2701 0           my %hash;
2702            
2703 0           my ($url) = ($self->error =~ /^NO \[REFERRAL (.*?)\][^\[]$/);
2704 0 0         return($self->throw_error("Invalid referral: ".$self->error)) unless $url;
2705              
2706 0           my $uri = URI->new ($url);
2707 0 0         return($self->throw_error("Wrong scheme: ".$uri->scheme)) unless ($uri->scheme eq "imap");
2708            
2709 0           %hash = ('SCHEME' => $uri->scheme,
2710             'HOST' => $uri->host($uri->host),
2711             'PORT' => $uri->port,
2712             'QUERY' => uri_unescape($uri->query),
2713             );
2714 0           ($hash{'USER'},$hash{'AUTH'}) =
2715             split(/;AUTH=/,uri_unescape($uri->userinfo));
2716 0           my $fullpath = '';
2717 0           foreach my $dir (split('/',uri_unescape($uri->path))) {
2718 0 0         if (my ($option) = ($dir =~ /^\;(.*)$/)) {
2719 0           my ($key,$value) = split(/=/,$1);
2720 0           $hash{uc($key)} = $value;
2721             } else {
2722 0           $fullpath .= $dir;
2723             }
2724             }
2725 0           $hash{'PATH'} = $fullpath;
2726              
2727 0           return(%hash);
2728             }
2729              
2730             =pod
2731              
2732             =back
2733              
2734             =head1 SEARCH KEYS
2735              
2736             =over 4
2737              
2738             =item * - Messages with message sequence numbers corresponding to the specified message sequence number set.
2739              
2740             =item * ALL - All messages in the mailbox; the default initial key for ANDing.
2741              
2742             =item * ANSWERED - Messages with the \Answered flag set.
2743              
2744             =item * BCC - Messages that contain the specified string in the envelope structure's BCC field.
2745              
2746             =item * BEFORE - Messages whose internal date (disregarding time and timezone) is earlier than the specified date.
2747              
2748             =item * BODY - Messages that contain the specified string in the body of the message.
2749              
2750             =item * CC - Messages that contain the specified string in the envelope structure's CC field.
2751              
2752             =item * DELETED - Messages with the \Deleted flag set.
2753              
2754             =item * DRAFT - Messages with the \Draft flag set.
2755              
2756             =item * FLAGGED - Messages with the \Flagged flag set.
2757              
2758             =item * FROM - Messages that contain the specified string in the envelope structure's FROM field.
2759              
2760             =item * HEADER - Messages that have a header with the specified field-name (as defined in [RFC-2822]) and that contains the specified string in the text of the header (what comes after the colon). If the string to search is zero-length, this matches all messages that have a header line with the specified field-name regardless of the contents.
2761              
2762             =item * KEYWORD - Messages with the specified keyword flag set.
2763              
2764             =item * LARGER - Messages with an [RFC-2822] size larger than the specified number of octets.
2765              
2766             =item * NEW - Messages that have the \Recent flag set but not the \Seen flag. This is functionally equivalent to "(RECENT UNSEEN)".
2767              
2768             =item * NOT - Messages that do not match the specified search key.
2769              
2770             =item * OLD - Messages that do not have the \Recent flag set. This is functionally equivalent to "NOT RECENT" (as opposed to "NOT NEW").
2771              
2772             =item * ON - Messages whose internal date (disregarding time and timezone) is within the specified date.
2773              
2774             =item * OR - Messages that match either search key.
2775              
2776             =item * RECENT - Messages that have the \Recent flag set.
2777              
2778             =item * SEEN - Messages that have the \Seen flag set.
2779              
2780             =item * SENTBEFORE - Messages whose [RFC-2822] Date: header (disregarding time and timezone) is earlier than the specified date.
2781              
2782             =item * SENTON - Messages whose [RFC-2822] Date: header (disregarding time and timezone) is within the specified date.
2783              
2784             =item * SENTSINCE - Messages whose [RFC-2822] Date: header (disregarding time and timezone) is within or later than the specified date.
2785              
2786             =item * SINCE - Messages whose internal date (disregarding time and timezone) is within or later than the specified date.
2787              
2788             =item * SMALLER - Messages with an [RFC-2822] size smaller than the specified number of octets.
2789              
2790             =item * SUBJECT - Messages that contain the specified string in the envelope structure's SUBJECT field.
2791              
2792             =item * TEXT - Messages that contain the specified string in the header or body of the message.
2793              
2794             =item * TO - Messages that contain the specified string in the envelope structure's TO field.
2795              
2796             =item * UID - Messages with unique identifiers corresponding to the specified unique identifier set. Sequence set ranges are permitted.
2797              
2798             =item * UNANSWERED - Messages that do not have the \Answered flag set.
2799              
2800             =item * UNDELETED - Messages that do not have the \Deleted flag set.
2801              
2802             =item * UNDRAFT - Messages that do not have the \Draft flag set.
2803              
2804             =item * UNFLAGGED - Messages that do not have the \Flagged flag set.
2805              
2806             =item * UNKEYWORD - Messages that do not have the specified keyword flag set.
2807              
2808             =item * UNSEEN - Messages that do not have the \Seen flag set.
2809              
2810             =back
2811              
2812             =head1 FETCH RESPONSE TUTORIAL
2813              
2814             The response to the fetch command is a tree of hash references pointing to other hash references in a tree-like structure. Going into this module and using fetch without an understanding about how the results come back is at least frustrationg and at worst futile. This section is meant to clear some of the potential confusion up, and for you to understand exactly where the data you want is stored.
2815              
2816             The fetch response is stored in a tree structure of hash references. This means that it will not be uncommon for you to have ugly-looking statements like such strewn throughout your code:
2817              
2818             =over 2
2819              
2820             $fetch{$msgid}->{BODY}->{2}->{HEADER}->{BODY}
2821              
2822             =back
2823              
2824             By the end of this tutorial, you will hopefully understand exactly what that top statement means.
2825              
2826             To understand where this structure comes from, you need to understand the structure that RFC3506 defines parts of a message. An example from the RFC looks like this:
2827              
2828             =over 4
2829              
2830             HEADER ([RFC-2822] header of the message)
2831             TEXT ([RFC-2822] text body of the message) MULTIPART/MIXED
2832             1 TEXT/PLAIN
2833             2 APPLICATION/OCTET-STREAM
2834             3 MESSAGE/RFC822
2835             3.HEADER ([RFC-2822] header of the message)
2836             3.TEXT ([RFC-2822] text body of the message) MULTIPART/MIXED
2837             3.1 TEXT/PLAIN
2838             3.2 APPLICATION/OCTET-STREAM
2839             4 MULTIPART/MIXED
2840             4.1 IMAGE/GIF
2841             4.1.MIME ([MIME-IMB] header for the IMAGE/GIF)
2842             4.2 MESSAGE/RFC822
2843             4.2.HEADER ([RFC-2822] header of the message)
2844             4.2.TEXT ([RFC-2822] text body of the message) MULTIPART/MIXED
2845             4.2.1 TEXT/PLAIN
2846             4.2.2 MULTIPART/ALTERNATIVE
2847             4.2.2.1 TEXT/PLAIN
2848             4.2.2.2 TEXT/RICHTEXT
2849              
2850             =back
2851              
2852             This example is rather complicated, but it gets the point across that this is no small feat. From the top, you can see that the HEADER and the TEXT are seperate pieces for the main message that was delivered, and thus can be retrieved as such. 1, 2, and 3 specify different sections of the email message - part 1 is a plain text email (the acutal text that was written to you), while part 2 is an OCTET-STREAM, perhaps a binary attachment to the email. The 3rd message is defined as an RFC822 - a bounce message. The bounce message (3), naturally has its own HEADER, and TEXT parts, along with an email with an attachment (the RFC822 bounce message included the original email, and all its attachments!). This goes on, and as you can see with part 4, the nesting can be quite deep, depending on if the email is multi-part (i.e. both plain-text and HTML), and/or if attachments have attachments, etc.
2853              
2854             Now, lets look at a concrete example. Lets say that we received a plain-text email with a forwarded email as an attachment. This would mean that the message contains 2 parts, and, for the sake of argument, we know this ahead of time. The command to retrieve the header of the forwarded message would be
2855              
2856             =over 4
2857              
2858             my %fetch = $imap->fetch($sequence, {'body' => '2.header'});
2859              
2860             =back
2861              
2862             For this example, however, we're going to retrieve the entire message, but still seek out the forwarded message's header
2863              
2864             =over 4
2865              
2866             my %fetch = $imap->fetch($sequence, {});
2867              
2868             =back
2869              
2870             where $imap is a connected IMAP::Client instance, and $sequence has the message ID we are looking for.
2871              
2872             Now, we need to retrieve the data that the IMAP server do dutifully sent to us, and this is where we get grease on our hands and learn exactly how to traverse a fetch response.
2873              
2874             The first level of a fetch response is always the message ID of the message, and is the only level that is *not* a reference. This allows the fetch command to retrieve multiple message within a single command (i.e. using the sequence of '1:*' will retrieve all messages in the mailbox), and still present the data in a managable fasion.
2875              
2876             Lets say that $sequence was the message '1234'. In order to reach the base of the message we are looking to retrieve the data from, we now need to access $fetch{1234}. Everything below here is information about our message.
2877              
2878             Next, we will need to navigate to the area of the tree that will contain the data. The data we are looking for will *always* be in the same place, no matter if we retrieved the entire message, half the message, or just that one single peice of data, like the first example - if it was retreived, it will be in its specified place. This the key design feature of the fetch return structure.
2879              
2880             At this level ($fetch{1234}), we can access anything about the main message. We are looking at the message from the outside, what you would normally see in your email client when you first opened a message. We can look at things like the date of the message, the flags set on this message by the imap server, the UID of the message, the envelope of the message, etc.
2881              
2882             In this case, we're not interested in the main message, however. We want to retrieve the header of the forwarded message, so we need to go into the BODY of the message. To get there, we're now at $fetch{1234}->{BODY}. IMAP::Client uses the {BODY} reference to seperate it from the other aspects of the main message, incidcating its a result from a BODY fetch query. The BODY section also contains various peices of information, depending on what the CONTENTTYPE of the message is - for things like MULTIPART/ALTERNATIVE (which means the message comes in multiple forms, like plain text and HTML), there simply isn't much information to relay, since that content type is basically just a container for the two message formats. If the section is part of the actual message (rather than just a container) - for example a PLAIN/TEXT part, things like SIZE for the size of the message, LINES for number of lines in the message, and even the ENCODING and extra PARAMETERS are available.
2883              
2884             Now that we're in the body, we can look at things like the content type of this particular piece of the email. Again, we're not interested in whats here. What we want is the second part of this body, the attachment part. The main body of the email that was sent is located in $fetch{1234}->{BODY}->{1}, and the attachment is located at $fetch{1234}->{BODY}->{2}. (See how the fetch structure reflects the IMAP structure of the message). Had there been more attachments or parts, there would have been more parts we could traverse, like $fetch{1234}->{BODY}->{3}.
2885              
2886             Now at $fetch{1234}->{BODY}->{2}, we're in the section of the message we are interested in. Here we can find out information about the part we're in. This part is essentially identical to the first {BODY} part, only representing a subset of the mesage that {BODY} represented.
2887              
2888             Now that we're here, we want the header for this part, which gives us $fetch{1234}->{BODY}->{2}->{HEADER}. We're not done yet, however, as there is still information about the HEADER available, like the SIZE. If we want the acutal HEADER body of the header, rather than a piece of information about it, we need to go one level deeper, to $fetch{1234}->{BODY}->{2}->{HEADER}->{BODY}. This is the value that will allow you to retreive the header of the forward that was sent in an attachment.
2889              
2890              
2891              
2892             In the last example, we assumed that we already knew the struture of the email. In real life, this is almost never the case. If you need to know what the structure of a message looks like so you can extract a small piece of it, you can use the BODYSTRCUTRE command, which is structured simiilarly to the BODY command. If we use the example above, then we can traverse the BODYSTRUCTURE information by going to $fetch{1234}->{BODYSTRUCTURE}. From here, you can explore and poke around to see exactly what the structure is that the message has. The acutal data within a BODYSTRUCTURE is basically all the flags you would see when youre in a part - like the content type, size, lines, etc - but without any of the acutal message. As its name implies, its mainly for determining the structure and *type* of the message and its subparts. Part numbers are always sequential.
2893              
2894             =head1 AUTHOR/COPYRIGHT
2895              
2896             Copyright 2005-2006, Brenden Conte All rights reserved.
2897              
2898             This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
2899              
2900             =head1 SEE ALSO
2901              
2902             perl, IO::Socket, IO::Socket::SSL, MIME::Base64, URI, URI::imap, URI::Escape
2903              
2904             =cut
2905              
2906              
2907             1;
2908             __END__