File Coverage

blib/lib/LWP/Protocol/https/SocksChain10.pm
Criterion Covered Total %
statement 24 173 13.8
branch 0 94 0.0
condition 0 66 0.0
subroutine 8 16 50.0
pod 1 1 100.0
total 33 350 9.4


line stmt bran cond sub pod time code
1             ########################################################################
2             #
3             # $Id: SocksChain10.pm,v 1.7 2009-11-21 20:25:47 gosha Exp $
4             #
5             # Copyright (C) Igor V. Okunev goshaprv.mts-nn.ru 2005
6             #
7             # All rights reserved. This library is free software;
8             # you can redistribute it and/or modify it under the
9             # same terms as Perl itself.
10             #
11             ########################################################################
12              
13             package LWP::Protocol::https::SocksChain10;
14              
15 1     1   64842 use strict;
  1         3  
  1         44  
16 1     1   6 use vars qw( @ISA $VERSION @EXTRA_SOCK_OPTS );
  1         2  
  1         78  
17              
18 1     1   16294 use Net::SC;
  1         51834  
  1         240  
19              
20 1     1   1167 use HTTP::Response;
  1         115148  
  1         47  
21 1     1   14 use HTTP::Status;
  1         2  
  1         425  
22 1     1   1067 use IO::Select;
  1         1860  
  1         58  
23 1     1   12181 use IO::Socket::SSL;
  1         134635  
  1         13  
24 1     1   12036 use LWP::Protocol;
  1         4810  
  1         2337  
25              
26             ($VERSION='$Revision: 1.7 $')=~s/^\S+\s+(\S+)\s+.*/$1/;
27              
28             local $^W = 1;
29              
30             @ISA = qw(
31             LWP::Protocol
32             IO::Socket::SSL
33             );
34              
35 0     0     sub IO::Socket::SSL::SSL_HANDLE::READ { ${shift()}->read (@_) }
  0            
