File Coverage

blib/lib/LWP/UserAgent/Paranoid.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1 2     2   10 use strict;
  2         2  
  2         73  
2 2     2   6 use warnings;
  2         3  
  2         60  
3 2     2   6 no warnings "void";
  2         3  
  2         88  
4              
5             =head1 NAME
6              
7             LWP::UserAgent::Paranoid - A modern LWPx::ParanoidAgent for safer requests
8              
9             =cut
10              
11             package LWP::UserAgent::Paranoid;
12 2     2   6 use base 'LWP::UserAgent';
  2         3  
  2         1257  
13              
14             our $VERSION = "0.97";
15              
16 2     2   47569 use Scalar::Util qw/ refaddr /;
  2         7  
  2         162  
17 2     2   11 use Time::HiRes qw/ alarm /;
  2         3  
  2         19  
18 2     2   3214 use LWPx::ParanoidHandler qw//;
  0            
  0            
19             use Net::DNS::Paranoid qw//;
20             use Carp qw//;
21              
22             =head1 SYNOPSIS
23              
24             use LWP::UserAgent::Paranoid;
25             my $ua = LWP::UserAgent::Paranoid->new(
26             request_timeout => 5 # seconds; may be fractional
27             );
28              
29             # use $ua as a normal LWP::UserAgent...
30             my $response = $ua->get("http://example.com");
31              
32             # allow requests to localhost and 127.0.0.1
33             $ua->whitelisted_hosts('localhost', '127.0.0.1');
34              
35             =head1 DESCRIPTION
36              
37             This module is a more modern L with cleaner internals and
38             a very similar feature set. It is a not a drop-in replacement, however, since
39             the API differs.
40              
41             The primary features provided by this module:
42              
43             =head2 Overall request timeout
44              
45             A configurable timeout from start to finish of a "logical" request made by
46             calling one of L's request methods. It encompasses all
47             followed redirects to ensure that you can't be tarpitted by a series of
48             stalling redirects. The default is 5 seconds.
49              
50             =head2 Blocked private hosts and IP address ranges
51              
52             All new agents are automatically made paranoid of private hostnames and IP
53             address ranges using L. You may access the
54             L resolver via the L method in order to
55             customize its behaviour.
56              
57             For simple whitelisting and blacklisting, you may call L or
58             L. These methods are proxied to the corresponding methods
59             of L. The only difference is that you may pass a list to
60             this class' methods.
61              
62             =head1 EVEN MORE PARANOIA
63              
64             You may also wish to tune standard L parameters for greater
65             paranoria depending on your requirements:
66              
67             =head2 Maximum number of redirects
68              
69             Although generally unnecessary given the request timeout, you can tune
70             L down from the default of 7.
71              
72             =head2 Protocols/URI schemes allowed
73              
74             If you don't want to allow requests for schemes other than http and https, you
75             may use L either as a method or as an option
76             to I.
77              
78             $ua->protocols_allowed(["http", "https"]);
79              
80             =head1 WHY NOT LWPx::ParanoidAgent?
81              
82             L's implemention involves a 2009-era fork of LWP's http
83             and https protocol handlers, and it is no longer maintained. A more
84             maintainable approach is taken by this module and L.
85              
86             =head1 METHODS
87              
88             All methods from L are available via inheritence. In addition,
89             the following methods are available:
90              
91             =head2 request_timeout
92              
93             Gets/sets the timeout which encapsulates each logical request, including any
94             redirects which are followed. The default is 5 seconds. Fractional seconds
95             are OK.
96              
97             =head2 resolver
98              
99             Gets the DNS resolver which is used to block private hosts. There is little
100             need to set your own but if you do it should be an L
101             object. This attribute is read-only, so if you want to replace the resolver
102             you need to call L again to create a new L.
103              
104             Use the blocking and whitelisting methods on the resolver, or this class'
105             L and L, to customize the behaviour.
106              
107             =head2 whitelisted_hosts / blocked_hosts
108              
109             Accepts a single arrayref and proxies to the method of the same name on the
110             L. For convenience, you may pass a list which will be passed as an
111             arrayref to the resolver's method.
112              
113             =cut
114              
115             sub new {
116             my ($class, %opts) = @_;
117              
118             my $timeout = delete $opts{request_timeout};
119             $timeout = 5 unless $timeout;
120              
121             my $resolver = delete $opts{resolver};
122             $resolver = Net::DNS::Paranoid->new unless $resolver;
123              
124             for my $acl (qw(blocked_hosts whitelisted_hosts)) {
125             next unless $opts{$acl};
126             $resolver->$acl( delete $opts{$acl} );
127             }
128              
129             my $self = $class->SUPER::new(%opts);
130             $self->request_timeout($timeout);
131             $self->_elem("resolver", $resolver);
132              
133             LWPx::ParanoidHandler::make_paranoid($self, $self->_resolver);
134              
135             return $self;
136             }
137              
138             sub request_timeout { shift->_elem("request_timeout", @_) }
139             sub resolver {
140             my $self = shift;
141             Carp::croak("resolver is read-only; to use a new resolver, create a new user agent")
142             if @_;
143             return $self->_resolver;
144             }
145             sub _resolver {
146             shift->_elem("resolver");
147             }
148              
149             sub blocked_hosts { shift->_resolver->blocked_hosts(ref $_[0] ? $_[0] : \@_) }
150             sub whitelisted_hosts { shift->_resolver->whitelisted_hosts(ref $_[0] ? $_[0] : \@_) }
151              
152             sub __timed_out { Carp::croak("Client timed out request") }
153             sub __with_timeout {
154             my $method = shift;
155             my $self = shift;
156             my $SUPER = $self->can("SUPER::$method")
157             or Carp::croak("No such method '$method'");
158              
159             my $our_alarm = (
160             ref($SIG{ALRM}) eq "CODE"
161             and refaddr($SIG{ALRM}) eq refaddr(\&__timed_out)
162             );
163              
164             if (not $our_alarm) {
165             local $SIG{ALRM} = \&__timed_out;
166             alarm $self->request_timeout;
167             my $ret = $self->$SUPER(@_);
168             alarm 0;
169             return $ret;
170             } else {
171             return $self->$SUPER(@_);
172             }
173             };
174              
175             sub request { __with_timeout("request", @_) }
176             sub simple_request { __with_timeout("simple_request", @_) }
177              
178             "The truth is out there.";
179              
180             =head1 CAVEATS
181              
182             The overall request timeout is implemented using SIGALRM. Any C<$SIG{ALRM}>
183             handler from an outer scope is replaced in the scope of
184             L requests.
185              
186             =head1 BUGS
187              
188             All bugs should be reported via
189             L
190             or L.
191              
192             =head1 AUTHOR
193              
194             Thomas Sibley
195              
196             =head1 LICENSE AND COPYRIGHT
197            
198             This software is primarily Copyright (c) 2013 by Best Practical Solutions,
199             with parts of it Copyright (c) 2014-2015 by Thomas Sibley.
200            
201             This is free software, licensed under:
202            
203             The GNU General Public License, Version 2, June 1991
204              
205             =cut