File Coverage

blib/lib/Mail/SimpleList.pm
Criterion Covered Total %
statement 155 155 100.0
branch 27 28 96.4
condition 15 19 78.9
subroutine 27 27 100.0
pod 13 18 72.2
total 237 247 95.9


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