File Coverage

blib/lib/POE/Component/Captcha/reCAPTCHA.pm
Criterion Covered Total %
statement 27 118 22.8
branch 0 24 0.0
condition 0 3 0.0
subroutine 9 23 39.1
pod 6 6 100.0
total 42 174 24.1


line stmt bran cond sub pod time code
1             package POE::Component::Captcha::reCAPTCHA;
2              
3 1     1   990 use strict;
  1         2  
  1         44  
4 1     1   7 use warnings;
  1         2  
  1         33  
5 1     1   17 use Carp;
  1         1  
  1         90  
6 1     1   5185 use POE qw(Component::Client::HTTP);
  1         85999  
  1         6  
7 1     1   499369 use HTTP::Request::Common;
  1         4356  
  1         93  
8 1     1   1584 use Captcha::reCAPTCHA;
  1         33389  
  1         36  
9 1     1   9 use vars qw($VERSION);
  1         3  
  1         52  
10              
11             $VERSION = '0.02';
12              
13 1     1   6 use constant API_VERIFY_SERVER => 'http://api-verify.recaptcha.net';
  1         3  
  1         45  
14 1     1   5 use constant SERVER_ERROR => 'recaptcha-not-reachable';
  1         2  
  1         3123  
15              
16             # Stolen from POE::Wheel. This is static data, shared by all
17             my $current_id = 0;
18             my %active_identifiers;
19              
20             sub _allocate_identifier {
21 0     0     while (1) {
22 0 0         last unless exists $active_identifiers{ ++$current_id };
23             }
24 0           return $active_identifiers{$current_id} = $current_id;
25             }
26              
27             sub _free_identifier {
28 0     0     my $id = shift;
29 0           delete $active_identifiers{$id};
30             }
31              
32              
33             sub spawn {
34 0     0 1   my $package = shift;
35 0           my %opts = @_;
36 0           $opts{lc $_} = delete $opts{$_} for keys %opts;
37 0           my $options = delete $opts{options};
38 0           my $self = bless \%opts, $package;
39 0 0         $self->{session_id} = POE::Session->create(
40             object_states => [
41             $self => { shutdown => '_shutdown',
42             check_answer => '_check_answer',
43             },
44             $self => [ qw(_start _check_answer _dispatch _http_request _http_response) ],
45             ],
46             heap => $self,
47             ( ref($options) eq 'HASH' ? ( options => $options ) : () ),
48             )->ID();
49 0           my $captcha = Captcha::reCAPTCHA->new();
50 0           $self->{_captcha} = $captcha;
51 0           return $self;
52             }
53              
54             sub session_id {
55 0     0 1   return $_[0]->{session_id};
56             }
57              
58             sub shutdown {
59 0     0 1   my $self = shift;
60 0           $poe_kernel->post( $self->{session_id}, 'shutdown' );
61             }
62              
63             sub _start {
64 0     0     my ($kernel,$self) = @_[KERNEL,OBJECT];
65 0           $self->{session_id} = $_[SESSION]->ID();
66 0 0         if ( $self->{alias} ) {
67 0           $kernel->alias_set( $self->{alias} );
68             }
69             else {
70 0           $kernel->refcount_increment( $self->{session_id} => __PACKAGE__ );
71             }
72 0           $self->{_httpc} = 'httpc-' . $self->{session_id};
73 0           POE::Component::Client::HTTP->spawn(
74             Alias => $self->{_httpc},
75             FollowRedirects => 2,
76             );
77 0           return;
78             }
79              
80             sub _shutdown {
81 0     0     my ($kernel,$self) = @_[KERNEL,OBJECT];
82 0           $kernel->alias_remove( $_ ) for $kernel->alias_list();
83 0 0         $kernel->refcount_decrement( $self->{session_id} => __PACKAGE__ ) unless $self->{alias};
84 0           $self->{_shutdown} = 1;
85 0           $kernel->post( $self->{_httpc}, 'shutdown' );
86 0           undef;
87             }
88              
89             sub get_html {
90 0     0 1   my $self = shift;
91 0           $self->{_captcha}->get_html( @_ );
92             }
93              
94             sub get_options_setter {
95 0     0 1   my $self = shift;
96 0           $self->{_captcha}->get_options_setter( @_ );
97             }
98              
99             sub check_answer {
100 0     0 1   my $self = shift;
101 0           $poe_kernel->post( $self->{session_id}, 'check_answer', @_ );
102             }
103              
104             sub _check_answer {
105 0     0     my ($kernel,$self,$state) = @_[KERNEL,OBJECT,STATE];
106 0           my $sender = $_[SENDER]->ID();
107 0 0         return if $self->{_shutdown};
108 0           my $args;
109 0 0         if ( ref( $_[ARG0] ) eq 'HASH' ) {
110 0           $args = { %{ $_[ARG0] } };
  0            
111             } else {
112 0           $args = { @_[ARG0..$#_] };
113             }
114              
115 0           $args->{lc $_} = delete $args->{$_} for grep { $_ !~ /^_/ } keys %{ $args };
  0            
  0            
116              
117 0 0         unless ( $args->{event} ) {
118 0           warn "No 'event' specified for $state";
119 0           return;
120             }
121              
122             croak
123 0 0         "To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey"
124             unless $args->{privatekey};
125              
126 0 0         croak "For security reasons, you must pass the remote ip to reCAPTCHA"
127             unless $args->{remoteip};
128              
129 0           $args->{sender} = $sender;
130 0           $kernel->refcount_increment( $sender => __PACKAGE__ );
131 0           $kernel->yield( '_http_request', $args );
132              
133 0           return;
134             }
135              
136             sub _http_request {
137 0     0     my ($kernel,$self,$req) = @_[KERNEL,OBJECT,ARG0];
138            
139 0 0 0       unless ( $req->{challenge} and $req->{response} ) {
140 0           $req->{is_valid} = 0;
141 0           $req->{error} = 'incorrect-captcha-sol';
142 0           $kernel->yield( '_dispatch', $req );
143 0           return;
144             }
145              
146 0           my %postargs = map {
147 0           ( $_ => $req->{$_} )
148             } qw(privatekey remoteip challenge response);
149              
150 0           my $id = _allocate_identifier();
151              
152 0           $kernel->post(
153             $self->{_httpc},
154             'request',
155             '_http_response',
156             POST( API_VERIFY_SERVER . '/verify', \%postargs ),
157             "$id",
158             );
159              
160 0           $self->{_requests}->{ $id } = $req;
161 0           return;
162             }
163              
164             sub _http_response {
165 0     0     my ($kernel,$self,$request_packet,$response_packet) = @_[KERNEL,OBJECT,ARG0,ARG1];
166 0           my $id = $request_packet->[1];
167 0           my $req = delete $self->{_requests}->{ $id };
168 0           _free_identifier( $id );
169 0           my $resp = $response_packet->[0];
170 0 0         if ( $resp->is_success ) {
171 0           my ( $answer, $message ) = split( /\n/, $resp->content, 2 );
172 0 0         if ( $answer =~ /true/ ) {
173 0           $req->{is_valid} = 1;
174             }
175             else {
176 0           chomp $message;
177 0           $req->{is_valid} = 0; $req->{error} = $message;
  0            
178             }
179             }
180             else {
181 0           $req->{is_valid} = 0; $req->{error} = SERVER_ERROR;
  0            
182             }
183              
184 0           $kernel->yield( '_dispatch', $req );
185 0           return;
186             }
187              
188             sub _dispatch {
189 0     0     my ($kernel,$self,$input) = @_[KERNEL,OBJECT,ARG0];
190 0           my $session = delete $input->{sender};
191 0           my $event = delete $input->{event};
192 0           $kernel->post( $session, $event, $input );
193 0           $kernel->refcount_decrement( $session => __PACKAGE__ );
194 0           return;
195             }
196              
197             qq[CAPTCH!];
198              
199             __END__