File Coverage

blib/lib/Postfix/ContentFilter.pm
Criterion Covered Total %
statement 15 80 18.7
branch 0 38 0.0
condition 0 21 0.0
subroutine 5 13 38.4
pod 3 3 100.0
total 23 155 14.8


line stmt bran cond sub pod time code
1             package Postfix::ContentFilter;
2              
3 3     3   189302 use Modern::Perl;
  3         43405  
  3         26  
4 3     3   620 use Carp;
  3         10  
  3         394  
5 3     3   2216 use Try::Tiny 0.11;
  3         1712  
  3         212  
6 3     3   3541 use IPC::Open3 1.03;
  3         10431  
  3         206  
7 3     3   27 use Scalar::Util qw(blessed);
  3         6  
  3         6344  
8              
9             =head1 NAME
10              
11             Postfix::ContentFilter - a perl content_filter for postfix
12              
13             =head1 VERSION
14              
15             Version 1.11
16              
17             =cut
18              
19             our $VERSION = '1.11';
20              
21             =head1 SYNOPSIS
22              
23             use Postfix::ContentFilter;
24              
25             $exitcode = Postfix::ContentFilter->process(sub{
26             $entity = shift; # isa MIME::Entity
27            
28             # do something with $entity
29            
30             return $entity;
31             });
32            
33             # Or specifying the parser
34             my $cf = Postfix::ContentFilter->new({ parser => 'Mail::Message' });
35              
36             $exitcode = $cf->process(sub{
37             $entity = shift; # isa Mail::Message
38            
39             # do something with $entity
40            
41             return $entity;
42             });
43              
44             exit $exitcode;
45              
46             =head1 DESCRIPTION
47              
48             Postfix::ContentFilter can be used for C scripts, as described here: L.
49              
50             =cut
51              
52             our $parser;
53             our $sendmail = [qw[ /usr/sbin/sendmail -G -i ]];
54             our $output;
55             our $error;
56              
57             =head1 FUNCTIONS
58              
59             =head2 new($args)
60             C creates a new Postfix::Contentfilter. It takes an optional argument of a hash with the key 'parser', which specifies the parser to use as per C
. This can be either C or C.
61              
62             Alternatively C can be called directly.
63              
64             =cut
65              
66             sub new($%)
67 0     0 1   { my ($class, $options) = @_;
68 0           my $self = bless {}, $class;
69 0 0 0       if ($options && $options->{parser})
70             {
71 0           parser($self, $options->{parser});
72             }
73              
74 0           $self;
75             }
76              
77             =head2 parser($string)
78              
79             C specifies the parser to use, which can be either C or C. It defaults to C, if available, or C whichever could be found first. When called without any arguments, it returns the current parser.
80              
81             =cut
82              
83             sub _load_any {
84 0     0     foreach my $module (@_) {
85 0           my $path = $module;
86 0           $path =~ s/::/\//g;
87 0           $path .= '.pm';
88 0 0         return $module if exists $INC{$path};
89 0 0         eval "require $module; 1" and return $module;
90             }
91 0           croak("Couldn't find any of these implementations: @_");
92             }
93              
94             sub parser {
95 0     0 1   my ($self, $ptype) = @_;
96 0           my $parsers = {
97             # Key is parser, value is returned entity
98             'MIME::Parser' => 'MIME::Entity',
99             'Mail::Message' => 'Mail::Message',
100             };
101            
102 0 0 0       return $self->{parser} if defined $self->{parser} and not defined $ptype;
103              
104 0   0       $ptype = _load_any($ptype || qw(MIME::Parser Mail::Message));
105            
106 0 0         if (my $ent = $parsers->{$ptype}) {
107 0           $self->{parser} = $ptype;
108 0           $self->{entity} = $ent;
109             } else {
110 0           croak "Unknown parser $ptype";
111             }
112            
113 0           return $self->{parser};
114             }
115              
116             sub _parse {
117 0     0     my ($self, $handle) = @_;
118             }
119              
120             =head2 process($coderef [, $inputhandle])
121              
122             C reads the mail from C (or C<$inputhandle>, if given), parses it, calls the coderef and finally runs C with our own command-line arguments (C<@ARGV>).
123              
124             This function returns the exitcode of C.
125              
126             =cut
127              
128             sub process($&;*) {
129 0     0 1   my ($class, $coderef, $handle) = @_;
130            
131 0 0         my $self = blessed $class
132             ? $class
133             : bless {}, $class
134             ; # For backwards compatibility, to enable calling directly
135              
136 0 0         confess "please call as ".__PACKAGE__."->process(sub{ ... })" unless ref $coderef eq 'CODE';
137            
138 0 0         $handle = \*STDIN unless ref $handle eq 'GLOB';
139              
140 0           my $entity;
141 0           my $parser = $self->parser;
142            
143 0   0       given (ref $parser || $parser) {
144 0           when ('Mail::Message') {
145 0 0         $entity = $parser->read($handle) or confess "failed to parse with Mail::Message";
146             }
147 0           when ('MIME::Parser') {
148 0           $parser = $parser->new;
149 0 0         $entity = $parser->parse($handle) or confess "failed to parse wth MIME::Parser";
150             }
151 0           default {
152 0           confess "Unkown parser $parser";
153             }
154             }
155            
156             try {
157 0     0     $entity = $coderef->($entity);
158             } catch {
159 0   0 0     given (ref $parser || $parser) {
160 0           when ('Mail::Message') {
161 0           $entity->DESTROY;
162             }
163 0           when ('MIME::Parser') {
164 0           $parser->filer->purge;
165             }
166             }
167 0           confess $_;
168 0           };
169            
170 0 0 0       confess "subref should return instance of $self->{entity}"
171             unless blessed($entity) and $entity->isa($self->{entity});
172              
173 0           my $ret = -1;
174            
175 0 0   0     $SIG{CHLD} = sub { wait; $ret = $? if $? >= 0 };
  0            
  0            
176            
177 0 0         delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV', 'PATH'} if ${^TAINT};
178            
179 0           my ($in, $out, $err);
180 0 0         my $pid = open3 ($in, $out, $err, @$sendmail, @ARGV) or confess "open3: $!";
181            
182 0 0         $entity->print($in) or confess "print: $!";
183              
184 0           close $in;
185            
186 0 0         $output = join '' => <$out> if defined $out;
187 0 0         $error = join '' => <$err> if defined $err;
188            
189 0           close $out;
190            
191 0           waitpid($pid, 0);
192 0 0         $ret = $? if $? >= 0;
193            
194 0   0       given (ref $parser || $parser) {
195 0           when ('Mail::Message') {
196 0           $entity->DESTROY;
197             }
198 0           when ('MIME::Parser') {
199 0           $parser->filer->purge;
200             }
201             }
202            
203 0 0         return $ret == 0 ? 1 : 0;
204             }
205              
206             =head1 VARIABLES
207              
208             =over 4
209              
210             =item * C<$sendmail>
211              
212             C<$sendmail> defaults to C.
213              
214             $Postfix::ContentFilter::sendmail = [ '/usr/local/sbin/sendmail', '-G', '-i' ];
215              
216             Please note C<$sendmail> must be an arrayref. Don't forget to use the proper arguments for C, or just replace the first element in array.
217              
218             Additional arguments can be added with:
219              
220             push @$Postfix::ContentFilter::sendmail => '-t';
221              
222             =item * C<$output>
223              
224             Any output from C command is populated in C<$output>.
225              
226             =item * C<$parser>
227              
228             The L object is available via C<$parser>. To tell where to put the things, use:
229              
230             $Postfix::ContentFilter::parser->output_under('/tmp');
231              
232             =back
233              
234             =head1 CAVEATS
235              
236             If taint mode is on, %ENV will be stripped:
237              
238             delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV', 'PATH'}
239              
240             So set C<$Postfix::ContentFilter::sendmail> to an absolute path, if you are using taint mode. See L for more details about unsafe variables and tainted input.
241              
242             =head1 SEE ALSO
243              
244             =over 4
245              
246             =item * L
247              
248             =item * L
249              
250             =item * L
251              
252             =back
253              
254             =head1 AUTHOR
255              
256             David Zurborg, C<< >>
257              
258             =head1 BUGS
259              
260             Please report any bugs or feature requests trough L. I will be notified, and then you'll
261             automatically be notified of progress on your bug as I make changes.
262              
263             =head1 SUPPORT
264              
265             You can find documentation for this module with the perldoc command.
266              
267             perldoc Postfix::ContentFilter
268              
269             You can also look for information at:
270              
271             =over 4
272              
273             =item * Redmine: Homepage of this module
274              
275             L
276              
277             =item * RT: CPAN's request tracker
278              
279             L
280              
281             =item * AnnoCPAN: Annotated CPAN documentation
282              
283             L
284              
285             =item * CPAN Ratings
286              
287             L
288              
289             =item * Search CPAN
290              
291             L
292              
293             =back
294              
295             =head1 COPYRIGHT & LICENSE
296              
297             Copyright 2014 David Zurborg, all rights reserved.
298              
299             This program is free software; you can redistribute it and/or modify it under the terms of the ISC license.
300              
301             =cut
302              
303             1; # End of Postfix::ContentFilter