Proxy Pac Rb
proxy_pac_rb
is a gem to compress, lint and parse proxy auto-config files. It comes with a cli program, some rack middlewares and can be used from within ruby scripts as well. proxy_pac_rb
uses a JavaScript runtime to evaulate a proxy auto-config file the same way a browser does to determine what proxy (if any at all) should a program use to connect to a server. You must install on of the supported JavaScript runtimes: therubyracer or therubyrhino.
Big thanks to sstephenson's execjs for the runtime wrapper code and to samuelkadolph's ruby-pac for the foundation of this gem.
Installing
Add this line to your application's Gemfile:
gem 'proxy_pac_rb'
And then execute:
$ bundle
Or install it yourself as:
$ gem install proxy_pac_rb
Requirements
After installing the proxy_pac_rb
gem you must install a JavaScript runtime. Compatible runtimes include:
- therubyracer Google V8 embedded within Ruby
- therubyrhino Mozilla Rhino embedded within JRuby
Usage
Command Line
Find proxy for url
Arguments
-
-p|--proxy-pac FILE
: Path to proxy pac file -
-t|--time YYYY-MM-DD HH:MM:SS
: Time to use in proxy.pac -
-c|--client-ip x.x.x.x
: Client-IP to use in proxy.pac -
-h|--help
: Show help
Use
# Download via curl and parse pac
curl -L -o sample.pac https://github.com/fedux-org/proxy_pac_rb/raw/master/files/sample.pac
pprb find proxy -c 127.0.0.1 -t "2014-03-09 12:00:00" -p sample.pac -u https://github.com
# Or download via pprb directly and parse pac #2
pprb find proxy -c 127.0.0.1 -t "2014-03-09 12:00:00" -p https://github.com/fedux-org/proxy_pac_rb/raw/master/files/sample.pac -u https://github.com
# Or download this example if you are behind a coporate proxy via pprb directly and parse pac #3
pprb find proxy -c 127.0.0.1 -t "2014-03-09 12:00:00" -p https://github.com/fedux-org/proxy_pac_rb/raw/master/files/sample.pac -u https://github.com --use-proxy
# => url: result
# => https://github.com: DIRECT
Compress proxy.pac-file
You can compress a proxy.pac with pprb
to reduce the amount of data
transferred to download the proxy.pac.
# Download pac
curl -L -o sample.pac https://github.com/fedux-org/proxy_pac_rb/raw/master/files/sample.pac
# Compress pac
pprb compress proxy_pac -p sample.pac
Lint proxy.pac-file
You can lint a proxy.pac with pprb
to check a proxy.pac before deploying it.
# Download pac
curl -L -o sample.pac https://github.com/fedux-org/proxy_pac_rb/raw/master/files/sample.pac
# Lint pac
pprb lint proxy_pac -p sample.pac
Init new proxy.pac
You can use the following command to start a new proxy.pac:
Plain
pprb init proxy_pac
Add rspec
To test your proxy.pac you can use rspec
.
pprb init proxy_pac --test rspec
Add middleman
To build your proxy.pac you can use middleman
.
pprb init proxy_pac --build middleman
"rack"-middleware
The middleware which comes with proxy_pac_rb
is compliant with the
rack
-specification - tested via
rack/lint
and
should work with every rack
-compliant-server.
Prerequisites
Make sure the content is served with "Content-Type": 'application/x-ns-proxy-autoconfig'
. Otherwise the content is ignored by both
middlewares.
Warning
The linter-rack
-middleware needs to be activated before ANY other
middleman-extension, rack
-middleware or whatever framework you are using
can instantiate the V8
-runtime! Only the first time the
V8
-javascript-engine - aka therubyracer
- is instantiated, it is possible to
create a binding to ruby code. Every other V8
-object created later re-uses
this binding.
You might an error like this if you ignore this warning!
error build/proxy.pac
Unexpected token: name (is) (line: 1, col: 10, pos: 10)
Error
at new JS_Parse_Error (<eval>:2359:10623)
at js_error (<eval>:2359:10842)
at croak (<eval>:2359:19086)
at token_error (<eval>:2359:19223)
at unexpected (<eval>:2359:19311)
at semicolon (<eval>:2359:19784)
at simple_statement (<eval>:2359:22580)
at <eval>:2359:20553
at <eval>:2359:19957
at <eval>:2359:31968
There were errors during this build
Linter Middleware
require 'proxy_pac_rb/rack/proxy_pac_linter'
use ProxyPacRb::Rack::ProxyPacLinter
Compressor Middleware
require 'proxy_pac_rb/rack/proxy_pac_compressor'
use ProxyPacRb::Rack::ProxyPacCompressor
If you need to change some settings, please pass it a options
-parameter.
Please see the (uglifier
)[https://github.com/lautis/uglifier] for more
information about this.
require 'proxy_pac_rb/rack/proxy_pac_compressor'
use ProxyPacRb::Rack::ProxyPacCompressor.new(options: {})
Using "rack"-middleware with "middleman"
If you want to use the rack
-middleware with middleman
look at the following
code snippet captured from the middleman
-configuration file config.rb
:
-
config.rb
:# IMPORTANT: Needs to come first before compressor require 'proxy_pac_rb/rack/proxy_pac_linter' use ProxyPacRb::Rack::ProxyPacLinter require 'proxy_pac_rb/rack/proxy_pac_compressor' use ProxyPacRb::Rack::ProxyPacCompressor # The middleware works on content served with # "Content-Type" 'application/x-ns-proxy-autoconfig' page "*.pac", content_type: 'application/x-ns-proxy-autoconfig', layout: false # This provides an uncompressed copy of the proxy.pac to make it # possible for your support to review it by hand Dir.glob(File.join(source_dir, '**', '*.pac*')).each do |f| # Path should be relative to source dir relative_path = Pathname.new(f).relative_path_from(Pathname.new(source_dir)) relative_path = relative_path.to_s.gsub(/(?<path>.*\.pac)\..*/, '\k<path>') # "text/plain" prevents the middlewares to handle it proxy(format('%s.raw', relative_path), relative_path, content_type: 'text/plain', layout: false) end
Ruby
Load from website
require 'proxy_pac_rb'
file = ProxyPacRb::Parser.new.parse('https://github.com/fedux-org/proxy_pac_rb/raw/master/files/sample.pac')
file.find('https://github.com') # => "DIRECT"
Load from filesystem
curl -L -o sample.pac https://github.com/fedux-org/proxy_pac_rb/raw/master/files/sample.pac
require 'proxy_pac_rb'
file = ProxyPacRb::Parser.new.parse("sample.pac")
file.find('https://github.com') # => "DIRECT"
Use string
require 'proxy_pac_rb'
file = ProxyPacRb::Parser.new.parse <<-JS
function FindProxyForURL(url, host) {
return "DIRECT";
}
JS
file.find('http://localhost') # => "DIRECT"
Use Client IP
require 'proxy_pac_rb'
environment = ProxyPacRb::Environment.new(client_ip: '127.0.0.1')
file = ProxyPacRb::Parser.new(environment).parse('https://github.com/fedux-org/proxy_pac_rb/raw/master/files/sample2.pac')
file.find('https://github.com') # => "PROXY localhost:8080"
environment = ProxyPacRb::Environment.new(client_ip: '127.0.0.2')
file = ProxyPacRb::Parser.new(environment).parse('https://github.com/fedux-org/proxy_pac_rb/raw/master/files/sample2.pac')
file.find('https://github.com') # => "DIRECT"
Use Date Time
require 'proxy_pac_rb'
string = <<~EOS
function FindProxyForURL(url, host) {
if (dateRange("JUL", "SEP")) {
return "PROXY localhost:8080";
} else {
return "DIRECT";
}
}
EOS
environment = ProxyPacRb::Environment.new(time: Time.parse('2014-07-06 12:00'))
file = ProxyPacRb::Parser.new(environment).parse(string)
file.find('http://localhost') # => 'PROXY localhost:8080'
environment = ProxyPacRb::Environment.new(time: Time.parse('2014-03-08 6:00'))
file = ProxyPacRb::Parser.new(environment).parse(string)
file.find('http://localhost') # => 'DIRECT'
Available JavaScript Functions
- isPlainHostName(host)
- dnsDomainIs(host, domain)
- localHostOrDomainIs(host, hostdom)
- isResolvable(host)
- isInNet(host, pattern, mask)
- dnsResolve(host)
- myIpAddress()
- dnsDomainLevels(host)
- shExpMatch(str, shexp)
- weekdayRange(wd1, wd2, gmt)
- dateRange(*args)
- timeRange(*args)
- alert(msg) (output on stderr by default)
RSpec-integration
proxy_pac_rb
comes with helpers and matchers for rspec
. To make those
helpers and matchers available in your project, add this code snippet in your
project:
require 'proxy_pac_rb/rspec'
Helpers
-
proxy_pac
:This helper makes a proxy.pac available. It requires a source for your proxy.pac given in
subject { }
- e.g. a file, a string, or a url. It represents aProxyPacFile
. -
time
:The
time
-helper makes1970-01-01 00:00:00
as time available. Overwrite this helper at will with another time string or a time-object - e.glet(:time) { Time.now }
. -
client_ip
:The
client_ip
-helper makes127.0.0.1
available. Overwrite it with a different ip-address as string or an object which returns an ip-address on#to_s
. -
root_path
:The
root_path
-helper is meant for overriding. It is used to find your "proxy.pac"-files. By default its value isDir.getwd
which is set byrspec
.
Configuration
You can either configure ProxyPacRb
either via a global ProxyPacRb.configure
-block:
ProxyPacRb.configure do |config|
config.use_proxy = true
end
or via RSpec
-metadata:
RSpec.describe 'proxy.pac', type: :proxy_pac, use_proxy: true do
end
Examples
To make it easier for you to start, you find some examples below.
Type for specs
It is important that you flag your specs with type: :proxy_pac
. Otherwise the
helpers are not included and not available in your examples.
RSpec.describe 'proxy.pac', type: :proxy_pac do
Supported sources
String
RSpec.describe 'String', type: :proxy_pac do
subject do
<<~EOS.chomp
function FindProxyForURL(url, host) {
return "DIRECT";
}
EOS
end
end
Local File
RSpec.describe 'proxy.pac', type: :proxy_pac do
subject { 'proxy.pac' }
end
URL
RSpec.describe 'http://server/proxy.pac', type: :proxy_pac do
subject { 'http://server/proxy.pac' }
end
Matchers
Readable
To check if a proxy.pac could be read from filesystem or downloaded via HTTP,
check be_readable
.
require 'proxy_pac_rb/rspec'
RSpec.describe 'proxy.pac', type: :proxy_pac do
context 'when proxy pac exist' do
context 'when is file' do
subject { 'proxy.pac' }
it { expect(proxy_pac).to be_readable }
end
context 'when is url' do
subject { 'http://www.example.com/proxy.pac' }
it { expect(proxy_pac).to be_readable }
end
end
end
Equal
If you want to check if a proxy.pac is the same you can use the
be_the_same_proxy_pac_file
-matcher.
require 'proxy_pac_rb/rspec'
RSpec.describe 'proxy.pac', type: :proxy_pac do
subject do
<<~EOS.chomp
function FindProxyForURL(url, host) {
return "DIRECT";
}
EOS
end
context 'Check equality of proxy pac' do
context 'when proxy.pac is eq' do
it { expect(proxy_pac).to be_the_same_proxy_pac_file 'proxy.pac' }
end
context 'when proxy.pac is not eq' do
it { expect(proxy_pac).not_to be_the_same_proxy_pac_file 'proxy.pac' }
end
end
end
This is quite handy to compare a local proxy.pac with a remote one - e.g. a deployed one.
require 'proxy_pac_rb/rspec'
RSpec.describe 'proxy.pac', type: :proxy_pac do
let(:local_proxy_pac) { ProxyPacRb::ProxyPacFile.new(source: file) }
before(:each) do
ProxyPacRb::ProxyPacLoader.new.load(local_proxy_pac)
ProxyPacRb::ProxyPacCompressor.new.compress(local_proxy_pac) if local_proxy_pac.readable?
end
context 'when no proxy.pac is given' do
subject { 'http://server.example.com/proxy.pac' }
let(:file) { 'source/proxy.pac' }
it { expect(proxy_pac).to be_the_same_proxy_pac_file(local_proxy_pac) }
end
end
Valid
You want to check if your proxy.pac is valid. This will find reference errors
(e.g. unknown variables) and syntax errors
.
require 'proxy_pac_rb/rspec'
RSpec.describe 'proxy.pac', type: :proxy_pac do
subject do
<<~EOS.chomp
function FindProxyForURL(url, host) {
return "DIRECT";
}
EOS
end
context 'Check validity of proxy pac' do
context 'when proxy.pac is valid' do
it { expect(proxy_pac).to be_valid }
end
context 'when proxy.pac is not valid' do
subject do
<<~EOS.chomp
function FindProxyForURL(url, host) {
return adsf;
}
EOS
end
it { expect(proxy_pac).not_to be_valid }
end
end
end
Parse proxy.pac
To make some logic checks use the be_downloaded_via
-matcher. This will parse
the proxy.pac and returns the proxy to be used for a given url.
require 'proxy_pac_rb/rspec'
RSpec.describe 'proxy.pac', type: :proxy_pac do
subject do
<<~EOS.chomp
function FindProxyForURL(url, host) {
if (dnsDomainIs(host, 'www1.example.com'')) {
return "PROXY proxy1.example.com:8080";
} else {
return "PROXY proxy2.example.net:8080";
}
}
EOS
end
context 'Parse Proxy Pac' do
context 'when url is forwarded via' do
let(:url) { 'http://www1.example.com' }
it { expect(url).to be_downloaded_via 'PROXY proxy.example.com:8080' }
end
context 'when url is not forwarded via' do
let(:url) { 'http://www2.example.com' }
it { expect(url).not_to be_downloaded_via 'PROXY proxy.example.com:8080' }
end
end
end
Developers
After checking out the repo, run script/bootstrap
to install dependencies.
Then, run script/console
for an interactive prompt that will allow you to
experiment. To run tests execute script/test
.
To install this gem onto your local machine, run bundle exec rake gem:install
. To
release a new version, update the version number in version.rb
, and then run
bundle exec rake gem:release
to create a git tag for the version, push git
commits and tags, and push the .gem
file to
rubygems.org.
Contributing
If you want to contribute: fork, branch & pull request and please see CONTRIBUTING.md.
Running Tests
bundle install
rake test:rspec
rake test:rubyracer
rake test:rubyrhino
If you want to open an issue. Please send a PR with a test describing the bug.