File Coverage

blib/lib/Mail/Box/IMAP4.pm
Criterion Covered Total %
statement 33 176 18.7
branch 0 74 0.0
condition 0 43 0.0
subroutine 11 27 40.7
pod 14 16 87.5
total 58 336 17.2


line stmt bran cond sub pod time code
1             # This code is part of Perl distribution Mail-Box-IMAP4 version 4.01.
2             # The POD got stripped from this file by OODoc version 3.05.
3             # For contributors see file ChangeLog.
4              
5             # This software is copyright (c) 2001-2025 by Mark Overmeer.
6              
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
10              
11              
12             package Mail::Box::IMAP4;{
13             our $VERSION = '4.01';
14             }
15              
16 3     3   593807 use base 'Mail::Box::Net';
  3         9  
  3         2174  
17              
18 3     3   351948 use strict;
  3         13  
  3         76  
19 3     3   18 use warnings;
  3         6  
  3         172  
20              
21 3     3   19 use Log::Report 'mail-box-imap4', import => [ qw/__x error notice trace warning/ ];
  3         8  
  3         17  
22              
23 3     3   1764 use Mail::Box::IMAP4::Head ();
  3         8  
  3         66  
24 3     3   1125 use Mail::Box::IMAP4::Message ();
  3         8  
  3         93  
25 3     3   732 use Mail::Box::Parser::Perl ();
  3         6944  
  3         64  
26 3     3   17 use Mail::Message::Head::Complete ();
  3         7  
  3         50  
27 3     3   14 use Mail::Message::Head::Delayed ();
  3         5  
  3         45  
28 3     3   1156 use Mail::Transport::IMAP4 ();
  3         9  
  3         109  
29              
30 3     3   27 use Scalar::Util qw/weaken blessed/;
  3         8  
  3         8347  
