File Coverage

blib/lib/Net/APNs/Extended/Base.pm
Criterion Covered Total %
statement 48 145 33.1
branch 9 64 14.0
condition 11 25 44.0
subroutine 15 26 57.6
pod 1 3 33.3
total 84 263 31.9


line stmt bran cond sub pod time code
1             package Net::APNs::Extended::Base;
2              
3 13     13   266364 use strict;
  13         65  
  13         395  
4 13     13   66 use warnings;
  13         22  
  13         330  
5 13     13   251 use 5.008_001;
  13         45  
6             our $VERSION = '0.14';
7              
8 13     13   1720 use parent 'Class::Accessor::Lite';
  13         1195  
  13         94  
9              
10 13     13   30037 use JSON::XS;
  13         83106  
  13         842  
11 13     13   112 use Carp qw(croak);
  13         28  
  13         595  
12 13     13   8830 use File::Temp qw(tempfile);
  13         232366  
  13         950  
13 13     13   6836 use Socket qw(PF_INET SOCK_STREAM MSG_DONTWAIT inet_aton pack_sockaddr_in);
  13         45551  
  13         2633  
14 13     13   7502 use Net::SSLeay ();
  13         93088  
  13         507  
15 13     13   180 use Errno qw(EAGAIN EWOULDBLOCK EINTR);
  13         30  
  13         1632  
16 13     13   6736 use Time::HiRes ();
  13         15917  
  13         20289  