36              
37             my $CRLF = "\015\012";
38              
39             sub _new_socket
40             {
41 0     0     my($self, $host, $port, $timeout ) = @_;
42              
43 0           my %cfg = (
44             PeerHost => $host,
45             PeerPort => $port,
46             timeout => $timeout,
47             $self->_extra_sock_opts($host, $port) );
48              
49             #
50             # client certificate support
51             #
52 0 0 0       if ( defined $ENV{HTTPS_KEY_FILE} and not exists $cfg{SSL_key_file} ) {
53 0           $cfg{SSL_key_file} = $ENV{HTTPS_KEY_FILE};
54             }
55              
56 0 0 0       if ( defined $ENV{HTTPS_CA_DIR} and not exists $cfg{SSL_ca_path} ) {
57 0           $cfg{SSL_ca_path} = $ENV{HTTPS_CA_DIR};
58             }
59              
60 0 0 0       if ( defined $ENV{HTTPS_CA_FILE} and not exists $cfg{SSL_ca_file} ) {
61 0           $cfg{SSL_ca_file} = $ENV{HTTPS_CA_FILE};
62             }
63              
64 0 0 0       if ( defined $ENV{HTTPS_CERT_FILE} and not exists $cfg{SSL_cert_file} ) {
65 0           $cfg{SSL_cert_file} = $ENV{HTTPS_CERT_FILE};
66             }
67              
68 0 0 0       if ( not exists $cfg{SSL_use_cert} and exists $cfg{SSL_cert_file} ) {
69 0           $cfg{SSL_use_cert} = 1
70             }
71              
72 0   0       my $sc = Net::SC->new( %cfg ) || die $!;
73              
74 0 0         unless ( ( my $rc = $sc->connect( $host, $port ) ) == SOCKS_OKAY ) {
75 0           die socks_error($rc) . "\n";
76             }
77              
78 0           my $obj = bless $sc->sh;
79              
80 0 0         if ( $IO::Socket::SSL::VERSION > 0.97 ) {
81 0 0         $obj->configure_SSL( \%cfg ) && $obj->connect_SSL();
82             } else {
83 0 0         $obj->configure_SSL( \%cfg ) && $obj->connect_SSL($sc->sh);
84             }
85              
86 0 0         unless ($obj) {
87             # IO::Socket leaves additional error messages in $@
88 0           $@ =~ s/^.*?: //;
89 0           die "Can't connect to $host:$port ($@)";
90             }
91              
92             # perl 5.005's IO::Socket does not have the blocking method.
93 0           eval { $obj->blocking(0); };
  0            
94              
95 0           return $obj;
96             }
97              
98             sub _extra_sock_opts # to be overridden by subclass
99             {
100 0     0     return @EXTRA_SOCK_OPTS;
101             }
102              
103             sub _check_sock
104             {
105 0     0     my($self, $req, $sock) = @_;
106 0           my $check = $req->header("If-SSL-Cert-Subject");
107 0 0         if (defined $check) {
108 0   0       my $cert = $sock->get_peer_certificate ||
109             die "Missing SSL certificate";
110 0           my $subject = $cert->subject_name;
111 0 0         die "Bad SSL certificate subject: '$subject' !~ /$check/"
112             unless $subject =~ /$check/;
113 0           $req->remove_header("If-SSL-Cert-Subject"); # don't pass it on
114             }
115             }
116              
117             sub _get_sock_info
118             {
119 0     0     my $self = shift;
120             #$self->SUPER::_get_sock_info(@_);
121 0           my($res, $sock) = @_;
122 0           $res->header("Client-SSL-Cipher" => $sock->get_cipher);
123 0           my $cert = $sock->get_peer_certificate;
124 0 0         if ($cert) {
125 0           $res->header("Client-SSL-Cert-Subject" => $cert->subject_name);
126 0           $res->header("Client-SSL-Cert-Issuer" => $cert->issuer_name);
127             }
128 0           $res->header("Client-SSL-Warning" => "Peer certificate not verified");
129             }
130              
131             sub _fixup_header
132             {
133 0     0     my($self, $h, $url) = @_;
134              
135 0           $h->remove_header('Connection'); # need support here to be useful
136              
137             # HTTP/1.1 will require us to send the 'Host' header, so we might
138             # as well start now.
139 0           my $hhost = $url->authority;
140            
141 0           $hhost =~ s/^([^\@]*)\@//; # get rid of potential "user:pass@"
142 0 0         $h->header('Host' => $hhost) unless defined $h->header('Host');
143              
144             # add authorization header if we need them. HTTP URLs do
145             # not really support specification of user and password, but
146             # we allow it.
147 0 0 0       if (defined($1) && not $h->header('Authorization')) {
148 0           require URI::Escape;
149 0           $h->authorization_basic(map URI::Escape::uri_unescape($_),
150             split(":", $1, 2));
151             }
152             }
153              
154             sub request
155             {
156 0     0 1   my($self, $request, undef, $arg, $size, $timeout) = @_;
157              
158 0   0       $size ||= 4096;
159              
160             # check method
161 0           my $method = $request->method;
162              
163 0 0         unless ($method =~ /^[A-Za-z0-9_!\#\$%&\'*+\-.^\`|~]+$/) { # HTTP token
164 0           return new HTTP::Response &HTTP::Status::RC_BAD_REQUEST,
165             'Library does not allow method ' .
166             "$method for 'http:' URLs";
167             }
168              
169 0           my $url = $request->url;
170              
171 0           my $host = $url->host;
172 0           my $port = $url->port;
173 0           my $fullpath = $url->path_query;
174              
175 0 0         $fullpath = "/" unless length $fullpath;
176              
177             # connect to remote site
178 0           my $socket = $self->_new_socket( $host, $port, $timeout );
179              
180 0           $self->_check_sock($request, $socket);
181              
182 0 0         my $sel = IO::Select->new($socket) if $timeout;
183              
184 0           my $request_line = "$method $fullpath HTTP/1.0$CRLF";
185              
186 0           my $h = $request->headers->clone;
187 0           my $cont_ref = $request->content_ref;
188              
189 0 0         $cont_ref = $$cont_ref if ref($$cont_ref);
190            
191 0           my $ctype = ref($cont_ref);
192              
193             # If we're sending content we *have* to specify a content length
194             # otherwise the server won't know a messagebody is coming.
195 0 0         if ($ctype eq 'CODE') {
196 0 0 0       die 'No Content-Length header for request with dynamic content'
197             unless defined($h->header('Content-Length')) ||
198             $h->content_type =~ /^multipart\//;
199             # For HTTP/1.1 we could have used chunked transfer encoding...
200             } else {
201 0 0 0       $h->header('Content-Length' => length $$cont_ref)
202             if defined($$cont_ref) && length($$cont_ref);
203             }
204              
205 0           $self->_fixup_header($h, $url);
206              
207 0           my $buf = $request_line . $h->as_string($CRLF) . $CRLF;
208 0           my $n; # used for return value from syswrite/sysread
209             my $length;
210 0           my $offset;
211              
212             # syswrite $buf
213 0           $length = length($buf);
214 0           $offset = 0;
215            
216 0           while ( $offset < $length ) {
217 0 0 0       die "write timeout" if $timeout && !$sel->can_write($timeout);
218 0           $n = $socket->syswrite($buf, $length-$offset, $offset );
219 0 0         die $! unless defined($n);
220 0           $offset += $n;
221             }
222            
223 0 0 0       if ($ctype eq 'CODE') {
    0          
224 0   0       while ( ($buf = &$cont_ref()), defined($buf) && length($buf)) {
225             # syswrite $buf
226 0           $length = length($buf);
227 0           $offset = 0;
228 0           while ( $offset < $length ) {
229 0 0 0       die "write timeout" if $timeout && !$sel->can_write($timeout);
230 0           $n = $socket->syswrite($buf, $length-$offset, $offset );
231 0 0         die $! unless defined($n);
232 0           $offset += $n;
233             }
234             }
235             } elsif (defined($$cont_ref) && length($$cont_ref)) {
236             # syswrite $$cont_ref
237 0           $length = length($$cont_ref);
238 0           $offset = 0;
239 0           while ( $offset < $length ) {
240 0 0 0       die "write timeout" if $timeout && !$sel->can_write($timeout);
241 0           $n = $socket->syswrite($$cont_ref, $length-$offset, $offset );
242 0 0         die $! unless defined($n);
243 0           $offset += $n;
244             }
245             }
246              
247             # read response line from server
248              
249 0           my $response;
250 0           $buf = '';
251              
252             # Inside this loop we will read the response line and all headers
253             # found in the response.
254 0           while (1) {
255 0 0 0       die "read timeout" if $timeout && !$sel->can_read($timeout);
256 0           $n = $socket->sysread($buf, $size, length($buf));
257 0 0         die $! unless defined($n);
258 0 0         die "unexpected EOF before status line seen" unless $n;
259              
260 0 0 0       if ($buf =~ s/^(HTTP\/\d+\.\d+)[ \t]+(\d+)[ \t]*([^\012]*)\012//) {
    0 0        
261             # HTTP/1.0 response or better
262 0           my($ver,$code,$msg) = ($1, $2, $3);
263 0           $msg =~ s/\015$//;
264 0           $response = HTTP::Response->new($code, $msg);
265 0           $response->protocol($ver);
266              
267             # ensure that we have read all headers. The headers will be
268             # terminated by two blank lines
269 0   0       until ($buf =~ /^\015?\012/ || $buf =~ /\015?\012\015?\012/) {
270             # must read more if we can...
271 0 0 0       die "read timeout" if $timeout && !$sel->can_read($timeout);
272 0           my $old_len = length($buf);
273 0           $n = $socket->sysread($buf, $size, $old_len);
274 0 0         die $! unless defined($n);
275 0 0         die "unexpected EOF before all headers seen" unless $n;
276             }
277              
278             # now we start parsing the headers. The strategy is to
279             # remove one line at a time from the beginning of the header
280             # buffer ($res).
281 0           my($key, $val);
282 0           while ($buf =~ s/([^\012]*)\012//) {
283 0           my $line = $1;
284              
285             # if we need to restore as content when illegal headers
286             # are found.
287 0           my $save = "$line\012";
288            
289 0           $line =~ s/\015$//;
290 0 0         last unless length $line;
291              
292 0 0 0       if ($line =~ /^([a-zA-Z0-9_\-.]+)\s*:\s*(.*)/) {
    0          
293 0 0         $response->push_header($key, $val) if $key;
294 0           ($key, $val) = ($1, $2);
295             } elsif ($line =~ /^\s+(.*)/ && $key) {
296 0           $val .= " $1";
297             } else {
298 0           $response->push_header("Client-Bad-Header-Line" => $line);
299             }
300             }
301 0 0         $response->push_header($key, $val) if $key;
302 0           last;
303            
304             } elsif ((length($buf) >= 5 and $buf !~ /^HTTP\//) or
305             $buf =~ /\012/ ) {
306             # HTTP/0.9 or worse
307 0           $response = HTTP::Response->new(&HTTP::Status::RC_OK, "OK");
308 0           $response->protocol('HTTP/0.9');
309 0           last;
310              
311             } else {
312             # need more data
313             }
314             };
315 0           $response->request($request);
316 0           $self->_get_sock_info($response, $socket);
317              
318 0 0         if ($method eq "CONNECT") {
319 0           $response->{client_socket} = $socket; # so it can be picked up
320 0           $response->content($buf); # in case we read more than the headers
321 0           return $response;
322             }
323              
324 0           my $usebuf = length($buf) > 0;
325             $response = $self->collect($arg, $response, sub {
326 0 0   0     if ($usebuf) {
327 0           $usebuf = 0;
328 0           return \$buf;
329             }
330 0 0 0       die "read timeout" if $timeout && !$sel->can_read($timeout);
331 0           my $n = $socket->sysread($buf, $size);
332 0 0         die $! unless defined($n);
333 0           return \$buf;
334 0           } );
335              
336             # $socket->close;
337              
338 0           $response;
339             }
340              
341             1;
342              
343             __END__