File Coverage

blib/lib/Mail/Header/Generator.pm
Criterion Covered Total %
statement 77 78 98.7
branch 38 46 82.6
condition 12 17 70.5
subroutine 10 10 100.0
pod 5 5 100.0
total 142 156 91.0


line stmt bran cond sub pod time code
1             package Mail::Header::Generator;
2 4     4   319663 use warnings;
  4         12  
  4         204  
3 4     4   24 use strict;
  4         8  
  4         151  
4              
5 4     4   19505 use Time::Local ();
  4         15073  
  4         6985  
6              
7             =head1 NAME
8              
9             Mail::Header::Generator - Generate various email headers.
10              
11             =head1 VERSION
12              
13             Version 0.200
14              
15             =cut
16              
17             our $VERSION = '0.301';
18              
19             =head1 SYNOPSIS
20              
21             use Mail::Header::Generator ();
22              
23             my $gen = Mail::Header::Generator->new({
24             hostname => 'foobar.example.com',
25             });
26              
27             # Generate Message-Id: header contents. $message_id will
28             # contain:
29             # <20100601142143.GA9035@foobar.example.com>
30             # and use the hostname provided to the constructor.
31             my $message_id = $gen->message_id({
32             timestamp => time(),
33             queue_id => 'GA9035',
34             });
35              
36             # Generate a Received: header, including the Received: name.
37             # Will result in something similar to:
38             # Received: from localhost.localdomain ([127.0.0.1] localhost)
39             # by foobar.example.com with ESMTP id o53JaiwX007246; Thu, 03 Jun 2010 15:37:51 -0400
40             my $received = $gen->received({
41             header_name => undef,
42             relay_address => '127.0.0.1',
43             relay_hostname => 'localhost',
44             helo => 'localhost.localdomain',
45             protocol => 'ESMTP',
46             queue_id => 'o53JaiwX007246',
47             });
48              
49             =head1 DESCRIPTION
50              
51             This class generates RFC 5321/5322-compliant headers for use in
52             email messages.
53              
54             =head1 METHODS
55              
56             =head2 Class Methods
57              
58             =over 4
59              
60             =item new ( { args } )
61              
62             Constructs a new Mail::Header::Generator.
63              
64             Takes a hash reference containing default arguments to be used for the various
65             header-generation callbacks.
66              
67             Commonly useful keys for default arguments are:
68              
69             =over 4
70              
71             =item timestamp
72              
73             A UNIX timestamp (from time()).
74              
75             =item hostname
76              
77             The local host name.
78              
79             =item queue_id
80              
81             The queue ID for the message you're generating these headers for.
82              
83             =back
84              
85             =cut
86              
87             sub new
88             {
89 3     3 1 2210 my ($class, $args) = @_;
90              
91             # Yeah, yeah.... Should be using a real base class
92 3 50       8 my $self = { %{ $args || {} } };
  3         25  
93              
94 3         20 return bless $self, $class;
95             }
96              
97             # Return instance data overridden by args, if we're called on an instance.
98             # Return args if we're called on the class
99             sub _header_data
100             {
101 15     15   54 my ($referent, $args) = @_;
102 15   50     43 $args ||= {};
103              
104 15 100       46 if( ref $referent ) {
105 9         62 return (%$referent, %$args)
106             }
107              
108 6         28 return %$args;
109             }
110              
111             =pod
112              
113             =back
114              
115             =head2 Common Parameters
116              
117             The following methods all take named parameters (as a hash reference). The
118             following parameters are common:
119              
120             =over 8
121              
122             =item header_name
123              
124             The name of this header. If this key exists and has a value of undef, the
125             contents of the generated header will be returned with no name prefixed. If
126             the key exists, and has a value, that value will be used as the name. If the
127             key does not exist, the header will default to the same name as the method used
128             to create it.
129              
130             =item timestamp
131              
132             UNIX timestamp (as generated by time()) for this header. If not provided,
133             defaults to the current value of time().
134              
135             =item hostname
136              
137             The local host name.
138              
139             =back
140              
141             =head2 Instance Methods
142              
143             =over 4
144              
145             =item message_id ( ... )
146              
147             Returns a string containing a Message-ID header.
148              
149             Takes a hash reference containing values to use in generating this header.
150             Valid keys are:
151              
152             =over 4
153              
154             =item header_name
155              
156             Defaults to 'Message-ID' if not provided.
157              
158             =item timestamp
159              
160             =item hostname
161              
162             =back
163              
164             See top of this section for further details on these parameters.
165              
166             =cut
167              
168             sub message_id
169             {
170 3     3 1 2514 my ($referent, $args) = @_;
171              
172             # $args can override values from $referent
173 3         12 my %data = $referent->_header_data( $args );
174              
175 3   66     15 $data{timestamp} ||= time();
176 3   100     12 $data{hostname} ||= 'localhost';
177 3 100       9 $data{header_name} = 'Message-ID' unless exists $data{header_name};
178              
179 3         165 my ($ss, $mm, $hh, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($data{timestamp});
180              
181             # Generate a "random" message ID, similar in format to Sendmail-generated IDs
182 3 100       135 return sprintf('%s<%04d%02d%02d%02d%02d.%s@%s>',
    100          
183             ($data{header_name} ? "$data{header_name}: " : ''),
184             $year + 1900,
185             $mon + 1,
186             $mday,
187             $hh,
188             $mm,
189             ($data{queue_id} ? $data{queue_id} : sprintf('%06d',int(rand(1000000)))),
190             $data{hostname},
191             );
192             }
193              
194             =item received ( ... )
195              
196             Returns a string containing a Received header.
197              
198             Takes a hash reference containing values to use in generating this header.
199             Valid keys are:
200              
201             =over 4
202              
203             =item header_name
204              
205             Defaults to 'Received' if not provided.
206              
207             =item timestamp
208              
209             =item hostname
210              
211             =item product_name
212              
213             String containing name of product responsible for adding this header. Will be
214             added as a comment in the generated header if present.
215              
216             =item protocol
217              
218             Protocol this message was received over. Should be "SMTP" or "ESMTP" for
219             standards compliance. Will not be used in the generated header if not
220             provided.
221              
222             =item recipients
223              
224             An array reference of recipient addresses. Currently, a 'for' tag will be created as
225             part of the header only if the recipients array contains a single address.
226              
227             =item relay_address
228              
229             String representation of IPv4 or IPv6 address that relayed this message to us.
230             If not present, no 'from' section of the header will be created.
231              
232             =item relay_hostname
233              
234             Hostname of relay address.
235              
236             =item sender
237              
238             The envelope sender address. If present, an 'envelope-sender' comment will be
239             added to the generated header.
240              
241             =item user
242              
243             Local user that generated this message. Defaults to $ENV{USER} if not present
244             and no relay address was provided.
245              
246             =back
247              
248             See top of this section for further details on these parameters.
249              
250              
251             =cut
252              
253             sub received
254             {
255 4     4 1 13186 my ($referent, $args) = @_;
256              
257             # $args can override values from $referent
258 4         17 my %data = $referent->_header_data( $args );
259              
260 4 50       23 $data{user} = $ENV{USER} unless exists $data{user};
261 4 50       14 $data{user} = 'unknown' unless $data{user};
262 4   100     23 $data{protocol} ||= '';
263 4   66     14 $data{timestamp} ||= time();
264 4 100       16 $data{recipients} = [] unless exists $data{recipients};
265 4 100       23 $data{header_name} = 'Received' unless exists $data{header_name};
266              
267 4         8 my $header = '';
268 4 100       16 $header .= "$data{header_name}: " if defined $data{header_name};
269              
270             # TODO: should escape our data for anything that could cause a
271             # problem in a () comment.
272              
273             # Add relay address, if we have one
274 4 100       11 if($data{relay_address}) {
275 1         2 $header .= 'from';
276             # TODO: helo needs cleansing, blow away non-ASCII-printable?
277 1 50       6 $header .= " $data{helo}" if exists $data{helo};
278 1         21 my $relay_info = "[$data{relay_address}]";
279 1 50       6 if($data{relay_hostname} ne $relay_info) {
280 1         4 $relay_info = "$data{relay_hostname} $relay_info";
281             }
282 1         3 $header .= " ($relay_info)";
283             } else {
284 3         10 $header .= "(from $data{user}\@localhost)";
285             }
286              
287 4 100       10 if($data{hostname}) {
288 1         3 $header .= "\n\tby " . $data{hostname};
289 1 50 33     17 if($data{protocol} =~ /e?smtp/i && $data{sender}) {
290             # TODO: sanitize envelope sender?
291 1         4 $header .= " (envelope-sender <$data{sender}>)";
292             }
293             }
294              
295 4 50       11 if($data{product_name}) {
296 0         0 $header .= " ($data{product_name})";
297             }
298              
299 4 100       16 if($data{protocol} =~ /e?smtp/i) {
300 1         4 $header .= ' with ' . uc($data{protocol});
301             }
302              
303 4 100       11 if( $data{queue_id} ) {
304 1         2 $header .= ' id ' . $data{queue_id};
305             }
306              
307             # If more than one recipient, don't specify to protect privacy
308 4 100       6 if(scalar @{ $data{recipients} } == 1) {
  4         13  
309 1         3 $header .= "\n\tfor <" . $data{recipients}->[0] . '>';
310             }
311              
312 4         23 $header .= '; '
313             . $referent->rfc2822_date({
314             header_name => undef,
315             timestamp => $data{timestamp}
316             });
317              
318 4         2427 return $header;
319             }
320              
321             =item rfc2822_date ( ... )
322              
323             Returns a string containing a RFC 2822 formatted date.
324              
325             Takes a hash reference containing values to use in generating this header.
326             Valid keys are:
327              
328             =over 4
329              
330             =item header_name
331              
332             No default if not provided.
333              
334             =item timestamp
335              
336             UNIX timestamp. Defaults to current value of time() if not provided.
337              
338             =back
339              
340             =cut
341              
342             # _tz_diff and rfc2822_date borrowed from Email::Date. Why?
343             # Because they depend on Date::Parse and Time::Piece, and I don't want
344             # to add them as dependencies.
345             # Similar functions exist in MIMEDefang as well
346             sub _tz_diff
347             {
348 8     8   13 my ($time) = @_;
349              
350 8         42 my $diff = Time::Local::timegm(localtime $time) - Time::Local::timegm(gmtime $time);
351              
352 8 50       417 my $direc = $diff < 0 ? '-' : '+';
353 8         12 $diff = abs $diff;
354 8         18 my $tz_hr = int($diff / 3600);
355 8         14 my $tz_mi = int($diff / 60 - $tz_hr * 60);
356              
357 8         23 return ($direc, $tz_hr, $tz_mi);
358             }
359              
360             =item date ( ... )
361              
362             Returns a date header containing a RFC 2822 formatted date. This is a
363             convenience wrapper around rfc2822_date().
364              
365             Takes a hash reference containing values to use in generating this header.
366             Valid keys are:
367              
368             =over 4
369              
370             =item header_name
371              
372             Defaults to 'Date' if not provided.
373              
374             =item timestamp
375              
376             UNIX timestamp. Defaults to current value of time() if not provided.
377              
378             =back
379              
380             =cut
381              
382             sub date
383             {
384 4     4 1 2232 my ($referent, $args) = @_;
385              
386 4   100     17 $args ||= {};
387              
388 4 100       17 $args->{header_name} = 'Date' unless exists $args->{header_name};
389              
390 4         13 return $referent->rfc2822_date($args);
391             }
392              
393             sub rfc2822_date
394             {
395 8     8 1 144 my ($referent, $args) = @_;
396              
397             # $args can override values from $referent
398 8         21 my %data = $referent->_header_data( $args );
399              
400 8 100       30 $data{timestamp} = time unless exists $data{timestamp};
401              
402 8         367 my ($sec, $min, $hour, $mday, $mon, $year, $wday) = localtime $data{timestamp};
403 8         19 my $day = (qw[Sun Mon Tue Wed Thu Fri Sat])[$wday];
404 8         15 my $month = (qw[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec])[$mon];
405 8         14 $year += 1900;
406              
407 8         21 my ($direc, $tz_hr, $tz_mi) = _tz_diff($data{timestamp});
408              
409             return
410 8 100       148 sprintf "%s%s, %d %s %d %02d:%02d:%02d %s%02d%02d",
411             (defined $data{header_name} ? "$data{header_name}: " : ''),
412             $day, $mday, $month, $year, $hour, $min, $sec, $direc, $tz_hr, $tz_mi;
413             }
414              
415             =pod
416              
417             =back
418              
419             =head1 AUTHOR
420              
421             Dave O'Neill,
422              
423             =head1 BUGS
424              
425             Please report any bugs or feature requests to C, or through
426             the web interface at L. I will be notified, and then you'll
427             automatically be notified of progress on your bug as I make changes.
428              
429             =head1 SUPPORT
430              
431             You can find documentation for this module with the perldoc command.
432              
433             perldoc Mail::Header::Generator
434              
435              
436             You can also look for information at:
437              
438             =over 4
439              
440             =item * RT: CPAN's request tracker
441              
442             L
443              
444             =item * Search CPAN
445              
446             L
447              
448             =back
449              
450             =head1 ACKNOWLEDGEMENTS
451              
452             received() and message_id() were originally implemented in MIMEDefang.
453              
454             rfc2822_date() implementation based on one from Email::Date.
455              
456             =head1 LICENSE AND COPYRIGHT
457              
458             Copyright 2010 Roaring Penguin Software
459              
460             This program is free software; you can redistribute it and/or modify
461             it under the terms of the GNU General Public License as published by
462             the Free Software Foundation; version 2 dated June, 1991 or at your option
463             any later version.
464              
465             This program is distributed in the hope that it will be useful,
466             but WITHOUT ANY WARRANTY; without even the implied warranty of
467             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
468             GNU General Public License for more details.
469              
470             A copy of the GNU General Public License is available in the source tree;
471             if not, write to the Free Software Foundation, Inc.,
472             59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
473              
474             =cut
475              
476             1; # End of Mail::Header::Generator