17              
18             __PACKAGE__->mk_accessors(qw[
19             host_production
20             host_sandbox
21             is_sandbox
22             port
23             password
24             cert_file
25             cert
26             cert_type
27             key_file
28             key
29             key_type
30             read_timeout
31             write_timeout
32             json
33             ]);
34              
35             my %default = (
36             cert_type => Net::SSLeay::FILETYPE_PEM(),
37             key_type => Net::SSLeay::FILETYPE_PEM(),
38             read_timeout => 3,
39             write_timeout => undef,
40             );
41              
42             sub new {
43 14     14 1 12963 my ($class, %args) = @_;
44             croak "`cert_file` or `cert` must be specify"
45 14 100 100     304 unless exists $args{cert_file} or exists $args{cert};
46             croak "specifying both `cert_file` and `cert` is not allowed"
47 13 100 100     227 if exists $args{cert_file} and exists $args{cert};
48             croak "specifying both `key_file` and `key` is not allowed"
49 12 100 100     211 if exists $args{key_file} and exists $args{key};
50              
51 11         16630 Net::SSLeay::load_error_strings();
52 11         1577 Net::SSLeay::SSLeay_add_ssl_algorithms();
53 11         486 Net::SSLeay::randomize();
54              
55 11   33     8206 $args{json} ||= JSON::XS->new->utf8;
56 11         170 bless { %default, %args }, $class;
57             }
58              
59             sub hostname {
60 2     2 0 10 my $self = shift;
61 2 100       8 $self->is_sandbox ? $self->host_sandbox : $self->host_production;
62             }
63              
64             sub _connect {
65 0     0   0 my $self = shift;
66 0   0     0 my $connection = $self->{_connection} || [];
67 0         0 my ($sock, $ctx, $ssl) = @$connection;
68 0 0 0     0 return $connection if $sock && $ctx && $ssl;
      0        
69              
70 0         0 $self->disconnect;
71              
72 0         0 $sock = $self->_create_socket;
73 0         0 $ctx = $self->_create_ctx;
74 0         0 $ssl = $self->_create_ssl($sock, $ctx);
75              
76 0         0 $self->{_connection} = [$sock, $ctx, $ssl];
77             }
78              
79             sub _create_socket {
80 0     0   0 my $self = shift;
81 0 0       0 socket(my $sock, PF_INET, SOCK_STREAM, 0) or die "can't create socket: $!";
82 0         0 my $sock_addr = do {
83 0 0       0 my $iaddr = inet_aton($self->hostname)
84             or die sprintf "can't create iaddr from %s", $self->hostname;
85 0 0       0 pack_sockaddr_in $self->port, $iaddr or die "can't create sock_addr: $!";
86             };
87 0 0       0 CORE::connect($sock, $sock_addr) or die "can't connect socket: $!";
88 0         0 my $old_out = select($sock); $| = 1; select($old_out); # autoflush
  0         0  
  0         0  
89              
90 0         0 return $sock;
91             }
92              
93             sub _create_ctx {
94 0     0   0 my $self = shift;
95 0 0       0 my $ctx = Net::SSLeay::CTX_new() or _die_if_ssl_error("can't create SSL_CTX: $!");
96 0         0 Net::SSLeay::CTX_set_options($ctx, Net::SSLeay::OP_ALL());
97 0         0 _die_if_ssl_error("ctx options: $!");
98              
99 0         0 my $pw = $self->password;
100 0 0   0   0 Net::SSLeay::CTX_set_default_passwd_cb($ctx, ref $pw ? $pw : sub { $pw });
  0         0  
101              
102 0         0 $self->_set_certificate($ctx);
103              
104 0         0 return $ctx;
105             }
106              
107             sub _create_ssl {
108 0     0   0 my ($self, $sock, $ctx) = @_;
109 0         0 my $ssl = Net::SSLeay::new($ctx);
110 0         0 Net::SSLeay::set_fd($ssl, fileno $sock);
111 0 0       0 Net::SSLeay::connect($ssl) or _die_if_ssl_error("failed ssl connect: $!");
112              
113 0         0 return $ssl;
114             }
115              
116             sub _set_certificate {
117 0     0   0 my ($self, $ctx) = @_;
118 0         0 my ($cert_guard, $key_guard);
119 0         0 my $cert_file = $self->cert_file;
120 0 0       0 ($cert_guard, $cert_file) = _tmpfile($self->cert) unless defined $cert_file;
121 0         0 Net::SSLeay::CTX_use_certificate_file($ctx, $cert_file, $self->cert_type);
122 0         0 _die_if_ssl_error("certificate: $!");
123              
124 0         0 my $key_file;
125 0 0 0     0 if (exists $self->{key_file} or exists $self->{key}) {
126 0         0 $key_file = $self->key_file;
127 0 0       0 ($key_guard, $key_file) = _tmpfile($self->key) unless defined $key_file;
128             }
129             else {
130 0         0 $key_file = $cert_file;
131             }
132 0         0 Net::SSLeay::CTX_use_RSAPrivateKey_file($ctx, $key_file, $self->key_type);
133 0         0 _die_if_ssl_error("private key: $!");
134             }
135              
136             sub disconnect {
137 11     11 0 41 my $self = shift;
138 11   50     118 my $connection = $self->{_connection} || [];
139 11 50       799 return 1 unless @$connection;
140              
141 0         0 my ($sock, $ctx, $ssl) = @$connection;
142 0 0       0 if ($sock) {
143 0 0       0 unless (defined CORE::shutdown($sock, 1)) {
144 0         0 die "can't shutdown socket: $!";
145             }
146             }
147 0 0       0 if ($ssl) {
148 0         0 Net::SSLeay::free($ssl);
149 0         0 _die_if_ssl_error("failed ssl free: $!");
150             }
151 0 0       0 if ($ctx) {
152 0         0 Net::SSLeay::CTX_free($ctx);
153 0         0 _die_if_ssl_error("failed ctx free: $!");
154             }
155 0 0       0 if ($sock) {
156 0 0       0 close $sock or die "can't close socket: $!";
157             }
158              
159 0         0 delete $self->{_connection};
160              
161 0         0 return 1;
162             }
163              
164             sub _send {
165 0     0   0 my $self = shift;
166 0         0 my $data = \$_[0];
167 0         0 my ($sock, $ctx, $ssl) = @{$self->_connect};
  0         0  
168              
169 0 0       0 return unless $self->_do_select($sock, 'write', $self->write_timeout);
170              
171 0 0       0 Net::SSLeay::ssl_write_all($ssl, $data) or _die_if_ssl_error("ssl_write_all error: $!");
172 0         0 return 1;
173             }
174              
175             sub _read {
176 0     0   0 my $self = shift;
177 0         0 my ($sock, $ctx, $ssl) = @{$self->_connect};
  0         0  
178              
179 0 0       0 return unless $self->_do_select($sock, 'read', $self->read_timeout);
180              
181 0 0       0 my $data = Net::SSLeay::ssl_read_all($ssl) or _die_if_ssl_error("ssl_read_all error: $!");
182 0         0 return $data;
183             }
184              
185             sub _do_select {
186 0     0   0 my ($self, $sock, $act, $timeout) = @_;
187              
188 0         0 my $begin_time = Time::HiRes::time();
189              
190 0         0 vec(my $bits = '', fileno($sock), 1) = 1;
191 0         0 while (1) {
192 0         0 my $nfound;
193 0 0       0 if ($act eq 'read') {
194 0         0 $nfound = select my $rout = $bits, undef, undef, $timeout;
195             }
196             else {
197 0         0 $nfound = select undef, my $wout = $bits, undef, $timeout;
198             }
199 0 0       0 return unless $nfound; # timeout
200              
201             # returned error
202 0 0       0 if ($nfound == -1) {
203 0 0       0 if ($! == EINTR) {
204             # can retry
205 0 0       0 $timeout -= (Time::HiRes::time() - $begin_time) if defined $timeout;
206 0         0 next;
207             }
208             else {
209             # other error
210 0         0 $self->disconnect;
211 0         0 return;
212             }
213             }
214              
215 0         0 last;
216             }
217              
218 0         0 return 1;
219             }
220              
221             sub DESTROY {
222 11     11   51191 my $self = shift;
223 11         71 $self->disconnect;
224             }
225              
226             sub _tmpfile {
227 0     0     my $fh = File::Temp->new(
228             TEMPLATE => "napnseXXXXXXXXXXX",
229             TMPDIR => 1,
230             EXLOCK => 0,
231             );
232 0           syswrite $fh, $_[0];
233 0           close $fh;
234              
235 0           return $fh, $fh->filename;
236             }
237              
238             sub _die_if_ssl_error {
239 0     0     my ($msg) = @_;
240 0           my $err = Net::SSLeay::print_errs("SSL error: $msg");
241 0 0         croak $err if $err;
242             }
243              
244             1;
245             __END__