File Coverage

blib/lib/LWP/Protocol/http/SocksChain.pm
Criterion Covered Total %
statement 15 180 8.3
branch 0 102 0.0
condition 0 34 0.0
subroutine 5 12 41.6
pod 1 1 100.0
total 21 329 6.3


line stmt bran cond sub pod time code
1             ########################################################################
2             #
3             # $Id: SocksChain.pm,v 1.7 2009-12-28 15:12:16 gosha Exp $
4             #
5             # Copyright (C) Igor V. Okunev goshaprv.mts-nn.ru 2005 - 2009
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::http::SocksChain;
13              
14 1     1   27681 use strict;
  1         2  
  1         43  
15 1     1   5 use vars qw( @ISA $VERSION @EXTRA_SOCK_OPTS );
  1         2  
  1         72  
16 1     1   970 use LWP::Protocol::http;
  1         156987  
  1         1904  
17              
18             @ISA = qw( LWP::Protocol::http );
19              
20             ($VERSION='$Revision: 1.7 $')=~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 0 0         die $! unless defined($n);
145 0 0         die "short write" unless $n == length($req_buf);
146             }
147              
148 0           my($code, $mess, @junk);
149 0           my $drop_connection;
150              
151 0 0         if ($has_content) {
152 0           my $write_wait = 0;
153 0 0 0       $write_wait = 2
154             if ($request_headers->header("Expect") || "") =~ /100-continue/;
155              
156 0           my $eof;
157             my $wbuf;
158 0           my $woffset = 0;
159 0 0         if (ref($content_ref) eq 'CODE') {
160 0           my $buf = &$content_ref();
161 0 0         $buf = "" unless defined($buf);
162 0 0         $buf = sprintf "%x%s%s%s", length($buf), $CRLF, $buf, $CRLF
163             if $chunked;
164 0           $wbuf = \$buf;
165             }
166             else {
167 0           $wbuf = $content_ref;
168 0           $eof = 1;
169             }
170              
171 0           my $fbits = '';
172 0           vec($fbits, fileno($socket), 1) = 1;
173              
174 0           while ($woffset < length($$wbuf)) {
175              
176 0           my $time_before;
177 0           my $sel_timeout = $timeout;
178 0 0         if ($write_wait) {
179 0           $time_before = time;
180 0 0         $sel_timeout = $write_wait if $write_wait < $sel_timeout;
181             }
182              
183 0           my $rbits = $fbits;
184 0 0         my $wbits = $write_wait ? undef : $fbits;
185 0           my $nfound = select($rbits, $wbits, undef, $sel_timeout);
186 0 0         unless (defined $nfound) {
187 0           die "select failed: $!";
188             }
189              
190 0 0         if ($write_wait) {
191 0           $write_wait -= time - $time_before;
192 0 0         $write_wait = 0 if $write_wait < 0;
193             }
194              
195 0 0 0       if (defined($rbits) && $rbits =~ /[^\0]/) {
196             # readable
197 0           my $buf = $socket->_rbuf;
198 0           my $n = $socket->sysread($buf, 1024, length($buf));
199 0 0         unless ($n) {
200 0           die "EOF";
201             }
202 0           $socket->_rbuf($buf);
203 0 0         if ($buf =~ /\015?\012\015?\012/) {
204             # a whole response present
205 0           ($code, $mess, @h) = $socket->read_response_headers(laxed => 1,
206             junk_out => \@junk,
207             );
208 0 0         if ($code eq "100") {
209 0           $write_wait = 0;
210 0           undef($code);
211             }
212             else {
213 0           $drop_connection++;
214 0           last;
215             # XXX should perhaps try to abort write in a nice way too
216             }
217             }
218             }
219 0 0 0       if (defined($wbits) && $wbits =~ /[^\0]/) {
220 0           my $n = $socket->syswrite($$wbuf, length($$wbuf), $woffset);
221 0 0         unless ($n) {
222 0 0         die "syswrite: $!" unless defined $n;
223 0           die "syswrite: no bytes written";
224             }
225 0           $woffset += $n;
226              
227 0 0 0       if (!$eof && $woffset >= length($$wbuf)) {
228             # need to refill buffer from $content_ref code
229 0           my $buf = &$content_ref();
230 0 0         $buf = "" unless defined($buf);
231 0 0         $eof++ unless length($buf);
232 0 0         $buf = sprintf "%x%s%s%s", length($buf), $CRLF, $buf, $CRLF
233             if $chunked;
234 0           $wbuf = \$buf;
235 0           $woffset = 0;
236             }
237             }
238             }
239             }
240 0 0         ($code, $mess, @h) = $socket->read_response_headers(laxed => 1, junk_out => \@junk)
241             unless $code;
242 0 0         ($code, $mess, @h) = $socket->read_response_headers(laxed => 1, junk_out => \@junk)
243             if $code eq "100";
244              
245 0           my $response = HTTP::Response->new($code, $mess);
246 0           my $peer_http_version = $socket->peer_http_version;
247 0           $response->protocol("HTTP/$peer_http_version");
248 0           while (@h) {
249 0           my($k, $v) = splice(@h, 0, 2);
250 0           $response->push_header($k, $v);
251             }
252 0 0         $response->push_header("Client-Junk" => \@junk) if @junk;
253              
254 0           $response->request($request);
255 0           $self->_get_sock_info($response, $socket);
256              
257 0 0         if ($method eq "CONNECT") {
258 0           $response->{client_socket} = $socket; # so it can be picked up
259 0           return $response;
260             }
261              
262 0 0         if (my @te = $response->remove_header('Transfer-Encoding')) {
263 0           $response->push_header('Client-Transfer-Encoding', \@te);
264             }
265 0           $response->push_header('Client-Response-Num', $socket->increment_response_count);
266              
267 0           my $complete;
268             $response = $self->collect($arg, $response, sub {
269 0     0     my $buf = ""; #prevent use of uninitialized value in SSLeay.xs
270 0           my $n;
271 0           READ:
272             {
273 0           $n = $socket->read_entity_body($buf, $size);
274 0 0         die "Can't read entity body: $!" unless defined $n;
275 0 0         redo READ if $n == -1;
276             }
277 0 0         $complete++ if !$n;
278 0           return \$buf;
279 0           } );
280 0 0         $drop_connection++ unless $complete;
281              
282 0           @h = $socket->get_trailers;
283 0           while (@h) {
284 0           my($k, $v) = splice(@h, 0, 2);
285 0           $response->push_header($k, $v);
286             }
287              
288             # keep-alive support
289 0 0         unless ($drop_connection) {
290 0 0         if (my $conn_cache = $self->{ua}{conn_cache}) {
291 0   0       my %connection = map { (lc($_) => 1) }
  0            
292             split(/\s*,\s*/, ($response->header("Connection") || ""));
293 0 0 0       if (($peer_http_version eq "1.1" && !$connection{close}) ||
      0        
294             $connection{"keep-alive"})
295             {
296 0           $conn_cache->deposit("http", "$host:$port", $socket);
297             }
298             }
299             }
300              
301 0           $response;
302             }
303              
304             #-----------------------------------------------------------
305             package LWP::Protocol::http::SocksChain::Socket;
306 1     1   1177 use Net::SC;
  1         14422  
  1         187  
307 1     1   12 use vars qw(@ISA);
  1         2  
  1         227  
308             @ISA = qw( LWP::Protocol::http::SocketMethods
309             Net::HTTP
310             );
311              
312             sub new {
313 0     0     my ( $self, %cfg ) = @_;
314            
315 0           my $host = $cfg{ PeerHost };
316 0           my $port = $cfg{ PeerPort };
317            
318 0   0       my $sc = Net::SC->new( %cfg ) || die $!;
319            
320 0 0         unless ( ( my $rc = $sc->connect( $host, $port ) ) == SOCKS_OKAY ) {
321 0           die socks_error($rc) . "\n";
322             }
323              
324 0           my $obj = bless $sc->sh, $self;
325            
326 0           $obj->http_configure( \%cfg );
327            
328 0           return $obj;
329             }
330              
331             sub http_connect {
332 0     0     return shift;
333             }
334              
335             1;
336              
337             __END__