File Coverage

blib/lib/App/MonM/Message.pm
Criterion Covered Total %
statement 36 161 22.3
branch 0 38 0.0
condition 0 41 0.0
subroutine 12 25 48.0
pod 12 12 100.0
total 60 277 21.6


line stmt bran cond sub pod time code
1             package App::MonM::Message;
2 1     1   6 use warnings;
  1         23  
  1         29  
3 1     1   4 use strict;
  1         2  
  1         14  
4 1     1   3 use utf8;
  1         2  
  1         3  
5              
6             =encoding utf-8
7              
8             =head1 NAME
9              
10             App::MonM::Message - The MonM Message manager
11              
12             =head1 VERSION
13              
14             Version 1.00
15              
16             =head1 SYNOPSIS
17              
18             use App::MonM::Message;
19              
20             my $message = App::MonM::Message->new(
21             recipient => "myaccount",
22             to => 'to@example.com',
23             from => 'from@example.com',
24             subject => "Test message",
25             body => "Body of test message",
26             );
27              
28             =head1 DESCRIPTION
29              
30             This is an extension for the monm messages
31              
32             =head2 new
33              
34             my $message = App::MonM::Message->new(
35             recipient => "myaccount",
36             to => 'to@example.com',
37             cc => 'cc@example.com',
38             bcc => 'bcc@example.com',
39             from => 'from@example.com',
40             subject => "Test message",
41             body => "Body of test message",
42             headers => { # optional
43             "X-My-Header" => "test",
44             },
45             contenttype => "text/plain", # optional
46             charset => "utf-8", # optional
47             encoding => "8bit", # optional
48             attachment => [{ # See Email::MIME
49             filename => "screenshot.png",
50             type => "image/png",
51             encoding => "base64",
52             disposition => "attachment",
53             path => "/tmp/screenshot.png",
54             }],
55             );
56              
57             Create new message
58              
59             my $message = App::MonM::Message->new;
60             $message->load("test.msg") or die $message->error;
61              
62             Load message from file
63              
64             =head2 body
65              
66             Returns body of message
67              
68             =head2 email
69              
70             my $email_object = $message->email;
71              
72             Returns L object
73              
74             $message->email($email_object);
75              
76             Sets L object
77              
78             =head2 error
79              
80             my $error = $message->error;
81              
82             Returns error string
83              
84             $message->error( "error text" );
85              
86             Sets error string
87              
88             =head2 from
89              
90             Returns the "From" header
91              
92             =head2 genId
93              
94             my $message_id = $message->genId('to@example.com',"Test message");
95              
96             Generate new ID of message
97              
98             =head2 load
99              
100             my $message = App::MonM::Message->new;
101             $message->load("test.msg") or die $message->error;
102              
103             Load message from file
104              
105             =head2 msgid
106              
107             my $MessageId = $message->msgid;
108              
109             Returns MessageId (X-Message-ID)
110              
111             =head2 recipient
112              
113             my $recipient = $message->recipient;
114              
115             Returns recipient
116              
117             =head2 save
118              
119             $message->save("test.msg") or die $message->error;
120              
121             Save message to file
122              
123             =head2 subject
124              
125             Returns the Subject of message
126              
127             =head2 to
128              
129             Returns the "To" header
130              
131             =head1 HISTORY
132              
133             See C file
134              
135             =head1 TO DO
136              
137             See C file
138              
139             =head1 SEE ALSO
140              
141             L
142              
143             =head1 AUTHOR
144              
145             Serż Minus (Sergey Lepenkov) L Eabalama@cpan.orgE
146              
147             =head1 COPYRIGHT
148              
149             Copyright (C) 1998-2022 D&D Corporation. All Rights Reserved
150              
151             =head1 LICENSE
152              
153             This program is free software; you can redistribute it and/or
154             modify it under the same terms as Perl itself.
155              
156             See C file and L
157              
158             =cut
159              
160 1     1   34 use vars qw/$VERSION/;
  1         1  
  1         47  
161             $VERSION = '1.00';
162              
163 1     1   439 use Email::MIME;
  1         14440  
  1         27  
164 1     1   5 use IO::File;
  1         2  
  1         106  
165              
166 1     1   331 use CTK::Digest::FNV32a;
  1         46752  
  1         29  
167 1     1   5 use CTK::ConfGenUtil;
  1         1  
  1         64  
168 1     1   5 use CTK::TFVals qw/ :ALL /;
  1         1  
  1         172  
