File Coverage

blib/lib/Mail/Transport/POP3.pm
Criterion Covered Total %
statement 21 206 10.1
branch 0 122 0.0
condition 0 30 0.0
subroutine 7 29 24.1
pod 18 19 94.7
total 46 406 11.3


line stmt bran cond sub pod time code
1             # Copyrights 2001-2019 by [Mark Overmeer].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.02.
5             # This code is part of distribution Mail-Box-POP3. Meta-POD processed with
6             # OODoc into POD and HTML manual-pages. See README.md
7             # Copyright Mark Overmeer. Licensed under the same terms as Perl itself.
8              
9             package Mail::Transport::POP3;
10 4     4   25 use vars '$VERSION';
  4         6  
  4         191  
11             $VERSION = '3.005';
12              
13 4     4   20 use base 'Mail::Transport::Receive';
  4         8  
  4         1773  
14              
15 4     4   14728 use strict;
  4         10  
  4         69  
16 4     4   19 use warnings;
  4         6  
  4         81  
17              
18 4     4   1918 use IO::Socket ();
  4         86593  
  4         116  
19 4     4   26 use Socket qw/$CRLF/;
  4         7  
  4         804  
20 4     4   29 use Digest::MD5 qw/md5_hex/;
  4         7  
  4         9845  
