File Coverage

blib/lib/Mail/SimpleList.pm
Criterion Covered Total %
statement 153 153 100.0
branch 27 28 96.4
condition 15 19 78.9
subroutine 25 25 100.0
pod 13 18 72.2
total 233 243 95.8


line stmt bran cond sub pod time code
1             package Mail::SimpleList;
2              
3 2     2   3234 use strict;
  2         4  
  2         145  
4             my $pod = do { local $/; };
5              
6 2     2   9 use base 'Mail::Action';
  2         4  
  2         4529  
7 2     2   133933 use Carp 'croak';
  2         6  
  2         116  
8              
9 2     2   11 use Mail::Mailer;
  2         4  
  2         41  
10 2     2   11 use Email::Address;
  2         2  
  2         48  
11              
12 2     2   9 use vars '$VERSION';
  2         3  
  2         84  
13             $VERSION = '0.93';
14              
15 2     2   1879 use Mail::SimpleList::Aliases;
  2         7  
  2         3841  
16              
17             sub storage_class
18             {
19 22     22 0 239014 'Mail::SimpleList::Aliases'
20             }
21              
22             sub parse_alias
23             {
24 9     9 1 97 my ($self, $address) = @_;
25 9         41 my ($add) = Email::Address->parse( $address );
26 9         620 my $user = $add->user();
27 9         133 my $expansion_pattern = $self->expansion_pattern();
28              
29 9 50       117 return ( $user =~ $expansion_pattern ) ? $1 : '';
30             }
31              
32             sub expansion_pattern
33             {
34 9     9 1 51 return qr/\+([^+]+)$/;
35             }
36              
37             sub command_help
38             {
39 1     1 1 3 my $self = shift;
40 1         8 $self->SUPER::command_help( $pod, 'USING LISTS', 'DIRECTIVES' );
41             }
42              
43             sub command_new
44             {
45 8     8 0 98 my $self = shift;
46 8         33 my $from = $self->address_field( 'From' );
47 8         73 my $addresses = $self->storage();
48 8         69 my $alias = $addresses->create( $from );
49 8         41 my $users = $self->process_body( $alias );
50 8         747 my $id = $self->generate_alias( $alias->name() );
51 8         32 my $post = $self->post_address( $id );
52              
53 8         39 $self->add_to_alias( $alias, $post, @$users );
54 8         279 $addresses->save( $alias, $id );
55              
56 8         38050 $self->reply({ To => $from, Subject => "Created list $id" },
57             "Mailing list created. Post to $post." );
58              
59 8         635 return $alias;
60             }
61              
62             sub command_clone
63             {
64 2     2 0 101 my $self = shift;
65              
66 2         11 my $from = $self->address_field( 'From' );
67 2         18 my $request = $self->request();
68 2         15 (my $subject = $request->header( 'Subject' )) =~ s/^\*clone\*\s+//;
69 2         35 my ($alias_id) = $self->parse_alias( $subject );
70 2         11 my $addresses = $self->storage();
71 2         24 my $parent = $addresses->fetch( $alias_id );
72 2         89 my $alias = $addresses->create( $from );
73 2         71 my $users = $self->process_body( $alias );
74 2   66     200 my $wanted_id = $alias->name() || $alias_id;
75 2         82 my $id = $self->generate_alias( $wanted_id );
76 2         10 my $post = $self->post_address( $id );
77              
78 2         18 $self->add_to_alias( $alias, $post, @{ $parent->members() }, @$users );
  2         14  
79              
80 2         210 $addresses->save( $alias, $id );
81              
82 2         2929 $self->reply({ To => $from, Subject => "Cloned alias $alias_id => $id" },
83             "Mailing list created. Post to $post." );
84              
85 2         268 return $alias;
86             }
87              
88             sub address_field
89             {
90 21     21 0 64 my ($self, $field) = @_;
91              
92 21         65 my @values = $self->request->header( $field );
93 21 100       363 return wantarray ? @values : $values[0]->address();
94             }
95              
96             sub generate_alias
97             {
98 12     12 1 3136 my ($self, $id) = @_;
99 12         46 my $addresses = $self->storage();
100              
101 12   66     142 $id ||= sprintf '%x', reverse scalar time;
102              
103 12         77 while ($addresses->exists( $id ))
104             {
105 7         413 $id = sprintf '%x', ( reverse ( time() + rand($$) ));
106             }
107              
108 12         231 return $id;
109             }
110              
111             sub post_address
112             {
113 11     11 1 1268 my ($self, $id) = @_;
114 11         40 my ($address) = $self->address_field( 'To' );
115              
116             # if this is a *new* request, there's no To field anymore
117 11   66     69 $address ||= $self->request->recipient();
118 11         331 my $host = $address->host();
119 11         341 (my $base = $address->user()) =~ s/\+([^+]+)$//;
120              
121 11         716 return "$base+$id\@$host";
122             }
123              
124             sub reply
125             {
126 31     31 1 60477 my ($self, $headers) = splice( @_, 0, 2 );
127 31         93 $headers->{'X-MSL-Seen'} = '1';
128 31         168 $self->SUPER::reply( $headers, @_ );
129             }
130              
131             sub command_unsubscribe
132             {
133 3     3 0 13796 my $self = shift;
134 3         27 my ($alias, $id) = $self->fetch_address();
135 3         187 my $from = $self->request->header( 'From' )->address();
136              
137 3 100 66     286 $self->reply({ To => $from, Subject => "Remove from $alias" },
138             ($alias->remove_address( $from ) and
139             $self->storage->save( $alias, $id )) ?
140             "Unsubscribed $from successfully." :
141             "Unsubscribe unsuccessful for $from. Check the address."
142             );
143             }
144              
145             sub process
146             {
147 21     21 1 10877 my $self = shift;
148              
149 21 100       114 return if $self->request->header('X-MSL-Seen');
150 20         1207 my $command = $self->find_command();
151 20 100       876 return $self->$command() if $command;
152              
153 9         53 my $alias = $self->fetch_address();
154 9 100       398 return $self->deliver( $alias ) if $alias;
155 2         14 $self->reject();
156             }
157              
158             sub deliver
159             {
160 10     10 1 1778 my ($self, $alias) = @_;
161              
162 10         66 my $name = $alias->name();
163 10         388 my $request = $self->request();
164 10         100 my $recipient = $request->recipient();
165 10         105 my $sent_to = $recipient->address();
166 10         98 my $host = $recipient->host();
167 10         407 my $message = $request->copy_headers();
168 10         2201 $message->{To} = $sent_to;
169              
170 10 100       47 unless ($self->can_deliver( $alias, $message ))
171             {
172 3         794 my $body = delete $message->{Body};
173 3         11 $message->{To} = delete $message->{From};
174 3         17 $self->reply( $message, $body );
175 3         302 return;
176             }
177              
178 7   100     104 my $desc = $alias->description() || '';
179              
180 7 100       279 if ( $alias->auto_add() )
181             {
182 4         77 my @to_friends = map { $_->address() } $request->header( 'To' );
  2         27  
183 4         46 my @cc_friends = map { $_->address() } $request->header( 'Cc' );
  1         13  
184              
185 4         50 $self->add_to_alias( $alias, @to_friends, @cc_friends );
186 4         23 $self->storage->save( $alias, $name );
187             }
188              
189 7         9099 $message->{Bcc} = $alias->members();
190 7 100       235 $message->{'List-Id'} = ( $desc ? qq|"$desc" | : '') .
191             "<$name.list-id.$host>";
192 7         23 $message->{'Reply-To'} = $sent_to;
193 7         20 delete $message->{'Delivered-to'};
194              
195 7         36 my $body = $self->add_signature( "\n-- \nTo unsubscribe:" .
196             qq| reply to this sender alone with "*UNSUBSCRIBE*" in the subject.\n|
197             );
198              
199 7         110 $self->reply( $message, $body );
200             }
201              
202             sub add_signature
203             {
204 7     7 1 17 my ($self, $sig) = @_;
205 7         28 my $request = $self->request();
206 7         54 my @parts = $request->message->parts();
207              
208 7 100       133 if (@parts == 1)
209             {
210 6         20 $request->message->body_set( $request->message->body() . $sig );
211             }
212             else
213             {
214 1         10 my $sig_part = Email::MIME->new( '' );
215              
216 1         458 $sig_part->content_type_set( 'text/plain' );
217 1         359 $sig_part->encoding_set( '7bit' );
218 1         124 $sig_part->disposition_set( 'attachment' );
219 1         59 $sig_part->body_set( $sig );
220              
221 1         50 push @parts, $sig_part;
222 1         4 $request->message->parts_set( \@parts );
223             }
224              
225 7         3789 return $request->message->body_raw();
226             }
227              
228             sub reject
229             {
230 2   100 2 1 5902 my $reason = $_[1] || "Invalid alias\n";
231 2         6 $! = 100;
232 2         19 die $reason;
233             }
234              
235             sub notify
236             {
237 6     6 1 6790 my ($self, $alias, $id) = splice( @_, 0, 3 );
238              
239 6         44 my $owner = $alias->owner();
240 6         105 my $desc = $alias->description();
241              
242 6         114 for my $address ( @_ )
243             {
244 9         372 $self->reply({
245             From => $owner,
246             To => $address,
247             'Reply-To' => $id,
248             Subject => "Added to alias $id",
249             }, "You have been subscribed to alias $id by $owner.\n\n", $desc );
250             }
251             }
252              
253             sub can_deliver
254             {
255 12     12 1 2061 my ($self, $alias, $message) = @_;
256 12 100 100     73 if ( $alias->closed() and not
  6         149  
257 3         145 grep { $_ eq $message->{From} } @{ $alias->members() })
258             {
259 2         8 $message->{To} = $message->{From};
260 2         6 $message->{Subject} = 'Alias closed';
261 2         7 $message->{Body} = 'This alias is closed to non-members.';
262 2         10 return;
263             }
264 10 100       362 return 1 unless my $expires = $alias->expires();
265 3 100       150 if ($expires < time())
266             {
267 2         8 $message->{To} = $message->{From};
268 2         6 $message->{Subject} = 'Alias expired';
269 2         7 $message->{Body} = 'This alias has expired.';
270 2         11 return;
271             }
272 1         7 return 1;
273             }
274              
275             sub add_to_alias
276             {
277 14     14 1 4583 my ($self, $alias, $id, @addresses) = @_;
278 14 100       81 my @added = $alias->add( @addresses ) or return;
279 7         48 $self->notify( $alias, $id, @added );
280             }
281              
282             1;
283             __DATA__