169              
170 1     1   5 use App::MonM::Util qw/header_field_normalize slurp node2anode/;
  1         1  
  1         40  
171 1     1   5 use App::MonM::Const qw/HOSTNAME/;
  1         2  
  1         39  
172              
173             use constant {
174 1         1243 CONTENT_TYPE => "text/plain",
175             CHARSET => "utf-8",
176             ENCODING => "8bit", # "quoted-printable", "8bit", "base64"
177             USERNAME => "anonymous",
178 1     1   4 };
  1         2  
179              
180             *TO_DEFAULT = sub {
181 0     0     return sprintf('%s@%s', USERNAME, HOSTNAME());
182             };
183              
184             my @CHARS = ('a'..'f', 0..9);
185             my %UNIQCNT;
186              
187             sub new {
188 0     0 1   my $class = shift;
189 0           my %args = @_;
190              
191 0           my $self = bless {
192             email => undef, # Email::SMTP object
193             msgid => undef, # X-Message-ID
194             recipient => "",
195             error => "",
196             fnv32a => CTK::Digest::FNV32a->new(),
197             }, $class;
198              
199             # No any data - returns empty object (without email)
200 0 0         return $self unless %args;
201              
202             # Headers
203 0   0       my $headers = $args{headers} || {};
204 0   0       my $to = $args{to} || TO_DEFAULT();
205 0   0       my $recipient = $args{recipient} || $to || USERNAME;
206 0           my $subject = $args{subject};
207 0 0         my %hset = (
208             To => $to =~ /\@/ ? $to : TO_DEFAULT(),
209             Subject => $subject,
210             );
211 0           foreach my $h (qw/from cc bcc/) {
212 0           my $uh = ucfirst($h);
213 0 0 0       $hset{$uh} = $args{$h} if $args{$h} && $args{$h} =~ /\@/;
214             }
215              
216 0 0 0       if ($headers && is_hash($headers) && keys(%$headers)) {
      0        
217 0           while (my ($k,$v) = each %$headers) {
218 0 0         next unless defined $v;
219 0           $hset{header_field_normalize($k)} = $v;
220             }
221             }
222              
223             # Attributes
224 0   0       my $contenttype = $args{contenttype} // CONTENT_TYPE;
225 0   0       my $charset = $args{charset} // CHARSET;
226 0   0       my $encoding = $args{encoding} // ENCODING;
227              
228             # Body content
229 0   0       my $body = $args{body} // '';
230              
231             # Multiparted message
232 0           my @parts;
233 0           my $main_part = Email::MIME->create(
234             attributes => {
235             content_type => $contenttype,
236             charset => $charset,
237             encoding => $encoding,
238             disposition => "inline", #disposition => "attachment",
239             },
240             body_str => $body,
241             );
242 0           push @parts, $main_part;
243              
244             # Attachments
245 0           my $attachments = node2anode($args{attachment});
246 0           foreach my $inatt (@$attachments) {
247 0   0       my $filename = lvalue($inatt, "filename") || lvalue($inatt, "file");
248 0 0         next unless $filename;
249 0           my $path = lvalue($inatt, "path");
250 0 0 0       next unless $path && -e $path;
251 0 0         my $body = slurp($path, 1) or next;
252 0   0       push @parts, Email::MIME->create(
      0        
      0        
      0        
253             attributes => {
254             filename => $filename,
255             name => $filename,
256             content_type => lvalue($inatt, "content_type") || lvalue($inatt, "type") // "application/octet-stream",
257             encoding => lvalue($inatt, "encoding") // "base64",
258             disposition => lvalue($inatt, "disposition") // "attachment",
259             },
260             body => $body,
261             );
262             }
263              
264             # Create message (single or multipart)
265 0           my $email = Email::MIME->create(
266             header_str => [%hset],
267             parts => [ @parts ],
268             );
269              
270             # Add attributes and body for single message
271             #$email->content_type_set($contenttype);
272             #$email->charset_set($charset);
273             #$email->encoding_set($encoding);
274             #$email->body_str_set($body);
275              
276             # Add X-Message-ID
277 0           $self->{msgid} = $self->genId($to, $recipient, $subject);
278 0           $email->header_str_set("X-Message-ID" => $self->{msgid});
279              
280             # Add X-Recipient
281 0           $self->{recipient} = $recipient;
282 0           $email->header_str_set("X-Recipient" => $recipient);
283              
284             # Done
285 0           $self->email($email);
286              
287 0           return $self;
288             }
289              
290             sub email {
291 0     0 1   my $self = shift;
292 0           my $v = shift;
293 0 0         $self->{email} = $v if defined $v;
294 0           return $self->{email};
295             }
296             sub error {
297 0     0 1   my $self = shift;
298 0           my $v = shift;
299 0 0         $self->{error} = $v if defined $v;
300 0           return $self->{error};
301             }
302             sub msgid {
303 0     0 1   my $self = shift;
304 0           return $self->{msgid};
305             }
306             sub genId {
307 0     0 1   my $self = shift;
308 0           my @arr = @_;
309 0           unshift @arr, $$;
310 0           my $text = join("|", @arr);
311 0           my $t = time;
312 0           my $short = $t & 0x7FFFFF;
313 0           my $fnv = $self->{fnv32a}->digest($text) & 0xFFFFFFFF;
314 0           my $salt = join '', map {; $CHARS[rand @CHARS] } (0..6);
  0            
315 0 0         my $u = exists $UNIQCNT{$t} ? ++$UNIQCNT{$t} : (%UNIQCNT = ($t => 0))[1];
316             # hex(SHORT_TIME) . hex(TIME_UNIQ_CNT) . SALT . hex(FNV32a)
317 0           return sprintf("%x%x%s%x",$short, $u, $salt, $fnv);
318             }
319             sub save {
320 0     0 1   my $self = shift;
321 0           my $file = shift;
322 0           $self->error("");
323 0 0         unless ($file) {
324 0           $self->error("No file specified");
325 0           return;
326             }
327 0           my $email = $self->email;
328 0 0         unless ($email) {
329 0           $self->error("No email object found");
330 0           return;
331             }
332              
333 0           my $fh = IO::File->new($file, "w");
334 0 0         unless (defined $fh) {
335 0           $self->error("Can't write file $file: $!");
336 0           return;
337             }
338              
339 0           $fh->binmode(); # ':raw:utf8'
340 0           $fh->print($email->as_string);
341 0           undef $fh;
342 0           return 1;
343             }
344             sub load {
345 0     0 1   my $self = shift;
346 0           my $file = shift;
347 0           $self->error("");
348 0 0         unless ($file) {
349 0           $self->error("No file specified");
350 0           return;
351             }
352 0 0         unless (-e $file) {
353 0           $self->error("No file found: $file");
354 0           return;
355             }
356 0           my $size = -s $file;
357 0 0         unless ($size) {
358 0           $self->error("The file is empty: $file");
359 0           return;
360             }
361              
362             # Load file
363 0           my $fh = IO::File->new($file, "r");
364 0 0         unless (defined $fh) {
365 0           $self->error("Can't load file $file: $!");
366 0           return;
367             }
368              
369 0           $fh->binmode(':raw:utf8');
370 0           my $buf;
371 0           read $fh, $buf, $size; # File::Slurp in a nutshell
372 0           undef $fh;
373              
374             # Set email object
375 0           my $email = Email::MIME->new($buf);
376 0           $self->email($email);
377 0           my $to = $email->header("To");
378              
379             # Add X-Recipient
380 0   0       my $recipient = $email->header("X-Recipient") || $to || USERNAME;
381 0           $self->{recipient} = $recipient;
382              
383             # Add X-Message-ID
384 0           my $msgid = $email->header("X-Message-ID");
385 0 0         unless ($msgid) {
386 0           my $subject = $email->header("Subject");
387 0           $msgid = $self->genId($to, $subject);
388 0           $email->header_str_set("X-Message-ID" => $msgid);
389             }
390 0           $self->{msgid} = $msgid;
391              
392 0           return $self;
393             }
394             sub recipient {
395 0     0 1   my $self = shift;
396 0           return $self->{recipient};
397             }
398             sub to {
399 0     0 1   my $self = shift;
400 0           my $val = $self->email->header("To");
401 0           return $val;
402             }
403             sub from {
404 0     0 1   my $self = shift;
405 0           my $val = $self->email->header("From");
406 0           return $val;
407             }
408             sub subject {
409 0     0 1   my $self = shift;
410 0           my $val = $self->email->header("Subject");
411 0           return $val;
412             }
413             sub body {
414 0     0 1   my $self = shift;
415 0           my $val = $self->email->body;
416 0           return $val;
417             }
418              
419             1;
420              
421             __END__