File Coverage

script/selective_proxy
Criterion Covered Total %
statement 45 102 44.1
branch 0 20 0.0
condition 0 6 0.0
subroutine 15 19 78.9
pod n/a
total 60 147 40.8


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2 1     1   1514 use strict;
  1         3  
  1         48  
3 1     1   9 use warnings;
  1         2  
  1         43  
4 1     1   1751 use HTTP::Proxy;
  1         135693  
  1         49  
5 1     1   954 use Config::Tiny;
  1         1016  
  1         28  
6 1     1   7 use HTTP::Proxy::Selective;
  1         2  
  1         24  
7 1     1   6 use LWP::UserAgent;
  1         2  
  1         21  
8 1     1   1576 use IO::Socket::SSL;
  1         88538  
  1         11  
9 1     1   248 use IO::Socket::INET;
  1         3  
  1         16  
10 1     1   828 use Net::SSLeay;
  1         8  
  1         37  
11 1     1   8 use File::Temp;
  1         2  
  1         458  
12              
13             # For PAR
14             my $sep = '/';
15             if ($^O =~ /WIN32/i) {
16             $sep = "\\";
17             }
18             require join($sep, qw/HTTP Proxy Engine NoFork.pm/);
19              
20             # Monkeypatch HTTP::Proxy to handle CONNECT as I want to.
21             my ($key, $cert);
22             {
23             my $key_temp = File::Temp->new( UNLINK => 0 );
24             print $key_temp q{-----BEGIN RSA PRIVATE KEY-----
25             MIIEowIBAAKCAQEAshDKYNsCd+ETRUITIg1U3Tg4uy/vXJkN3ZZS14LSbcFpnwzi
26             nMxFD4A/g/dSphHWxl/yZegDVz3ZWIV0En62YC7PfYwJWWd/4YLvDenQAEWz7cNT
27             kBzXqQwqirjDqEKXDyQQZ4jFLR3EwYafjrD99h71JEjuOa+ZZ0rgLu2CPhH5MxEV
28             WjSz0tSFU77bZNZKdYFdeKtZv0Ez4JGyTlVu8dwfsnfMpoyVL/c4xCXsJ+kNcnLA
29             p4RGjYrUTmh/XrYK07QuPjUhPPXylTYKrzYCchjMRZjAmz5EvXSbXl6CTn0JOUEt
30             YVvkJGNdd14jKez5ioDf1+gnX7nh20uog6ks9QIDAQABAoIBACBNfXk+od7/fNB2
31             oSPvSTLsjRYgJwskVOia6aJhAC2bBb8txjptsCWUvXECQAMSf2TzaPTltx1vgetW
32             Im1sgUdHlqqO6e9HIGLXruhWPz6dZnu+kH03TkRDicAqrovqsJ61iyhNHoAFw3jc
33             JDvtjdTFXvFbLaRXX7vmUG8S9SqvKIMwDIlURJlW71RwsbrkVskc3Ioq7VVWbc5Z
34             cUwGLZv7WJidKTmsoFXClT5sVCj+GMvIHM2Ib8rwZsv9vdzY1oPNt3CIIWaoD3ea
35             PADlqK80tx43vHdZhb50QZk41Rs6fcecaL0gU9wMMxQAzvEISLswgS3bPAiU0bkT
36             WggocUECgYEA3VCKGJlEn598ELqicp1NLiel+u0EVIdPUbkDJQfLijyN/UI5Kz5J
37             02lV5SLZ2F7Cnj9X+prMy3G/TcLMZz3gemhrrdBEUt+RbeBWdDP5pGsTOPmb+Cq6
38             ocDAPGQkIVsK5nmP/4z5Y3ldpJPUhbV6aOhVA4o8d4dz0ebLn44N3+UCgYEAzfkJ
39             yB681UT9ne6zwfRX32aE8Hy4aGnBMgB4UP7508e87anDYcK+WnRgtSEPCqYnfngC
40             tZA7bNMN1HEG65CYKssZD3FqqPepw6c/7siLdxgcJ+/q5XEjjn0aWQu0Aj/qnCWZ
41             9Z5Fq78cZKu6TR7Z1wja02cXdZ/4JrIXnx27p9ECgYAPMV85jxQB7T3kHBvYyGmq
42             +HfRgQHiF6PfVVcc7KsRY1TQBQLNsCn7RGjsIPdZfi/YEzsj7gqPEND0MqI7mCjX
43             3mE9/mUiV0yxgUwOEB9cJSmdqK0HXU+QmR3ZR3qfe5OE/OVgwrnAFW3TRX66axnr
44             J7/mTVAXWIof57skyeiz7QKBgQCJEA71T5cDKJzIat7N02ZiMBuI2MXyHWXFe1CV
45             PYdL6Z+MW6q7tFbtZIIyJiSXRogDfaL35VnWCgAq/WfIe/j2iR5NC4EZnW0n2HUP
46             1f4Qq0eZP+sE8aviltdgqAwKbzQU4mS4cLEWH9+qEiiwRzZZBPhxMyoGSQRd46ca
47             aDPG8QKBgGcfirAer9OGH0TOktK2fzfkZlV7mgmPtjp7ia1DnTgozZCq26j5Bwuy
48             g9hcGJT7XwPVChY4A3pLX87Xx08TBlcLpKAorY8tP7maxHa0Dpg8/tErmwNyPE/A
49             g0oXuSr48qa6mkrQMqkmCcouNT4MKuvFiQ70DB+kwJ5hB2pM75bS
50             -----END RSA PRIVATE KEY-----
51             };
52             $key = $key_temp->filename;
53             close($key_temp);
54            
55             my $cert_temp = File::Temp->new( UNLINK => 0 );
56             print $cert_temp q{-----BEGIN CERTIFICATE-----
57             MIIEhjCCA26gAwIBAgIJALsLM/f4lmkHMA0GCSqGSIb3DQEBBQUAMIGIMQswCQYD
58             VQQGEwJHQjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xGzAZBgNV
59             BAoTEkJvYiBUIEZpc2ggZG90IE5ldDEUMBIGA1UEAxMLVG9tYXMgRG9yYW4xJDAi
60             BgkqhkiG9w0BCQEWFWJvYnRmaXNoQGJvYnRmaXNoLm5ldDAeFw0wODA4MDYxNjI5
61             MTFaFw0zNTEyMjMxNjI5MTFaMIGIMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9u
62             ZG9uMQ8wDQYDVQQHEwZMb25kb24xGzAZBgNVBAoTEkJvYiBUIEZpc2ggZG90IE5l
63             dDEUMBIGA1UEAxMLVG9tYXMgRG9yYW4xJDAiBgkqhkiG9w0BCQEWFWJvYnRmaXNo
64             QGJvYnRmaXNoLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALIQ
65             ymDbAnfhE0VCEyINVN04OLsv71yZDd2WUteC0m3BaZ8M4pzMRQ+AP4P3UqYR1sZf
66             8mXoA1c92ViFdBJ+tmAuz32MCVlnf+GC7w3p0ABFs+3DU5Ac16kMKoq4w6hClw8k
67             EGeIxS0dxMGGn46w/fYe9SRI7jmvmWdK4C7tgj4R+TMRFVo0s9LUhVO+22TWSnWB
68             XXirWb9BM+CRsk5VbvHcH7J3zKaMlS/3OMQl7CfpDXJywKeERo2K1E5of162CtO0
69             Lj41ITz18pU2Cq82AnIYzEWYwJs+RL10m15egk59CTlBLWFb5CRjXXdeIyns+YqA
70             39foJ1+54dtLqIOpLPUCAwEAAaOB8DCB7TAdBgNVHQ4EFgQUOzPRmC5xIBWKeeOT
71             sam6S+s5l8swgb0GA1UdIwSBtTCBsoAUOzPRmC5xIBWKeeOTsam6S+s5l8uhgY6k
72             gYswgYgxCzAJBgNVBAYTAkdCMQ8wDQYDVQQIEwZMb25kb24xDzANBgNVBAcTBkxv
73             bmRvbjEbMBkGA1UEChMSQm9iIFQgRmlzaCBkb3QgTmV0MRQwEgYDVQQDEwtUb21h
74             cyBEb3JhbjEkMCIGCSqGSIb3DQEJARYVYm9idGZpc2hAYm9idGZpc2gubmV0ggkA
75             uwsz9/iWaQcwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAZmk7GGuI
76             xiI/ctxD7DY9j7K9nbb6geie/BUHhAkK6MFX+wU9/txA19MhxZo/j/pZyWFs1ocH
77             DFk+DGk1cbxyJVa5EhIRaGygKDfkD3RO21rbvkqOeEONnqAkrXbD0C2RaO/yPpQh
78             Eo7MzmVnDSJC03MRPMSmcOf4/+FdgXNmI7fJ6uqH1poVuISvcyVaufSIiwz1rmCw
79             U3f1B/1R70Fj7X5yj+pd2BQHUHzfwk6kSwBXbnqzA8zReOorrCkGuier9wzB2OUT
80             5EFOcIb3iNvk445bowUsH7pCGUYh3dJqWjIQ39BMfyO5K2SaOzldF0Z9VoK/lCOE
81             eCRh+7VA074hiw==
82             -----END CERTIFICATE-----
83            
84             };
85             $cert = $cert_temp->filename;
86             close($cert_temp);
87             }
88             sub _handle_CONNECT {
89 0     0     my ($self, $served) = @_;
90 0           my $last = 0;
91 0           my $conn = $self->client_socket;
92 0           my $req = $self->request;
93 0           my $upstream = IO::Socket::INET->new( PeerAddr => $req->uri->host_port );
94 0 0 0       unless( $upstream and $upstream->connected ) {
95             # 502 Bad Gateway / 504 Gateway Timeout
96             # Note to implementors: some deployed proxies are known to
97             # return 400 or 500 when DNS lookups time out.
98 0           my $response = HTTP::Response->new( 200 );
99 0           $response->content_type( "text/plain" );
100 0           $self->response($response);
101 0           return $last;
102             }
103              
104             # send the response headers (FIXME more headers required?)
105 0           my $response = HTTP::Response->new(200);
106 0           $self->response($response);
107 0           $self->{$_}{response}->select_filters( $response ) for qw( headers body );
108              
109 0           $self->_send_response_headers( $served );
110              
111             # we now have a TCP connection to the upstream host
112 0           $last = 1;
113 0           my $class = ref($conn);
114 1     1   6 { no strict 'refs'; unshift(@{$class . "::ISA"}, 'IO::Socket::SSL'); } # Forcibly change classes the socket inherits from
  1         3  
  1         114  
  0            
  0            
  0            
115 0 0         $class->start_SSL($conn,
116             SSL_server => 1,
117             SSL_key_file => $key,
118             SSL_cert_file => $cert, # Turn our client socket into SSL.
119             ) or warn("Could not start SSL");
120 0           ${*$conn}{'httpd_nomore'} = 0; # Pay no attention to the Connection: close header behind the curtain.
  0            
121             { # Build a method to fiddle with the request object we get from the client, as it needs to http->https
122 0           my $old_setrequest_method = \&HTTP::Proxy::request;
  0            
123             my $new_request_method = sub {
124 0     0     my ($self, $new_req) = @_;
125 0 0         if ($new_req) {
126 1     1   2512 use Data::Dumper;
  1         8894  
  1         258  
127 0 0 0       if (!$new_req->uri->scheme or $new_req->uri->scheme eq 'http') {
128 0           $new_req->uri->scheme('https');
129 0           $new_req->uri->host($new_req->header('Host'));
130             }
131             }
132 0           $old_setrequest_method->($self, $new_req);
133 0           };
134             # And monkeypatch it into HTTP proxy, using local to restrict it by lexical scope
135             # so that it goes away once we exit the block (i.e. the CONNECT method finishes).
136 1     1   13 no warnings qw[once redefine];
  1         2  
  1         77  
137 0           local *HTTP::Proxy::request = $new_request_method;
138 1     1   6 use warnings qw[once redefine];
  1         2  
  1         220  
139 0           $self->serve_connections($conn);
140             }
141 0           $conn->stop_SSL($conn);
142 0           return $last;
143             }
144             {
145 1     1   7 no warnings qw(once redefine);
  1         2  
  1         632  
146             *HTTP::Proxy::_handle_CONNECT = \&_handle_CONNECT;
147             }
148              
149             our %http_proxy_defaults = (
150             port => 3128,
151             max_clients => 10,
152             max_requests_per_child => 100,
153             min_spare_servers => 1,
154             max_spare_servers => 5,
155             keep_alive => 0,
156             max_keep_alive_requests => 1,
157             keep_alive_timeout => 60,
158             engine => 'NoFork',
159             );
160              
161             sub _generate_proxy_config {
162 0     0     my %in_params = @_;
163 0           my %params;
164 0           foreach my $k (keys %http_proxy_defaults) {
165 0 0         $params{$k} = exists $in_params{$k} ? $in_params{$k} : $http_proxy_defaults{$k};
166             }
167 0           return %params;
168             }
169              
170             my $_help = q{No config file passed on command line.
171            
172             Please create a file in a text editor which looks like this:
173             # Note that more options are available, please see example_config.ini in the distribution for usage.
174             port = 3128
175             debug = 1
176             #upstream_proxy = proxy.example.com:8080
177              
178             [search.cpan.org]
179             /s/=/tmp/css
180             /stuff/=/tmp/stuff
181              
182             [www.google.com]
183             /js/=/tmp/js
184             /some/file.jpg=/tmp/somefile.jpg
185              
186             and save it in your editor. Then re-run selective_proxy, appending the configuration file name.
187             };
188              
189             sub main {
190 0     0     my $conf_file = shift(@ARGV);
191 0 0         die($_help) unless ($conf_file);
192 0 0         die("Config file passed on command line ($conf_file) could not be read.\n") unless (-r $conf_file);
193              
194 0           my %config = %{ Config::Tiny->read( $conf_file ) };
  0            
195              
196 0           my $root_config = delete $config{_};
197 0           my $debug = delete $root_config->{debug};
198 0           my $upstream_proxy = delete $root_config->{upstream_proxy};
199              
200 0           my $proxy = HTTP::Proxy->new(
201 0           _generate_proxy_config( %{$root_config} ),
202             max_connections => 0, # Not lettng the users stamp on this..
203             );
204 0           $proxy->init;
205 0 0         die("No agent") unless $proxy->{agent};
206 0 0         warn("Upstream proxy: $upstream_proxy") if $upstream_proxy;
207 0 0         $proxy->{agent}->proxy([qw/http https/], $upstream_proxy) if $upstream_proxy;;
208            
209 0           $proxy->push_filter(
210             method => 'GET, HEAD',
211             request => HTTP::Proxy::Selective->new(\%config, $debug)
212             );
213 0           warn("Starting proxy at " . $proxy->url . "\n");
214 0           $proxy->start;
215             }
216              
217             main() unless caller();
218             main() if $ENV{PAR_0};
219              
220             1;
221              
222             __END__