File Coverage

blib/lib/Net/APNs/Extended/Base.pm
Criterion Covered Total %
statement 49 146 33.5
branch 9 64 14.0
condition 11 25 44.0
subroutine 15 26 57.6
pod 1 3 33.3
total 85 264 32.2


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