File Coverage

blib/lib/LWP/Protocol/https/SocksChain.pm
Criterion Covered Total %
statement 21 215 9.7
branch 0 124 0.0
condition 0 51 0.0
subroutine 7 17 41.1
pod 1 1 100.0
total 29 408 7.1


line stmt bran cond sub pod time code
1             ########################################################################
2             #
3             # $Id: SocksChain.pm,v 1.8 2009-11-21 20:25:47 gosha Exp $
4             #
5             # Copyright (C) Igor V. Okunev goshaprv.mts-nn.ru 2005 - 2006
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             package LWP::Protocol::https::SocksChain;
13              
14 1     1   25434 use strict;
  1         3  
  1         48  
15 1     1   7 use vars qw( @ISA $VERSION @EXTRA_SOCK_OPTS );
  1         2  
  1         77  
16 1     1   1059 use LWP::Protocol::http;
  1         126883  
  1         7943  
17              
18             @ISA = qw( LWP::Protocol::http );
19              
20             ($VERSION='$Revision: 1.8 $')=~s/^\S+\s+(\S+)\s+.*/$1/;
21              
22             local $^W = 1;
23              
24             my $CRLF = "\015\012";
25              
26             sub _new_socket
27             {
28 0     0     my($self, $host, $port, $timeout) = @_;
29 0           my $conn_cache = $self->{ua}{conn_cache};
30 0 0         if ($conn_cache) {
31 0 0         if (my $sock = $conn_cache->withdraw("http", "$host:$port")) {
32 0 0 0       return $sock if $sock && !$sock->can_read(0);
33             # if the socket is readable, then either the peer has closed the
34             # connection or there are some garbage bytes on it. In either
35             # case we abandon it.
36 0           $sock->close;
37             }
38             }
39              
40 0           local($^W) = 0;
41              
42 0           my $sock = $self->socket_class->new(
43             PeerHost => $host,
44             PeerPort => $port,
45             TimeOut => $timeout,
46             KeepAlive => !!$conn_cache,
47             SendTE => 1,
48             $self->_extra_sock_opts($host, $port),
49             );
50              
51 0 0         unless ($sock) {
52             # IO::Socket leaves additional error messages in $@
53 0           $@ =~ s/^.*?: //;
54 0           die "Can't connect to $host:$port ($@)";
55             }
56              
57             # perl 5.005's IO::Socket does not have the blocking method.
58 0           eval { $sock->blocking(0); };
  0            
59              
60 0           return $sock;
61             }
62              
63             sub _extra_sock_opts # to be overridden by subclass
64             {
65 0     0     return @EXTRA_SOCK_OPTS;
66             }
67              
68             sub request
69             {
70 0     0 1   my($self, $request, undef, $arg, $size, $timeout) = @_;
71              
72 0   0       $size ||= 4096;
73              
74             # check method
75 0           my $method = $request->method;
76 0 0         unless ($method =~ /^[A-Za-z0-9_!\#\$%&\'*+\-.^\`|~]+$/) { # HTTP token
77 0           return new HTTP::Response &HTTP::Status::RC_BAD_REQUEST,
78             'Library does not allow method ' .
79             "$method for 'http:' URLs";
80             }
81              
82 0           my $url = $request->url;
83 0           my($host, $port, $fullpath);
84              
85 0           $host = $url->host;
86 0           $port = $url->port;
87 0           $fullpath = $url->path_query;
88 0 0         $fullpath = "/" unless length $fullpath;
89              
90             # connect to remote site
91 0           my $socket = $self->_new_socket( $host, $port, $timeout );
92 0           $self->_check_sock($request, $socket);
93              
94 0           my @h;
95 0           my $request_headers = $request->headers->clone;
96 0           $self->_fixup_header($request_headers, $url);
97              
98             $request_headers->scan(sub {
99 0     0     my($k, $v) = @_;
100 0           $v =~ s/\n/ /g;
101 0           push(@h, $k, $v);
102 0           });
103              
104 0           my $content_ref = $request->content_ref;
105 0 0         $content_ref = $$content_ref if ref($$content_ref);
106 0           my $chunked;
107             my $has_content;
108              
109 0 0         if (ref($content_ref) eq 'CODE') {
110 0           my $clen = $request_headers->header('Content-Length');
111 0 0         $has_content++ if $clen;
112 0 0         unless (defined $clen) {
113 0           push(@h, "Transfer-Encoding" => "chunked");
114 0           $has_content++;
115 0           $chunked++;
116             }
117             }
118             else {
119             # Set (or override) Content-Length header
120 0           my $clen = $request_headers->header('Content-Length');
121 0 0 0       if (defined($$content_ref) && length($$content_ref)) {
    0          
122 0           $has_content++;
123 0 0 0       if (!defined($clen) || $clen ne length($$content_ref)) {
124 0 0         if (defined $clen) {
125 0           warn "Content-Length header value was wrong, fixed";
126 0           hlist_remove(\@h, 'Content-Length');
127             }
128 0           push(@h, 'Content-Length' => length($$content_ref));
129             }
130             }
131             elsif ($clen) {
132 0           warn "Content-Length set when there is not content, fixed";
133 0           hlist_remove(\@h, 'Content-Length');
134             }
135             }
136              
137 0   0       my $req_buf = $socket->format_request($method, $fullpath, @h) || die $!;
138              
139             # print STDERR "------\n$req_buf\n------\n";
140              
141             # XXX need to watch out for write timeouts
142             {
143 0           my $n = $socket->syswrite($req_buf, length($req_buf));
  0            
144            
145 0 0         die $! unless defined($n);
146 0 0         die "short write" unless $n == length($req_buf);
147             }
148              
149 0           my($code, $mess, @junk);
150 0           my $drop_connection;
151              
152 0 0         if ($has_content) {
153 0           my $write_wait = 0;
154 0 0 0       $write_wait = 2
155             if ($request_headers->header("Expect") || "") =~ /100-continue/;
156              
157 0           my $eof;
158             my $wbuf;
159 0           my $woffset = 0;
160 0 0         if (ref($content_ref) eq 'CODE') {
161 0           my $buf = &$content_ref();
162 0 0         $buf = "" unless defined($buf);
163 0 0         $buf = sprintf "%x%s%s%s", length($buf), $CRLF, $buf, $CRLF
164             if $chunked;
165 0           $wbuf = \$buf;
166             }
167             else {
168 0           $wbuf = $content_ref;
169 0           $eof = 1;
170             }
171              
172 0           my $fbits = '';
173 0           vec($fbits, fileno($socket), 1) = 1;
174              
175 0           while ($woffset < length($$wbuf)) {
176              
177 0           my $time_before;
178 0           my $sel_timeout = $timeout;
179 0 0         if ($write_wait) {
180 0           $time_before = time;
181 0 0         $sel_timeout = $write_wait if $write_wait < $sel_timeout;
182             }
183              
184 0           my $rbits = $fbits;
185 0 0         my $wbits = $write_wait ? undef : $fbits;
186 0           my $nfound = select($rbits, $wbits, undef, $sel_timeout);
187 0 0         unless (defined $nfound) {
188 0           die "select failed: $!";
189             }
190              
191 0 0         if ($write_wait) {
192 0           $write_wait -= time - $time_before;
193 0 0         $write_wait = 0 if $write_wait < 0;
194             }
195              
196 0 0 0       if (defined($rbits) && $rbits =~ /[^\0]/) {
197             # readable
198 0           my $buf = $socket->_rbuf;
199 0           my $n = $socket->sysread($buf, 1024, length($buf));
200 0 0         unless ($n) {
201 0           die "EOF";
202             }
203 0           $socket->_rbuf($buf);
204 0 0         if ($buf =~ /\015?\012\015?\012/) {
205             # a whole response present
206 0           ($code, $mess, @h) = $socket->read_response_headers(laxed => 1,
207             junk_out => \@junk,
208             );
209 0 0         if ($code eq "100") {
210 0           $write_wait = 0;
211 0           undef($code);
212             }
213             else {
214 0           $drop_connection++;
215 0           last;
216             # XXX should perhaps try to abort write in a nice way too
217             }
218             }
219             }
220 0 0 0       if (defined($wbits) && $wbits =~ /[^\0]/) {
221 0           my $n = $socket->syswrite($$wbuf, length($$wbuf), $woffset);
222 0 0         unless ($n) {
223 0 0         die "syswrite: $!" unless defined $n;
224 0           die "syswrite: no bytes written";
225             }
226 0           $woffset += $n;
227              
228 0 0 0       if (!$eof && $woffset >= length($$wbuf)) {
229             # need to refill buffer from $content_ref code
230 0           my $buf = &$content_ref();
231 0 0         $buf = "" unless defined($buf);
232 0 0         $eof++ unless length($buf);
233 0 0         $buf = sprintf "%x%s%s%s", length($buf), $CRLF, $buf, $CRLF
234             if $chunked;
235 0           $wbuf = \$buf;
236 0           $woffset = 0;
237             }
238             }
239             }
240             }
241 0 0         ($code, $mess, @h) = $socket->read_response_headers(laxed => 1, junk_out => \@junk)
242             unless $code;
243 0 0         ($code, $mess, @h) = $socket->read_response_headers(laxed => 1, junk_out => \@junk)
244             if $code eq "100";
245              
246 0           my $response = HTTP::Response->new($code, $mess);
247 0           my $peer_http_version = $socket->peer_http_version;
248 0           $response->protocol("HTTP/$peer_http_version");
249 0           while (@h) {
250 0           my($k, $v) = splice(@h, 0, 2);
251 0           $response->push_header($k, $v);
252             }
253 0 0         $response->push_header("Client-Junk" => \@junk) if @junk;
254              
255 0           $response->request($request);
256 0           $self->_get_sock_info($response, $socket);
257              
258 0 0         if ($method eq "CONNECT") {
259 0           $response->{client_socket} = $socket; # so it can be picked up
260 0           return $response;
261             }
262              
263 0 0         if (my @te = $response->remove_header('Transfer-Encoding')) {
264 0           $response->push_header('Client-Transfer-Encoding', \@te);
265             }
266 0           $response->push_header('Client-Response-Num', $socket->increment_response_count);
267              
268 0           my $complete;
269             $response = $self->collect($arg, $response, sub {
270 0     0     my $buf = ""; #prevent use of uninitialized value in SSLeay.xs
271 0           my $n;
272 0           READ:
273             {
274 0           $n = $socket->read_entity_body($buf, $size);
275 0 0         die "Can't read entity body: $!" unless defined $n;
276 0 0         redo READ if $n == -1;
277             }
278 0 0         $complete++ if !$n;
279 0           return \$buf;
280 0           } );
281 0 0         $drop_connection++ unless $complete;
282              
283 0           @h = $socket->get_trailers;
284 0           while (@h) {
285 0           my($k, $v) = splice(@h, 0, 2);
286 0           $response->push_header($k, $v);
287             }
288              
289             # keep-alive support
290 0 0         unless ($drop_connection) {
291 0 0         if (my $conn_cache = $self->{ua}{conn_cache}) {
292 0   0       my %connection = map { (lc($_) => 1) }
  0            
293             split(/\s*,\s*/, ($response->header("Connection") || ""));
294 0 0 0       if (($peer_http_version eq "1.1" && !$connection{close}) ||
      0        
295             $connection{"keep-alive"})
296             {
297 0           $conn_cache->deposit("http", "$host:$port", $socket);
298             }
299             }
300             }
301              
302 0           $response;
303             }
304              
305             sub _check_sock
306             {
307 0     0     my($self, $req, $sock) = @_;
308 0           my $check = $req->header("If-SSL-Cert-Subject");
309 0 0         if (defined $check) {
310 0   0       my $cert = $sock->get_peer_certificate ||
311             die "Missing SSL certificate";
312 0           my $subject = $cert->subject_name;
313 0 0         die "Bad SSL certificate subject: '$subject' !~ /$check/"
314             unless $subject =~ /$check/;
315 0           $req->remove_header("If-SSL-Cert-Subject"); # don't pass it on
316             }
317             }
318              
319             sub _get_sock_info
320             {
321 0     0     my $self = shift;
322             #$self->SUPER::_get_sock_info(@_);
323 0           my($res, $sock) = @_;
324 0           $res->header("Client-SSL-Cipher" => $sock->get_cipher);
325 0           my $cert = $sock->get_peer_certificate;
326 0 0         if ($cert) {
327 0           $res->header("Client-SSL-Cert-Subject" => $cert->subject_name);
328 0           $res->header("Client-SSL-Cert-Issuer" => $cert->issuer_name);
329             }
330 0           $res->header("Client-SSL-Warning" => "Peer certificate not verified");
331             }
332              
333              
334             #-----------------------------------------------------------
335             package LWP::Protocol::https::SocksChain::Socket;
336 1     1   1082 use Net::SC;
  1         32011  
  1         216  
337 1     1   1616 use IO::Socket::SSL;
  1         102894  
  1         12  
338 1     1   1156 use Net::HTTPS;
  1         639  
  1         11  
339 1     1   56 use vars qw( @ISA );
  1         4  
  1         567  
340             @ISA = (
341             'LWP::Protocol::http::SocketMethods',
342             'Net::HTTPS'
343             );
344              
345 0     0     sub IO::Socket::SSL::SSL_HANDLE::READ { ${shift()}->read (@_) }
  0            
346              
347             sub new {
348 0     0     my ( $self, %cfg ) = @_;
349              
350 0           my $host = $cfg{ PeerHost };
351 0           my $port = $cfg{ PeerPort };
352              
353             #
354             # client certificate support
355             #
356 0 0 0       if ( defined $ENV{HTTPS_KEY_FILE} and not exists $cfg{SSL_key_file} ) {
357 0           $cfg{SSL_key_file} = $ENV{HTTPS_KEY_FILE};
358             }
359              
360 0 0 0       if ( defined $ENV{HTTPS_CA_DIR} and not exists $cfg{SSL_ca_path} ) {
361 0           $cfg{SSL_ca_path} = $ENV{HTTPS_CA_DIR};
362             }
363              
364 0 0 0       if ( defined $ENV{HTTPS_CA_FILE} and not exists $cfg{SSL_ca_file} ) {
365 0           $cfg{SSL_ca_file} = $ENV{HTTPS_CA_FILE};
366             }
367              
368 0 0 0       if ( defined $ENV{HTTPS_CERT_FILE} and not exists $cfg{SSL_cert_file} ) {
369 0           $cfg{SSL_cert_file} = $ENV{HTTPS_CERT_FILE};
370             }
371              
372 0 0 0       if ( not exists $cfg{SSL_use_cert} and exists $cfg{SSL_cert_file} ) {
373 0           $cfg{SSL_use_cert} = 1
374             }
375              
376 0   0       my $sc = Net::SC->new( %cfg ) || die $!;
377            
378 0 0         unless ( ( my $rc = $sc->connect( $host, $port ) ) == SOCKS_OKAY ) {
379 0           die socks_error($rc) . "\n";
380             }
381              
382 0           my $obj = bless $sc->sh, $self;
383            
384 0           $obj->http_configure(\%cfg);
385              
386 0 0         if ( $IO::Socket::SSL::VERSION > 0.97 ) {
387 0 0         $obj->configure_SSL( \%cfg ) && $obj->connect_SSL();
388             } else {
389 0 0         $obj->configure_SSL( \%cfg ) && $obj->connect_SSL($sc->sh);
390             }
391             }
392              
393             sub http_connect {
394 0     0     return shift;
395             }
396              
397             1;
398              
399             __END__