File Coverage

blib/lib/Mail/POP3/Folder/mbox.pm
Criterion Covered Total %
statement 133 178 74.7
branch 23 42 54.7
condition 6 9 66.6
subroutine 22 30 73.3
pod 14 14 100.0
total 198 273 72.5


line stmt bran cond sub pod time code
1             package Mail::POP3::Folder::mbox;
2              
3             our @ISA = qw(Mail::POP3::Folder);
4              
5 4     4   29 use strict;
  4         25  
  4         133  
6 4     4   27 use IO::File;
  4         11  
  4         678  
7 4     4   30 use Fcntl ':flock';
  4         10  
  4         6899  
8              
9             my $CRLF = "\015\012";
10              
11             sub new {
12             my (
13 2     2 1 10 $class,
14             $user_name,
15             $password,
16             $user_id,
17             $mailgroup,
18             $spoolfile,
19             $message_start,
20             $message_end,
21             ) = @_;
22 2         4 my $self = {};
23 2         9 bless $self, $class;
24 2         17 $self->{CLIENT_USER_ID} = $user_id;
25 2         8 $self->{MAILGROUP} = $mailgroup;
26 2         5 $self->{SPOOLFILE} = $spoolfile;
27 2         4 $self->{MESSAGE_START} = $message_start;
28 2         5 $self->{MESSAGE_END} = $message_end;
29 2         7 $self->{MESSAGECNT} = 0;
30 2         4 $self->{MSG2OCTETS} = {};
31 2         6 $self->{MSG2UIDL} = {};
32 2         5 $self->{MSG2FROMLINE} = {};
33 2         2 $self->{MSG2TEXT} = {};
34 2         4 $self->{TOTALOCTETS} = 0;
35 2         4 $self->{DELMESSAGECNT} = 0;
36 2         3 $self->{DELTOTALOCTETS} = 0;
37 2         6 $self;
38             }
39              
40             sub _start_new_message {
41 6     6   16 my ($self, $newmessageno, $fromline) = @_;
42             # Hold the "From ..." line to put back if message is not retrieved
43 6         35 $self->{MSG2FROMLINE}->{$newmessageno} = $fromline;
44             }
45              
46             sub _close_old_message {
47 6     6   14 my ($self, $oldmessageno, $messageuidl, $messageoctets) = @_;
48 6 50       12 return if $oldmessageno <= 0;
49 6         9 $self->{TOTALOCTETS} += $messageoctets;
50 6         13 $self->{MSG2OCTETS}->{$oldmessageno} = $messageoctets;
51 6         14 $self->{MSG2UIDL}->{$oldmessageno} = $messageuidl;
52             }
53              
54             # assume that mbox is good, no error-checking at start
55             # not sure the message_end is necessary, is that for MMDF?
56             sub _list_messages {
57 2     2   3 my $self = shift;
58 2 50       4 if (!-s $self->_spoolfile) {
59             # no mail
60 0         0 return;
61             }
62 2         4 my $seen_message_end = 1; # the end of fake "message 0"...
63 2         3 my $messagecnt = 0;
64 2         4 my $messageoctets = 0;
65 2         3 my $messageuidl = '';
66 2         5 local *MDROP;
67 2         7 open MDROP, $self->_spoolfile;
68 2         128 while () {
69 74         182 $self->_lock_update;
70 74         324 s/\r|\n//g;
71 74 100 100     236 if ($seen_message_end and /$self->{MESSAGE_START}/) {
72             # tick over
73 6         26 $self->_close_old_message(
74             $messagecnt,
75             $messageuidl,
76             $messageoctets,
77             );
78 6         24 $messagecnt++;
79 6         16 $messageuidl = '';
80 6         9 $messageoctets = 0;
81 6         17 $self->_start_new_message($messagecnt, $_);
82             } else {
83 68         84 $seen_message_end = 0;
84 68 100       236 if (/$self->{MESSAGE_END}/) {
85 8         11 $seen_message_end = 1;
86             }
87 68 100 66     162 if (/^Message-Id:\s*(.+)/ and not $messageuidl) {
88             # only take first Message-ID; cf such a header appearing in body
89 6         18 $messageuidl = $1;
90             }
91 68         162 $self->_push_message($messagecnt, $_);
92 68         224 $messageoctets += length ($_.$CRLF);
93             }
94             }
95             # catch the last one
96 2         8 $self->_close_old_message($messagecnt, $messageuidl, $messageoctets);
97 2         11 $self->{MESSAGECNT} = $messagecnt;
98             }
99              
100             sub _spoolfile {
101 10     10   16 my $self = shift;
102 10         62 $self->{SPOOLFILE};
103             }
104              
105             sub lock_acquire {
106 2     2 1 10 my $self = shift;
107 2         18 my $lockfile = $self->_lock_filename;
108 2 50       93 return if -f $lockfile;
109 2         11 $self->{LINE} = 0;
110 2 50       15 $self->{LOCK_FH} = IO::File->new(
111             ">$lockfile"
112             ) or die "open >$lockfile: $!\n";
113 2 50       184 unless (flock $self->{LOCK_FH}, LOCK_EX|LOCK_NB) {
114 0         0 unlink $lockfile;
115 0         0 return;
116             }
117 2         19 chmod 0600, $lockfile;
118 2         21 chown $self->{CLIENT_USER_ID}, $self->{MAILGROUP}, $lockfile;
119 2         9 my $oldfh = select $self->{LOCK_FH};
120 2         7 $| = 1;
121 2         4 select $oldfh;
122 2         19 $self->_lock_refresh;
123             # stat the file to get its size, this is checked before closing
124             # the mailbox.
125             # If the size has changed the lock may have been compromised, so a
126             # backup is then made.
127 2         15 my @filestat = stat $self->_spoolfile;
128 2         6 $self->{MAILBOX_TIMESTAMP_OPEN} = $filestat[9];
129             # set effective UID to user for the rest of the session;
130 2         7 $> = $self->{CLIENT_USER_ID};
131 2         14 $self->_list_messages;
132 2 50       8 die unless $self->{LOCK_FH};
133 2         10 1;
134             }
135              
136             sub lock_release {
137 2     2 1 6 my $self = shift;
138 2         17 close $self->{LOCK_FH};
139 2         17 $> = 0;
140 2         11 unlink $self->_lock_filename;
141 2         108 $> = $self->{CLIENT_USER_ID};
142             }
143              
144             sub _lock_filename {
145 4     4   11 my ($self) = @_;
146 4         17 "$self->{SPOOLFILE}.lock";
147             }
148              
149             sub _lock_refresh {
150             # This is to update the m time on the .lock mbox lock file.
151             # It may seem paranoid but I have seen lock files removed by impatient
152             # MDA's, so the file is written-to, unbuffered, as often as is
153             # practicable.
154 25     25   40 my $self = shift;
155 25         133 $> = 0;
156 25 50       71 die unless $self->{LOCK_FH};
157 25         79 seek $self->{LOCK_FH}, 0, SEEK_SET;
158 25         82 $self->{LOCK_FH}->print("\0");
159 25         354 $> = $self->{CLIENT_USER_ID};
160             }
161              
162             sub _lock_update {
163 174     174   256 my $self = shift;
164 174 50       424 if (++$self->{LINE} == 1000) {
165 0         0 $self->_lock_refresh;
166 0         0 $self->{LINE} = 0;
167             }
168             }
169              
170             sub _push_message_line {
171 0     0   0 my ($self, $messagecnt, $data) = @_;
172 0         0 push @{ $self->{MSG2TEXT}->{$messagecnt} }, "$data$CRLF";
  0         0  
173             }
174              
175             sub _set_message_line {
176 0     0   0 my ($self, $messagecnt, $lineno, $data) = @_;
177 0         0 $self->{MSG2TEXT}->{$messagecnt}->[$lineno] = $data;
178             }
179              
180             sub _get_message_line {
181 0     0   0 my ($self, $messagecnt, $lineno) = @_;
182 0         0 $self->{MSG2TEXT}->{$messagecnt}->[$lineno];
183             }
184              
185             sub _unshift_message_line {
186 0     0   0 my ($self, $messagecnt, $data) = @_;
187 0         0 unshift @{ $self->{MSG2TEXT}->{$messagecnt} }, "$data$CRLF";
  0         0  
188             }
189              
190             sub _list_message_line {
191 0     0   0 my ($self, $messagecnt) = @_;
192 0         0 @{ $self->{MSG2TEXT}->{$messagecnt} };
  0         0  
193             }
194              
195             sub octets {
196 4     4 1 82 my ($self, $message) = @_;
197 4 100       14 if (defined $message) {
198 2         13 $self->{MSG2OCTETS}->{$message};
199             } else {
200 2         7 $self->{TOTALOCTETS} - $self->{DELTOTALOCTETS};
201             }
202             }
203              
204             sub messages {
205 2     2 1 90 my ($self) = @_;
206 2         6 $self->{MESSAGECNT} - $self->{DELMESSAGECNT};
207             }
208              
209             # Build message arrays or write the next line to disk
210             sub _push_message {
211 0     0   0 my ($self, $messagecnt, $data) = @_;
212 0         0 $self->_push_message_line($messagecnt, $data);
213             }
214              
215             # $message starts at 1
216             # returns number of bytes
217             sub top {
218 0     0 1 0 my ($self, $message, $output_fh, $body_lines) = @_;
219 0         0 my $top_bytes = 0;
220 0         0 my $rows = (scalar $self->_list_message_line($message)) -1;
221 0 0       0 $body_lines = $rows if $body_lines > $rows;
222 0         0 my $cnt = 0;
223 0         0 for my $line ($self->_list_message_line($message)) {
224 0         0 $top_bytes += length($line);
225 0         0 ++$cnt;
226 0         0 $self->_lock_update;
227 0         0 $output_fh->print($line);
228 0 0       0 last if $line =~ /^\s*$/;
229             }
230 0         0 for my $lineno ($cnt..(($cnt + $body_lines) -1)) {
231 0         0 $self->_lock_update;
232 0         0 my $line = $self->_get_message_line($message, $lineno);
233 0         0 $top_bytes += length($line);
234 0         0 $line =~ s/^\./\.\./o;
235 0         0 $output_fh->print($line);
236             }
237 0         0 $top_bytes;
238             }
239              
240             sub flush_delete {
241 2     2 1 5 my ($self) = @_;
242 2         9 my $spool_mtime = (stat $self->_spoolfile)[9];
243 2 50       13 if ($spool_mtime != $self->{MAILBOX_TIMESTAMP_OPEN}) {
244 0         0 die "spool lock error\n";
245             }
246 2         7 my $spoolfile = $self->_spoolfile;
247 2 50       12 open MDROP, '>' . $spoolfile or die "open >$spoolfile: $!\n";
248 2         171 foreach my $cnt (1..$self->{MESSAGECNT}) {
249 6 100       18 if (!$self->is_deleted($cnt)) {
250 4         41 print MDROP "$self->{MSG2FROMLINE}->{$cnt}\n";
251 4         19 $self->retrieve($cnt, \*MDROP, 1);
252             }
253             }
254 2         1739 close MDROP;
255             }
256              
257             sub retrieve {
258 0     0 1 0 my ($self, $message, $output_fh, $mbox_destined) = @_;
259 0         0 for my $line ($self->_list_message_line($message)) {
260 0 0       0 $line =~ s/^\./\.\./o unless $mbox_destined;
261 0 0       0 $line =~ s/\r$// if $mbox_destined;
262 0         0 $self->_lock_update;
263 0         0 $output_fh->print($line);
264             }
265             }
266              
267             sub uidl {
268 1     1 1 3 my ($self, $message) = @_;
269 1         7 $self->{MSG2UIDL}->{$message};
270             }
271              
272             sub uidl_list {
273 4     4 1 751 my ($self, $output_fh) = @_;
274 4         17 for (1..$self->{MESSAGECNT}) {
275 12         75 $self->_lock_refresh;
276 12 100       37 if (!$self->is_deleted($_)) {
277 10         44 $output_fh->print("$_ $self->{MSG2UIDL}->{$_}$CRLF");
278             }
279             }
280 4         45 $output_fh->print(".$CRLF");
281             }
282              
283             sub is_deleted {
284 27     27 1 62 my ($self, $message) = @_;
285 27         107 $self->{DELETE}->{$message};
286             }
287              
288             sub delete {
289 3     3 1 33 my ($self, $message) = @_;
290 3         11 $self->_lock_refresh;
291 3         8 $self->{DELETE}->{$message} = 1;
292 3         7 $self->{DELMESSAGECNT} += 1;
293 3         12 $self->{DELTOTALOCTETS} += $self->{MSG2OCTETS}->{$message};
294             }
295              
296             sub is_valid {
297 7     7 1 26 my ($self, $msg) = @_;
298 7         23 $self->_lock_refresh;
299 7 50 33     60 $msg > 0 and $msg <= $self->{MESSAGECNT} and !$self->is_deleted($msg);
300             }
301              
302             sub reset {
303 1     1 1 4 my $self = shift;
304 1         7 $self->_lock_refresh;
305 1         6 $self->{DELETE} = {};
306 1         5 $self->{DELMESSAGECNT} = 0;
307 1         4 $self->{DELTOTALOCTETS} = 0;
308             }
309              
310             1;
311              
312             __END__