File Coverage

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