File Coverage

blib/lib/POE/Component/Server/POP3.pm
Criterion Covered Total %
statement 136 197 69.0
branch 30 76 39.4
condition 5 15 33.3
subroutine 23 34 67.6
pod 9 10 90.0
total 203 332 61.1


line stmt bran cond sub pod time code
1             package POE::Component::Server::POP3;
2              
3 2     2   274630 use strict;
  2         5  
  2         91  
4 2     2   11 use warnings;
  2         5  
  2         74  
5 2     2   925 use POE qw(Wheel::SocketFactory Wheel::ReadWrite Filter::Line);
  2         65254  
  2         18  
6 2     2   174264 use base qw(POE::Component::Pluggable);
  2         6  
  2         2463  
7 2     2   14859 use POE::Component::Pluggable::Constants qw(:ALL);
  2         8  
  2         306  
8 2     2   15 use Socket;
  2         4  
  2         1697  
9 2     2   26 use vars qw($VERSION);
  2         4  
  2         6025  
10              
11             $VERSION = '0.10';
12              
13             sub spawn {
14 1     1 1 618 my $package = shift;
15 1         15 my %opts = @_;
16 1         12 $opts{lc $_} = delete $opts{$_} for keys %opts;
17 1         5 my $options = delete $opts{options};
18 1 50       8 $opts{version} = join('-', __PACKAGE__, $VERSION ) unless $opts{version};
19 1 50 33     9 $opts{handle_connects} = 1 unless defined $opts{handle_connects} and !$opts{handle_connects};
20 1 50       6 $opts{hostname} = 'localhost' unless defined $opts{hostname};
21 1         4 my $self = bless \%opts, $package;
22 1         15 $self->_pluggable_init( prefix => 'pop3d_', types => [ 'POP3D', 'POP3C' ], debug => 1 );
23 1 50       119 $self->{session_id} = POE::Session->create(
24             object_states => [
25             $self => { shutdown => '_shutdown',
26             send_event => '__send_event',
27             send_to_client => '_send_to_client',
28             disconnect => '_disconnect',
29             },
30             $self => [ qw(_start register unregister _accept_client _accept_failed _conn_input _conn_error _conn_flushed _conn_alarm _send_to_client __send_event _disconnect) ],
31             ],
32             heap => $self,
33             ( ref($options) eq 'HASH' ? ( options => $options ) : () ),
34             )->ID();
35 1         132 return $self;
36             }
37              
38             sub session_id {
39 1     1 1 5 return $_[0]->{session_id};
40             }
41              
42             sub getsockname {
43 1 50   1 1 1192 return unless $_[0]->{listener};
44 1         7 return $_[0]->{listener}->getsockname();
45             }
46              
47             sub _conn_exists {
48 8     8   14 my ($self,$wheel_id) = @_;
49 8 50 33     50 return 0 unless $wheel_id and defined $self->{clients}->{ $wheel_id };
50 8         27 return 1;
51             }
52              
53             sub _valid_cmd {
54 2     2   3 my $self = shift;
55 2   50     6 my $cmd = shift || return;
56 2         5 $cmd = lc $cmd;
57 2 100       3 return 0 unless grep { $_ eq $cmd } @{ $self->{cmds} };
  24         58  
  2         11  
58 1         4 return 1;
59             }
60              
61             sub shutdown {
62 1     1 1 1600 my $self = shift;
63 1         5 $poe_kernel->post( $self->{session_id}, 'shutdown' );
64             }
65              
66             sub _start {
67 1     1   372 my ($kernel,$self,$sender) = @_[KERNEL,OBJECT,SENDER];
68 1         6 $self->{session_id} = $_[SESSION]->ID();
69 1 50       7 if ( $self->{alias} ) {
70 0         0 $kernel->alias_set( $self->{alias} );
71             }
72             else {
73 1         6 $kernel->refcount_increment( $self->{session_id} => __PACKAGE__ );
74             }
75 1 50       40 if ( $kernel != $sender ) {
76 1         4 my $sender_id = $sender->ID;
77 1         8 $self->{events}->{'pop3d_all'}->{$sender_id} = $sender_id;
78 1         3 $self->{sessions}->{$sender_id}->{'ref'} = $sender_id;
79 1         3 $kernel->refcount_increment($sender_id, __PACKAGE__);
80 1         26 $kernel->post( $sender, 'pop3d_registered', $self );
81 1         142 $kernel->detach_myself();
82             }
83              
84 1         118 $self->{filter} = POE::Filter::Line->new( Literal => "\015\012" );
85              
86 1         60 $self->{cmds} = [ qw(stat list retr dele noop rset top uidl user pass apop quit) ];
87              
88 1 50       25 $self->{listener} = POE::Wheel::SocketFactory->new(
    50          
89             ( defined $self->{address} ? ( BindAddress => $self->{address} ) : () ),
90             ( defined $self->{port} ? ( BindPort => $self->{port} ) : ( BindPort => 110 ) ),
91             SuccessEvent => '_accept_client',
92             FailureEvent => '_accept_failed',
93             SocketDomain => AF_INET, # Sets the socket() domain
94             SocketType => SOCK_STREAM, # Sets the socket() type
95             SocketProtocol => 'tcp', # Sets the socket() protocol
96             Reuse => 'on', # Lets the port be reused
97             );
98              
99 1         1489 return;
100             }
101              
102             sub _accept_client {
103 1     1   1135 my ($kernel,$self,$socket,$peeraddr,$peerport) = @_[KERNEL,OBJECT,ARG0..ARG2];
104 1         20 my $sockaddr = inet_ntoa( ( unpack_sockaddr_in ( CORE::getsockname $socket ) )[1] );
105 1         8 my $sockport = ( unpack_sockaddr_in ( CORE::getsockname $socket ) )[0];
106 1         7 $peeraddr = inet_ntoa( $peeraddr );
107              
108 1         14 my $wheel = POE::Wheel::ReadWrite->new(
109             Handle => $socket,
110             Filter => $self->{filter},
111             InputEvent => '_conn_input',
112             ErrorEvent => '_conn_error',
113             FlushedEvent => '_conn_flushed',
114             );
115              
116 1 50       358 return unless $wheel;
117            
118 1         6 my $id = $wheel->ID();
119 1         46 $self->{clients}->{ $id } =
120             {
121             wheel => $wheel,
122             peeraddr => $peeraddr,
123             peerport => $peerport,
124             sockaddr => $sockaddr,
125             sockport => $sockport,
126             };
127 1         6 $self->_send_event( 'pop3d_connection', $id, $peeraddr, $peerport, $sockaddr, $sockport );
128              
129 1   50     11 $self->{clients}->{ $id }->{alarm} = $kernel->delay_set( '_conn_alarm', $self->{time_out} || 300, $id );
130 1         77 return;
131             }
132              
133             sub _accept_failed {
134 0     0   0 my ($kernel,$self,$operation,$errnum,$errstr,$wheel_id) = @_[KERNEL,OBJECT,ARG0..ARG3];
135 0         0 warn "Wheel $wheel_id generated $operation error $errnum: $errstr\n";
136 0         0 delete $self->{listener};
137 0         0 $self->_send_event( 'pop3d_listener_failed', $operation, $errnum, $errstr );
138 0         0 return;
139             }
140              
141             sub _conn_input {
142 2     2   2318 my ($kernel,$self,$input,$id) = @_[KERNEL,OBJECT,ARG0,ARG1];
143 2 50       6 return unless $self->_conn_exists( $id );
144 2   50     19 $kernel->delay_adjust( $self->{clients}->{ $id }->{alarm}, $self->{time_out} || 300 );
145 2         175 $input =~ s/^\s+//g;
146 2         6 $input =~ s/\s+$//g;
147 2         6 my @args = split /\s+/, $input, 2;
148 2         6 my $cmd = shift @args;
149 2 50       5 return unless $cmd;
150 2 100       9 unless ( $self->_valid_cmd( $cmd ) ) {
151 1         3 $self->send_to_client( $id, '-ERR' );
152 1         9 return;
153             }
154 1         3 $cmd = lc $cmd;
155 1 50       6 $self->{clients}->{ $id }->{quit} = 1 if $cmd eq 'quit';
156 1         7 $self->_send_event( 'pop3d_cmd_' . $cmd, $id, @args );
157 1         4 return;
158             }
159              
160             sub _conn_error {
161 0     0   0 my ($self,$errstr,$id) = @_[OBJECT,ARG2,ARG3];
162 0 0       0 return unless $self->_conn_exists( $id );
163 0         0 delete $self->{clients}->{ $id };
164 0         0 $self->_send_event( 'pop3d_disconnected', $id );
165 0         0 return;
166             }
167              
168             sub _conn_flushed {
169 3     3   1975 my ($self,$id) = @_[OBJECT,ARG0];
170 3 50       9 return unless $self->_conn_exists( $id );
171 3 100       16 return unless $self->{clients}->{ $id }->{quit};
172 1         14 delete $self->{clients}->{ $id };
173 1         376 $self->_send_event( 'pop3d_disconnected', $id );
174 1         3 return;
175             }
176              
177             sub _conn_alarm {
178 0     0   0 my ($kernel,$self,$id) = @_[KERNEL,OBJECT,ARG0];
179 0 0       0 return unless $self->_conn_exists( $id );
180 0         0 delete $self->{clients}->{ $id };
181 0         0 $self->_send_event( 'pop3d_disconnected', $id );
182 0         0 return;
183             }
184              
185             sub _shutdown {
186 1     1   283 my ($kernel,$self) = @_[KERNEL,OBJECT];
187 1         7 delete $self->{listener};
188 1         184 delete $self->{clients};
189 1         5 $kernel->alarm_remove_all();
190 1         85 $kernel->alias_remove( $_ ) for $kernel->alias_list();
191 1 50       39 $kernel->refcount_decrement( $self->{session_id} => __PACKAGE__ ) unless $self->{alias};
192 1         57 $self->_pluggable_destroy();
193 1         33 $self->_unregister_sessions();
194 1         38 return;
195             }
196              
197             sub register {
198 0     0 1 0 my ($kernel, $self, $session, $sender, @events) =
199             @_[KERNEL, OBJECT, SESSION, SENDER, ARG0 .. $#_];
200              
201 0 0       0 unless (@events) {
202 0         0 warn "register: Not enough arguments";
203 0         0 return;
204             }
205              
206 0         0 my $sender_id = $sender->ID();
207              
208 0         0 foreach (@events) {
209 0 0       0 $_ = "pop3d_" . $_ unless /^_/;
210 0         0 $self->{events}->{$_}->{$sender_id} = $sender_id;
211 0         0 $self->{sessions}->{$sender_id}->{'ref'} = $sender_id;
212 0 0 0     0 unless ($self->{sessions}->{$sender_id}->{refcnt}++ or $session == $sender) {
213 0         0 $kernel->refcount_increment($sender_id, __PACKAGE__);
214             }
215             }
216              
217 0         0 $kernel->post( $sender, 'pop3d_registered', $self );
218 0         0 return;
219             }
220              
221             sub unregister {
222 0     0 1 0 my ($kernel, $self, $session, $sender, @events) =
223             @_[KERNEL, OBJECT, SESSION, SENDER, ARG0 .. $#_];
224              
225 0 0       0 unless (@events) {
226 0         0 warn "unregister: Not enough arguments";
227 0         0 return;
228             }
229              
230 0         0 $self->_unregister($session,$sender,@events);
231 0         0 undef;
232             }
233              
234             sub _unregister {
235 0     0   0 my ($self,$session,$sender) = splice @_,0,3;
236 0         0 my $sender_id = $sender->ID();
237              
238 0         0 foreach (@_) {
239 0 0       0 $_ = "pop3d_" . $_ unless /^_/;
240 0         0 my $blah = delete $self->{events}->{$_}->{$sender_id};
241 0 0       0 unless ( $blah ) {
242 0         0 warn "$sender_id hasn't registered for '$_' events\n";
243 0         0 next;
244             }
245 0 0       0 if (--$self->{sessions}->{$sender_id}->{refcnt} <= 0) {
246 0         0 delete $self->{sessions}->{$sender_id};
247 0 0       0 unless ($session == $sender) {
248 0         0 $poe_kernel->refcount_decrement($sender_id, __PACKAGE__);
249             }
250             }
251             }
252 0         0 undef;
253             }
254              
255             sub _unregister_sessions {
256 1     1   2 my $self = shift;
257 1         5 my $pop3d_id = $self->session_id();
258 1         2 foreach my $session_id ( keys %{ $self->{sessions} } ) {
  1         4  
259 1 50       7 if (--$self->{sessions}->{$session_id}->{refcnt} <= 0) {
260 1         3 delete $self->{sessions}->{$session_id};
261 1 50       7 $poe_kernel->refcount_decrement($session_id, __PACKAGE__)
262             unless ( $session_id eq $pop3d_id );
263             }
264             }
265             }
266              
267             sub __send_event {
268 0     0   0 my( $self, $event, @args ) = @_[ OBJECT, ARG0, ARG1 .. $#_ ];
269 0         0 $self->_send_event( $event, @args );
270 0         0 return;
271             }
272              
273             sub _pluggable_event {
274 0     0   0 my $self = shift;
275 0         0 $poe_kernel->post( $self->{session_id}, '__send_event', @_ );
276             }
277              
278             sub send_event {
279 0     0 1 0 my $self = shift;
280 0         0 $poe_kernel->post( $self->{session_id}, '__send_event', @_ );
281             }
282              
283             sub _send_event {
284 3     3   7 my $self = shift;
285 3         8 my ($event, @args) = @_;
286 3         6 my $kernel = $POE::Kernel::poe_kernel;
287 3         14 my $session = $kernel->get_active_session()->ID();
288 3         21 my %sessions;
289              
290             my @extra_args;
291              
292 3 50       21 return 1 if $self->_pluggable_process( 'POP3D', $event, \( @args ), \@extra_args ) == PLUGIN_EAT_ALL;
293              
294 3 50       148 push @args, @extra_args if scalar @extra_args;
295              
296 3         6 $sessions{$_} = $_ for (values %{$self->{events}->{'pop3d_all'}}, values %{$self->{events}->{$event}});
  3         9  
  3         24  
297              
298 3         18 $kernel->post( $_ => $event => @args ) for values %sessions;
299 3         313 undef;
300             }
301              
302             sub disconnect {
303 0     0 1 0 my $self = shift;
304 0         0 $poe_kernel->call( $self->{session_id}, '_disconnect', @_ );
305             }
306              
307             sub _disconnect {
308 0     0   0 my ($kernel,$self,$id) = @_[KERNEL,OBJECT,ARG0];
309 0 0       0 return unless $self->_conn_exists( $id );
310 0         0 $self->{clients}->{ $id }->{quit} = 1;
311 0         0 return 1;
312             }
313              
314             sub send_to_client {
315 3     3 1 435 my $self = shift;
316 3         18 $poe_kernel->call( $self->{session_id}, '_send_to_client', @_ );
317             }
318              
319             sub _send_to_client {
320 3     3   147 my ($kernel,$self,$id,$output) = @_[KERNEL,OBJECT,ARG0..ARG1];
321 3 50       11 return unless $self->_conn_exists( $id );
322 3 50       9 return unless defined $output;
323              
324 3 50       25 return 1 if $self->_pluggable_process( 'POP3C', 'response', $id, \$output ) == PLUGIN_EAT_ALL;
325              
326 3         149 $self->{clients}->{ $id }->{wheel}->put($output);
327 3         236 return 1;
328             }
329              
330             sub POP3D_connection {
331 1     1 0 111 my ($self,$pop3d) = splice @_, 0, 2;
332 1         2 my $id = ${ $_[0] };
  1         10  
333 1 50       5 return PLUGIN_EAT_NONE unless $self->{handle_connects};
334 1         8 $self->send_to_client( $id, join ' ', '+OK POP3', $self->{hostname}, $self->{version}, 'server ready' );
335 1         8 return PLUGIN_EAT_NONE;
336             }
337              
338             'poppet';
339              
340             __END__