31              
32             #--------------------
33              
34             sub init($)
35 0     0 0   { my ($self, $args) = @_;
36 0   0       my $folder = $args->{folder} // '/';
37              
38             # MailBox names top folder directory '=', but IMAP needs '/'
39 0 0         $folder = '/' if $folder eq '=';
40              
41             # There's a disconnect between the URL parser and this code.
42             # The URL parser always produces a full path (beginning with /)
43             # while this code expects to NOT get a full path. So, we'll
44             # trim the / from the front of the path.
45             # Also, this code can't handle a trailing slash and there's
46             # no reason to ever offer one. Strip that too.
47 0 0         if($folder ne '/')
48 0           { $folder =~ s,^/+,,g;
49 0           $folder =~ s,/+$,,g;
50             }
51              
52 0           $args->{folder} = $folder;
53              
54 0   0       my $access = $args->{access} ||= 'r';
55 0           my $writeable = $access =~ m/w|a/;
56 0   0       my $ch = $self->{MBI_c_head} = $args->{cache_head} || ($writeable ? 'NO' : 'DELAY');
57              
58 0 0 0       $args->{head_type} ||= 'Mail::Box::IMAP4::Head'
      0        
59             if $ch eq 'NO' || $ch eq 'PARTIAL';
60              
61 0   0       $args->{body_type} ||= 'Mail::Message::Body::Lines';
62 0   0       $args->{message_type} ||= 'Mail::Box::IMAP4::Message';
63              
64 0 0         if(my $client = $args->{imap_client}) {
65 0           $args->{server_name} = $client->Socket->peerhost();
66 0           $args->{server_port} = $client->Socket->peerport();
67 0           $args->{username} = $client->User;
68             }
69              
70 0           $self->SUPER::init($args);
71              
72 0           $self->{MBI_domain} = $args->{domain};
73 0   0       $self->{MBI_c_labels} = $args->{cache_labels} || ($writeable ? 'NO' : 'DELAY');
74 0   0       $self->{MBI_c_body} = $args->{cache_body} || ($writeable ? 'NO' : 'DELAY');
75              
76 0   0       my $transport = $args->{transporter} || 'Mail::Transport::IMAP4';
77 0 0         blessed $transport or $transport = $self->createTransporter($transport, %$args);
78              
79 0           $self->transporter($transport);
80 0 0         defined $transport or return;
81              
82 0 0         $args->{create} ? $self->create($transport, $args) : $self;
83             }
84              
85             sub create($@)
86 0     0 1   { my($self, $name, $args) = @_;
87              
88 0 0         if($args->{access} !~ /w|a/)
89 0           { error __x"you must have write access to create folder {name}.", name => $name;
90 0           return undef;
91             }
92              
93 0           $self->transporter->createFolder($name);
94             }
95              
96             sub foundIn(@)
97 0     0 1   { my $self = shift;
98 0 0         unshift @_, 'folder' if @_ % 2;
99 0           my %args = @_;
100              
101             (exists $args{type} && $args{type} =~ m/^imap/i)
102 0 0 0       || (exists $args{folder} && $args{folder} =~ m/^imap/);
      0        
103             }
104              
105             sub type() {'imap4'}
106              
107              
108              
109             sub close(@)
110 0     0 1   { my $self = shift;
111 0 0         $self->SUPER::close(@_) or return ();
112 0           $self->transporter(undef);
113 0           $self;
114             }
115              
116             sub listSubFolders(@)
117 0     0 1   { my ($thing, %args) = @_;
118 0           my $self = $thing;
119              
120 0 0 0       $self = $thing->new(%args) or return () # list toplevel
121             unless ref $thing;
122              
123 0           my $imap = $self->transporter;
124 0 0         defined $imap ? $imap->folders($self) : ();
125             }
126              
127 0     0 0   sub nameOfSubfolder($;$) { $_[1] }
128              
129             #--------------------
130              
131             sub readMessages(@)
132 0     0 1   { my ($self, %args) = @_;
133              
134 0           my $name = $self->name;
135 0 0         return $self if $name eq '/';
136              
137 0   0       my $imap = $self->transporter // return;
138 0           my $seqnr = 0;
139              
140 0           my $cl = $self->{MBI_c_labels} ne 'NO';
141 0           my $wl = $self->{MBI_c_labels} ne 'DELAY';
142              
143 0           my $ch = $self->{MBI_c_head};
144 0 0         my $ht = $ch eq 'DELAY' ? $args{head_delayed_type} : $args{head_type};
145 0 0         my @ho = $ch eq 'PARTIAL' ? (cache_fields => 1) : ();
146              
147             $self->{MBI_selectable}
148 0 0         or return $self;
149              
150 0           foreach my $id ($imap->ids)
151 0           { my $head = $ht->new(@ho);
152             my $message = $args{message_type}->new(
153 0           head => $head,
154             unique => $id,
155             folder => $self,
156             seqnr => $seqnr++,
157              
158             cache_labels => $cl,
159             write_labels => $wl,
160             cache_head => ($ch eq 'DELAY'),
161             cache_body => ($ch ne 'NO'),
162             );
163              
164 0           my $body = $args{body_delayed_type}->new(message => $message);
165 0           $message->storeBody($body);
166 0           $self->storeMessage($message);
167             }
168              
169 0           $self;
170             }
171              
172              
173             sub getHead($)
174 0     0 1   { my ($self, $message) = @_;
175 0 0         my $imap = $self->transporter or return;
176 0           my $uidl = $message->unique;
177 0           my @fields = $imap->getFields($uidl, 'ALL');
178              
179 0 0         unless(@fields)
180 0           { warning __x"message {id} disappeared from {folder}.", id => $uidl, folder => "$self";
181 0           return;
182             }
183              
184 0           my $head = $self->{MB_head_type}->new;
185 0           $head->addNoRealize($_) for @fields;
186              
187 0           trace "Loaded head of $uidl.";
188 0           $head;
189             }
190              
191              
192              
193             sub getHeadAndBody($)
194 0     0 1   { my ($self, $message) = @_;
195 0 0         my $imap = $self->transporter or return;
196 0           my $uid = $message->unique;
197 0           my $lines = $imap->getMessageAsString($uid);
198              
199 0 0         unless(defined $lines)
200 0           { warning __x"message {id} disappeared from {folder}.", id => $uid, folder => $self->name;
201 0           return ();
202             }
203              
204 0           my $parser = Mail::Box::Parser::Perl->new( # not parseable by C parser
205             filename => "$imap",
206             file => Mail::Box::FastScalar->new(\$lines)
207             );
208              
209 0           my $head = $message->readHead($parser);
210 0 0         unless(defined $head)
211 0           { warning __x"cannot find head back for {id} in {folder}.", id => $uid, folder => $self;
212 0           $parser->stop;
213 0           return ();
214             }
215              
216 0           my $body = $message->readBody($parser, $head);
217 0 0         unless(defined $body)
218 0           { warning __x"cannot read body for {id} in {folder}.", id => $uid, folder => $self->name;
219 0           $parser->stop;
220 0           return ();
221             }
222              
223 0           $parser->stop;
224              
225 0           trace "loaded message $uid.";
226 0           ($head, $body->contentInfoFrom($head));
227             }
228              
229              
230              
231             sub body(;$)
232 0     0 1   { my $self = shift;
233 0 0         @_ or return $self->{MBI_cache_body} ? $self->SUPER::body : undef;
    0          
234              
235 0           $self->unique();
236 0           $self->SUPER::body(@_);
237             }
238              
239              
240              
241             sub write(@)
242 0     0 1   { my ($self, %args) = @_;
243 0 0         my $imap = $self->transporter or return;
244              
245 0           $self->SUPER::write(%args, transporter => $imap);
246              
247 0 0         if($args{save_deleted})
248 0           { notice __x"impossible to keep deleted messages in IMAP folder {name}.", name => $self->name;
249             }
250 0           else { $imap->destroyDeleted($self->name) }
251              
252 0           $self;
253             }
254              
255             sub delete(@)
256 0     0 1   { my $self = shift;
257 0           my $transp = $self->transporter;
258 0           $self->SUPER::delete(@_); # subfolders
259 0           $transp->deleteFolder($self->name);
260             }
261              
262              
263              
264             sub writeMessages($@)
265 0     0 1   { my ($self, $args) = @_;
266              
267 0           my $imap = $args->{transporter};
268 0           my $fn = $self->name;
269              
270 0           $_->writeDelayed($fn, $imap) for @{$args->{messages}};
  0            
271              
272 0           $self;
273             }
274              
275              
276              
277             my %transporters;
278             sub createTransporter($@)
279 0     0 1   { my ($self, $class, %args) = @_;
280              
281 0   0       my $hostname = $self->{MBN_hostname} || 'localhost';
282 0   0       my $port = $self->{MBN_port} || '143';
283 0   0       my $username = $self->{MBN_username} || $ENV{USER};
284              
285 0 0         my $join = exists $args{join_connection} ? $args{join_connection} : 1;
286              
287 0           my $linkid;
288 0 0         if($join)
289 0           { $linkid = "$hostname:$port:$username";
290 0 0         return $transporters{$linkid} if defined $transporters{$linkid};
291             }
292              
293             my $transporter = $class->new(
294             %args,
295             hostname => $hostname, port => $port,
296             username => $username, password => $self->{MBN_password},
297             domain => $self->{MBI_domain},
298 0 0         ) or return undef;
299              
300 0 0         if(defined $linkid)
301 0           { $transporters{$linkid} = $transporter;
302 0           weaken($transporters{$linkid});
303             }
304              
305 0           $transporter;
306             }
307              
308              
309              
310             sub transporter(;$)
311 0     0 1   { my $self = shift;
312              
313 0           my $imap;
314 0 0         if(@_)
315 0   0       { $imap = $self->{MBI_transport} = shift // return;
316             }
317             else
318 0           { $imap = $self->{MBI_transport};
319             }
320              
321 0 0         defined $imap
322             or error __x"no IMAP4 transporter configured.";
323              
324 0           my $name = $self->name;
325              
326 0 0         $self->{MBI_selectable} = $imap->currentFolder($name)
327             or error "couldn't select IMAP4 folder {name}.", name => $name;
328              
329 0           $imap;
330             }
331              
332              
333              
334             sub fetch($@)
335 0     0 1   { my ($self, $what, @info) = @_;
336 0 0         my $imap = $self->transporter or return [];
337 0 0         $what = $self->messages($what) unless ref $what eq 'ARRAY';
338 0           $imap->fetch($what, @info);
339             }
340              
341             #--------------------
342              
343             1;