File Coverage

blib/lib/Net/Curl/Simple.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Net::Curl::Simple;
2              
3 5     5   27475 use strict;
  5         10  
  5         195  
4 5     5   57 use warnings; no warnings 'redefine';
  5     5   8  
  5         200  
  5         34  
  5         9  
  5         207  
5 5     5   10005 use Net::Curl 0.17;
  0            
  0            
6             use Net::Curl::Easy qw(/^CURLOPT_(PROXY|POSTFIELDS)/ /^CURLPROXY_/);
7             use Scalar::Util qw(looks_like_number);
8             use URI;
9             use URI::Escape qw(uri_escape);
10             use base qw(Net::Curl::Easy);
11              
12             our $VERSION = '0.13';
13              
14             use constant
15             curl_features => Net::Curl::version_info()->{features};
16              
17             use constant {
18             can_ipv6 => ( curl_features & Net::Curl::CURL_VERSION_IPV6 ) != 0,
19             can_ssl => ( curl_features & Net::Curl::CURL_VERSION_SSL ) != 0,
20             can_libz => ( curl_features & Net::Curl::CURL_VERSION_LIBZ ) != 0,
21             can_asynchdns => ( curl_features & Net::Curl::CURL_VERSION_ASYNCHDNS ) != 0,
22             };
23              
24             use Net::Curl::Simple::Async;
25              
26             my @common_options = (
27             connecttimeout => 60,
28             followlocation => 1,
29             # just to avoid loops
30             maxredirs => 50,
31             # there are to many broken servers to care about it by default
32             ssl_verifypeer => 0,
33             # enable cookie session
34             cookiefile => '',
35             useragent => __PACKAGE__ . ' v' . $VERSION,
36             headerfunction => \&_cb_header,
37             httpheader => [
38             'Accept: */*',
39             ],
40             # sets Accept-Encoding to all values supported by libcurl
41             encoding => '',
42             );
43              
44             my %proxytype = (
45             http => CURLPROXY_HTTP,
46             socks4 => CURLPROXY_SOCKS4,
47             socks5 => CURLPROXY_SOCKS5,
48             socks => CURLPROXY_SOCKS5,
49             );
50             {
51             # introduced later in 7.18.0 and 7.19.4
52             eval {
53             $proxytype{socks4a} = CURLPROXY_SOCKS4A();
54             $proxytype{socks5h} = CURLPROXY_SOCKS5_HOSTNAME();
55             };
56             eval {
57             $proxytype{http10} = CURLPROXY_HTTP_1_0();
58             };
59             }
60              
61             # options that accept either a single constant or a bitmask of constants
62             my %optlong2constprefix = (
63             http_version => 'CURL_HTTP_VERSION_',
64             ipresolve => 'CURL_IPRESOLVE_',
65             netrc => 'CURL_NETRC_',
66             postredir => 'CURL_REDIR_POST_',
67             rtsp_request => 'CURL_RTSPREQ_',
68             sslversion => 'CURL_SSLVERSION_',
69             timecondition => 'CURL_TIMECOND_',
70             httpauth => 'CURLAUTH_',
71             proxyauth => 'CURLAUTH_',
72             ftpsslauth => 'CURLFTPAUTH_',
73             ftp_filemethod => 'CURLFTPMETHOD_',
74             tlsauth_type => 'CURLOPT_TLSAUTH_',
75             protocols => 'CURLPROTO_',
76             redir_protocols => 'CURLPROTO_',
77             ssh_auth_types => 'CURLSSH_AUTH_',
78             use_ssl => 'CURLUSESSL_',
79             );
80              
81             {
82             my %optcache;
83             my %optlongcache;
84              
85             sub setopt
86             {
87             my ( $easy, $opt, $val, $temp ) = @_;
88              
89             unless ( looks_like_number( $opt ) ) {
90             if ( exists $optlong2constprefix{ $opt } ) {
91             # convert option value to a number
92             # FROM: protocols => "http, file"
93             # TO: CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_FILE
94             unless ( looks_like_number( $val ) ) {
95             unless ( exists $optlongcache{ $opt }->{ $val } ) {
96             my $value = 0;
97             my $prefix = $optlong2constprefix{ $opt };
98             foreach ( ref $val ? @$val : split /[\|, ]+/, $val ) {
99             my $const = $prefix . uc $_;
100             # only constants with lowercase letters:
101             # CURL_SSLVERSION_TLSv1, CURL_SSLVERSION_SSLv2...
102             $const =~ s/V(\d+)$/v$1/
103             if $prefix eq "CURL_SSLVERSION_";
104             eval "\$value |= Net::Curl::Easy::$const";
105             die "unrecognized literal value: $_ for option $opt\n"
106             if $@;
107             }
108             $optlongcache{ $opt }->{ $val } = $value;
109             }
110             $val = $optlongcache{ $opt }->{ $val };
111             }
112             }
113             # convert option name to option number
114             unless ( exists $optcache{ $opt } ) {
115             eval "\$optcache{ \$opt } = Net::Curl::Easy::CURLOPT_\U$opt";
116             die "unrecognized literal option: $opt\n"
117             if $@;
118             }
119             $opt = $optcache{ $opt };
120             }
121              
122             if ( $opt == CURLOPT_PROXY ) {
123             # guess proxy type from proxy string
124             my $type = ( $val =~ m#^([a-z0-9]+)://# );
125             if ( defined $type and exists $proxytype{ $type } ) {
126             $easy->setopt( CURLOPT_PROXYTYPE, $proxytype{ $type }, $temp );
127             }
128             } elsif ( $opt == CURLOPT_POSTFIELDS ) {
129             # perl knows the size, but libcurl may be wrong
130             $easy->setopt( CURLOPT_POSTFIELDSIZE, length $val, $temp );
131             }
132              
133             my $stash = $easy->{options_temp};
134             unless ( $temp ) {
135             delete $stash->{ $opt };
136             $stash = $easy->{options};
137             }
138             $stash->{ $opt } = $val;
139             $easy->SUPER::setopt( $opt => $val );
140             }
141             }
142              
143             sub setopts
144             {
145             my $easy = shift;
146              
147             while ( my ( $opt, $val ) = splice @_, 0, 2 ) {
148             $easy->setopt( $opt => $val );
149             }
150             }
151              
152             sub setopts_temp
153             {
154             my $easy = shift;
155              
156             while ( my ( $opt, $val ) = splice @_, 0, 2 ) {
157             $easy->setopt( $opt => $val, 1 );
158             }
159             }
160              
161              
162             {
163             my %infocache;
164              
165             sub getinfo
166             {
167             my ( $easy, $info ) = @_;
168              
169             unless ( looks_like_number( $info ) ) {
170             # convert option name to option number
171             unless ( exists $infocache{ $info } ) {
172             eval "\$infocache{ \$info } = Net::Curl::Easy::CURLINFO_\U$info";
173             die "unrecognized literal info: $info\n"
174             if $@;
175             }
176             $info = $infocache{ $info };
177             }
178              
179             $easy->SUPER::getinfo( $info );
180             }
181             }
182              
183             sub getinfos
184             {
185             my $easy = shift;
186             my @out;
187              
188             foreach my $arg ( @_ ) {
189             my $ret = undef;
190             eval {
191             $ret = $easy->getinfo( $arg );
192             };
193             push @out, $ret;
194             }
195             return @out;
196             }
197              
198             sub _cb_header
199             {
200             my ( $easy, $data, $uservar ) = @_;
201             push @{ $easy->{headers} }, $data;
202             return length $data;
203             }
204              
205             sub new
206             {
207             my $class = shift;
208              
209             my $easy = $class->SUPER::new(
210             {
211             body => '',
212             headers => [],
213             options => {},
214             options_temp => {},
215             }
216             );
217             # some sane defaults
218             $easy->setopts(
219             writeheader => \$easy->{headers},
220             file => \$easy->{body},
221             @common_options,
222             @_,
223             );
224              
225             return $easy;
226             }
227              
228             sub _finish
229             {
230             my ( $easy, $result ) = @_;
231             $easy->{referer} = $easy->getinfo( 'effective_url' );
232             $easy->{in_use} = 0;
233             $easy->{code} = $result;
234              
235             my $perm = $easy->{options};
236             foreach my $opt ( keys %{ $easy->{options_temp} } ) {
237             my $val = $perm->{$opt};
238             $easy->setopt( $opt => $val, 0 );
239             }
240              
241             my $cb = $easy->{cb};
242             eval { $cb->( $easy ) } if $cb;
243             }
244              
245             sub ua
246             {
247             return (shift)->share();
248             }
249              
250             sub _start_perform($);
251             sub _perform
252             {
253             my ( $easy, $uri, $cb ) = splice @_, 0, 3;
254             if ( $easy->{in_use} ) {
255             die "this handle is already in use\n";
256             }
257             if ( $easy->{referer} ) {
258             $easy->setopt( referer => $easy->{referer} );
259             $uri = URI->new( $uri )->abs( $easy->{referer} )->as_string;
260             }
261              
262             $easy->setopts_temp( @_ ) if @_;
263             $easy->setopt( url => $uri );
264              
265             $easy->{uri} = $uri;
266             $easy->{cb} = $cb;
267             $easy->{body} = '';
268             $easy->{headers} = [];
269             $easy->{in_use} = 1;
270              
271             Net::Curl::Simple::Async::multi->add_handle( $easy );
272              
273             # block unless we've got a callback
274             $easy->join unless $cb;
275              
276             return $easy;
277             }
278              
279             *join = sub ($)
280             {
281             my $easy = shift;
282             if ( not ref $easy ) {
283             # no object, wait for first easy that finishes
284             $easy = Net::Curl::Simple::Async::multi->get_one();
285             return $easy;
286             } else {
287             return $easy unless $easy->{in_use};
288             Net::Curl::Simple::Async::multi->get_one( $easy );
289             return $easy;
290             }
291             };
292              
293             # results
294             sub code
295             {
296             return (shift)->{code};
297             }
298              
299             sub headers
300             {
301             return @{ (shift)->{headers} };
302             }
303              
304             sub content
305             {
306             return (shift)->{body};
307             }
308              
309             # get some uri
310             sub get
311             {
312             my ( $easy, $uri ) = splice @_, 0, 2;
313             my $cb = @_ & 1 ? pop : undef;
314              
315             $easy->_perform( $uri, $cb,
316             @_,
317             httpget => 1,
318             );
319             }
320              
321             # request head on some uri
322             sub head
323             {
324             my ( $easy, $uri ) = splice @_, 0, 2;
325             my $cb = @_ & 1 ? pop : undef;
326              
327             $easy->_perform( $uri, $cb,
328             @_,
329             nobody => 1,
330             );
331             }
332              
333             # post data to some uri
334             sub post
335             {
336             my ( $easy, $uri, $post ) = splice @_, 0, 3;
337             my $cb = @_ & 1 ? pop : undef;
338              
339             my @postopts;
340             if ( not ref $post ) {
341             @postopts = ( postfields => $post );
342             } elsif ( UNIVERSAL::isa( $post, 'Net::Curl::Form' ) ) {
343             @postopts = ( httppost => $post );
344             } elsif ( ref $post eq 'HASH' ) {
345             # handle utf8 ?
346             my $postdata = join '&',
347             map { uri_escape( $_ ) . '=' . uri_escape( $post->{ $_ } ) }
348             sort keys %$post;
349             @postopts = ( postfields => $postdata );
350             } else {
351             die "don't know how to convert $post into a valid post\n";
352             }
353             $easy->_perform( $uri, $cb,
354             @_,
355             post => 1,
356             @postopts
357             );
358             }
359              
360             # put some data
361             sub put
362             {
363             my ( $easy, $uri, $put ) = splice @_, 0, 3;
364             my $cb = @_ & 1 ? pop : undef;
365              
366             my @putopts;
367             if ( not ref $put ) {
368             die "Cannot put file $put\n"
369             unless -r $put;
370             open my $fin, '<', $put;
371             @putopts = (
372             readfunction => sub {
373             my ( $easy, $maxlen, $uservar ) = @_;
374             sysread $fin, my ( $r ), $maxlen;
375             return \$r;
376             },
377             infilesize_large => -s $put
378             );
379             } elsif ( ref $put eq 'SCALAR' ) {
380             my $data = $$put;
381             use bytes;
382             @putopts = (
383             readfunction => sub {
384             my ( $easy, $maxlen, $uservar ) = @_;
385             my $r = substr $data, 0, $maxlen, '';
386             return \$r;
387             },
388             infilesize_large => length $data
389             );
390             } elsif ( ref $put eq 'CODE' ) {
391             @putopts = (
392             readfunction => $put,
393             );
394             } else {
395             die "don't know how to put $put\n";
396             }
397             $easy->_perform( $uri, $cb,
398             @_,
399             upload => 1,
400             @putopts
401             );
402             }
403              
404              
405             1;
406              
407             __END__