21              
22              
23 0   0 0     sub _OK($) { substr(shift // '', 0, 3) eq '+OK' }
24              
25             sub init($)
26 0     0 0   { my ($self, $args) = @_;
27 0           $args->{via} = 'pop3';
28 0   0       $args->{port} ||= 110;
29              
30 0 0         $self->SUPER::init($args) or return;
31              
32 0   0       $self->{MTP_auth} = $args->{authenticate} || 'AUTO';
33 0           $self->{MTP_ssl} = $args->{use_ssl};
34 0 0         $self->socket or return; # establish connection
35              
36 0           $self;
37             }
38              
39             #------------------------------------------
40              
41 0     0 1   sub useSSL() { shift->{MTP_ssl} }
42              
43             #------------------------------------------
44              
45             sub ids(;@)
46 0     0 1   { my $self = shift;
47 0 0         $self->socket or return;
48 0 0         wantarray ? @{$self->{MTP_n2uidl}} : $self->{MTP_n2uidl};
  0            
49             }
50              
51              
52             sub messages()
53 0     0 1   { my $self = shift;
54              
55 0 0         $self->log(ERROR =>"Cannot get the messages of pop3 via messages()."), return ()
56             if wantarray;
57              
58 0           $self->{MTP_messages};
59             }
60              
61              
62 0     0 1   sub folderSize() { shift->{MTP_folder_size} }
63              
64              
65             sub header($;$)
66 0     0 1   { my ($self, $uidl) = (shift, shift);
67 0 0         return unless $uidl;
68 0   0       my $bodylines = shift || 0;;
69              
70 0 0         my $socket = $self->socket or return;
71 0 0         my $n = $self->id2n($uidl) or return;
72              
73 0           $self->sendList($socket, "TOP $n $bodylines$CRLF");
74             }
75              
76              
77             sub message($;$)
78 0     0 1   { my ($self, $uidl) = @_;
79 0 0         return unless $uidl;
80              
81 0 0         my $socket = $self->socket or return;
82 0 0         my $n = $self->id2n($uidl) or return;
83 0           my $message = $self->sendList($socket, "RETR $n$CRLF");
84              
85 0 0         return unless $message;
86              
87             # Some POP3 servers add a trailing empty line
88 0 0 0       pop @$message if @$message && $message->[-1] =~ m/^[\012\015]*$/;
89              
90             $self->{MTP_fetched}{$uidl} = undef # mark this ID as fetched
91 0 0         unless exists $self->{MTP_nouidl};
92              
93 0           $message;
94             }
95              
96              
97             sub messageSize($)
98 0     0 1   { my ($self, $uidl) = @_;
99 0 0         return unless $uidl;
100              
101 0           my $list;
102 0 0         unless($list = $self->{MTP_n2length})
103 0 0         { my $socket = $self->socket or return;
104 0 0         my $raw = $self->sendList($socket, "LIST$CRLF") or return;
105 0           my @n2length;
106 0           foreach (@$raw)
107 0           { m#^(\d+) (\d+)#;
108 0           $n2length[$1] = $2;
109             }
110 0           $self->{MTP_n2length} = $list = \@n2length;
111             }
112              
113 0 0         my $n = $self->id2n($uidl) or return;
114 0           $list->[$n];
115             }
116              
117              
118             sub deleted($@)
119 0   0 0 1   { my $dele = shift->{MTP_dele} ||= {};
120 0 0         (shift) ? @$dele{ @_ } = () : delete @$dele{ @_ };
121             }
122              
123              
124             sub deleteFetched()
125 0     0 1   { my $self = shift;
126 0           $self->deleted(1, keys %{$self->{MTP_fetched}});
  0            
127             }
128              
129              
130             sub disconnect()
131 0     0 1   { my $self = shift;
132              
133 0           my $quit;
134 0 0         if($self->{MTP_socket}) # can only disconnect once
135 0 0         { if(my $socket = $self->socket)
136 0   0       { my $dele = $self->{MTP_dele} || {};
137 0           while(my $uidl = each %$dele)
138 0 0         { my $n = $self->id2n($uidl) or next;
139 0 0         $self->send($socket, "DELE $n$CRLF") or last;
140             }
141              
142 0           $quit = $self->send($socket, "QUIT$CRLF");
143 0           close $socket;
144             }
145             }
146              
147 0           delete @$self{ qw(
148             MTP_socket
149             MTP_dele
150             MTP_uidl2n
151             MTP_n2uidl
152             MTP_n2length
153             MTP_fetched
154             ) };
155              
156 0           _OK $quit;
157             }
158              
159              
160             sub fetched(;$)
161 0     0 1   { my $self = shift;
162 0 0         return if exists $self->{MTP_nouidl};
163 0           $self->{MTP_fetched};
164             }
165              
166              
167 0     0 1   sub id2n($;$) { shift->{MTP_uidl2n}{shift()} }
168              
169             #------------------------------------------
170              
171              
172             sub socket()
173 0     0 1   { my $self = shift;
174              
175             # Do we (still) have a working connection which accepts commands?
176 0           my $socket = $self->_connection;
177 0 0         return $socket if defined $socket;
178              
179 0 0         if(exists $self->{MTP_nouidl})
180 0           { $self->log(ERROR =>
181             "Can not re-connect reliably to server which doesn't support UIDL");
182 0           return;
183             }
184              
185             # (Re-)establish the connection
186 0 0         $socket = $self->login or return;
187 0 0         $self->status($socket) or return;
188 0           $self->{MTP_socket} = $socket;
189             }
190              
191              
192              
193             sub send($$)
194 0     0 1   { my $self = shift;
195 0           my $socket = shift;
196 0           my $response;
197            
198 0 0         if(eval {print $socket @_})
  0            
199 0           { $response = <$socket>;
200 0 0         $self->log(ERROR => "Cannot read POP3 from socket: $!")
201             unless defined $response;
202             }
203             else
204 0           { $self->log(ERROR => "Cannot write POP3 to socket: $@");
205             }
206 0           $response;
207             }
208              
209              
210             sub sendList($$)
211 0     0 1   { my ($self, $socket) = (shift, shift);
212 0           my $response = $self->send($socket, @_);
213 0 0 0       $response && _OK $response or return;
214              
215 0           my @list;
216 0           while(my $line = <$socket>)
217 0 0         { last if $line =~ m#^\.\r?\n#s;
218 0           $line =~ s#^\.##;
219 0           push @list, $line;
220             }
221              
222 0           \@list;
223             }
224              
225             sub DESTROY()
226 0     0     { my $self = shift;
227 0           $self->SUPER::DESTROY;
228 0 0         $self->disconnect if $self->{MTP_socket}; # only when open
229             }
230              
231             sub _connection()
232 0     0     { my $self = shift;
233              
234 0           my $socket = $self->{MTP_socket};
235 0 0         defined $socket or return;
236              
237             # Check if we (still) got a connection
238 0           eval { print $socket "NOOP$CRLF" };
  0            
239 0 0 0       if($@ || ! <$socket> )
240 0           { delete $self->{MTP_socket};
241 0           return undef;
242             }
243              
244 0           $socket;
245             }
246              
247              
248              
249             sub login(;$)
250 0     0 1   { my $self = shift;
251              
252             # Check if we can make a connection
253              
254 0           my ($host, $port, $username, $password) = $self->remoteHost;
255 0 0 0       unless($username && $password)
256 0           { $self->log(ERROR => "POP3 requires a username and password.");
257 0           return;
258             }
259              
260 0 0         my $net = $self->useSSL ? 'IO::Socket::SSL' : 'IO::Socket::INET';
261 0 0         eval "require $net" or die $@;
262              
263 0           my $socket = eval { $net->new("$host:$port") };
  0            
264 0 0         unless($socket)
265 0           { $self->log(ERROR => "Cannot connect to $host:$port for POP3: $!");
266 0           return;
267             }
268              
269             # Check if it looks like a POP server
270              
271 0           my $connected;
272 0           my $authenticate = $self->{MTP_auth};
273 0           my $welcome = <$socket>;
274 0 0         unless(_OK $welcome)
275 0           { $self->log(ERROR =>
276             "Server at $host:$port does not seem to be talking POP3.");
277 0           return;
278             }
279              
280             # Check APOP login if automatic or APOP specifically requested
281 0 0 0       if($authenticate eq 'AUTO' || $authenticate eq 'APOP')
282 0 0         { if($welcome =~ m#^\+OK .*(<\d+\.\d+\@[^>]+>)#)
283 0           { my $md5 = md5_hex $1.$password;
284 0           my $response = $self->send($socket, "APOP $username $md5$CRLF");
285 0           $connected = _OK $response;
286             }
287             }
288              
289             # Check USER/PASS login if automatic and failed or LOGIN specifically
290             # requested.
291 0 0         unless($connected)
292 0 0 0       { if($authenticate eq 'AUTO' || $authenticate eq 'LOGIN')
293 0 0         { my $response = $self->send($socket, "USER $username$CRLF")
294             or return;
295              
296 0 0         if(_OK $response)
297 0 0         { my $response2 = $self->send($socket, "PASS $password$CRLF")
298             or return;
299 0           $connected = _OK $response2;
300             }
301             }
302             }
303              
304             # If we're still not connected now, we have an error
305 0 0         unless($connected)
306 0 0         { $self->log(ERROR => $authenticate eq 'AUTO' ?
307             "Could not authenticate using any login method" :
308             "Could not authenticate using '$authenticate' method");
309 0           return;
310             }
311              
312 0           $socket;
313             }
314              
315              
316              
317             sub status($;$)
318 0     0 1   { my ($self, $socket) = @_;
319              
320             # Check if we can do a STAT
321              
322 0 0         my $stat = $self->send($socket, "STAT$CRLF") or return;
323 0 0         if($stat !~ m#^\+OK (\d+) (\d+)#)
324 0           { delete $self->{MTP_messages};
325 0           delete $self->{MTP_size};
326 0           $self->log(ERROR => "POP3 Could not do a STAT");
327 0           return;
328             }
329 0           $self->{MTP_messages} = my $nr_msgs = $1;
330 0           $self->{MTP_folder_size} = $2;
331              
332             # Check if we can do a UIDL
333              
334 0 0         my $uidl = $self->send($socket, "UIDL$CRLF") or return;
335 0           $self->{MTP_nouidl} = undef;
336 0           delete $self->{MTP_uidl2n}; # drop the reverse lookup: UIDL -> number
337              
338 0 0         if(_OK $uidl)
339 0           { my @n2uidl;
340 0           $n2uidl[$nr_msgs] = undef; # pre-alloc
341              
342 0           while(my $line = <$socket>)
343 0 0         { last if substr($line, 0, 1) eq '.';
344 0 0         $line =~ m#^(\d+) (.+?)\r?\n# or next;
345 0           $n2uidl[$1] = $2;
346             }
347              
348 0           shift @n2uidl; # make message 1 into index 0
349 0           $self->{MTP_n2uidl} = \@n2uidl;
350 0           delete $self->{MTP_n2length};
351 0           delete $self->{MTP_nouidl};
352             }
353             else
354             { # We can't do UIDL, we need to fake it
355 0 0         my $list = $self->send($socket, "LIST$CRLF") or return;
356 0           my (@n2length, @n2uidl);
357              
358 0 0         if(_OK $list)
359 0           { $n2length[$nr_msgs] = $n2uidl[$nr_msgs] = undef; # alloc all
360              
361 0           my ($host, $port) = $self->remoteHost;
362 0           while(my $line = <$socket>)
363 0 0         { last if substr($line, 0, 1) eq '.';
364 0 0         $line =~ m#^(\d+) (\d+)# or next;
365 0           $n2length[$1] = $2;
366 0           $n2uidl[$1] = "$host:$port:$1"; # fake UIDL, for id only
367             }
368 0           shift @n2length; shift @n2uidl; # make 1st message in index 0
  0            
369             }
370 0           $self->{MTP_n2length} = \@n2length;
371 0           $self->{MTP_n2uidl} = \@n2uidl;
372             }
373              
374 0           my $i = 1;
375 0           my %uidl2n = map +($_ => $i++), @{$self->{MTP_n2uidl}};
  0            
376 0           $self->{MTP_uidl2n} = \%uidl2n;
377              
378 0           1;
379             }
380              
381             #------------------------------------------
382              
383              
384             sub url(;$)
385 0     0 1   { my $self = shift;
386 0           my ($host, $port, $user, $pwd) = $self->remoteHost;
387 0 0         my $proto = $self->useSSL ? 'pop3s' : 'pop3';
388 0           "$proto://$user:$pwd\@$host:$port";
389             }
390              
391             #------------------------------------------
392              
393              
394             1;