proxy_pac_rb 0.3.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/.travis.yml +2 -1
  4. data/Gemfile +13 -13
  5. data/Gemfile.lock +84 -58
  6. data/README.md +40 -13
  7. data/features/check_proxy_pac.feature +31 -0
  8. data/features/compress_proxy_pac.feature +3 -16
  9. data/features/resolve_proxy.feature +1 -1
  10. data/lib/proxy_pac_rb/cli/compress_proxy_pac.rb +25 -11
  11. data/lib/proxy_pac_rb/cli/find_proxy.rb +13 -8
  12. data/lib/proxy_pac_rb/cli/lint.rb +11 -0
  13. data/lib/proxy_pac_rb/cli/lint_proxy_pac.rb +37 -0
  14. data/lib/proxy_pac_rb/cli/runner.rb +5 -0
  15. data/lib/proxy_pac_rb/cli/shared.rb +4 -0
  16. data/lib/proxy_pac_rb/cli_validator.rb +14 -0
  17. data/lib/proxy_pac_rb/errors.rb +19 -0
  18. data/lib/proxy_pac_rb/javascript.rb +19 -0
  19. data/lib/proxy_pac_rb/main.rb +22 -0
  20. data/lib/proxy_pac_rb/parser.rb +32 -21
  21. data/lib/proxy_pac_rb/proxy_pac.rb +10 -20
  22. data/lib/proxy_pac_rb/proxy_pac_compressor.rb +9 -0
  23. data/lib/proxy_pac_rb/proxy_pac_dumper.rb +65 -0
  24. data/lib/proxy_pac_rb/proxy_pac_file.rb +20 -3
  25. data/lib/proxy_pac_rb/proxy_pac_linter.rb +82 -0
  26. data/lib/proxy_pac_rb/proxy_pac_loader.rb +98 -0
  27. data/lib/proxy_pac_rb/proxy_pac_parser.rb +41 -0
  28. data/lib/proxy_pac_rb/rack/proxy_pac_compressor.rb +68 -0
  29. data/lib/proxy_pac_rb/rack/proxy_pac_linter.rb +66 -0
  30. data/lib/proxy_pac_rb/runtimes/rubyracer.rb +2 -2
  31. data/lib/proxy_pac_rb/runtimes/rubyrhino.rb +2 -2
  32. data/lib/proxy_pac_rb/runtimes.rb +3 -3
  33. data/lib/proxy_pac_rb/version.rb +1 -1
  34. data/lib/proxy_pac_rb.rb +10 -3
  35. data/proxy_pac_rb.gemspec +2 -0
  36. data/spec/api_spec.rb +207 -0
  37. data/spec/parser_spec.rb +66 -53
  38. data/spec/rack/proxy_pac_compressor_spec.rb +59 -0
  39. data/spec/rack/proxy_pac_linter_spec.rb +65 -0
  40. data/spec/spec_helper.rb +1 -0
  41. data/spec/support/matchers/file.rb +23 -0
  42. data/spec/support/rack_test.rb +1 -0
  43. data/spec/support/rspec.rb +3 -1
  44. data/spec/support/webmock.rb +1 -0
  45. metadata +30 -12
  46. data/features/support/fixtures.rb +0 -15
  47. data/lib/proxy_pac_rb/cli.rb +0 -75
  48. data/lib/proxy_pac_rb/file.rb +0 -21
  49. data/lib/proxy_pac_rb/javascript_compressor.rb +0 -9
  50. data/lib/proxy_pac_rb/proxy_pac_template +0 -39
  51. data/spec/file_spec.rb +0 -45
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+ module ProxyPacRb
3
+ module Cli
4
+ # Find proxy for url
5
+ class LintProxyPac < Thor::Group
6
+ include Shared
7
+
8
+ class_option :proxy_pac, type: :array, desc: 'Proxy.pac-file(s)', aliases: '-p', required: true
9
+
10
+ def pre_init
11
+ enable_debug_mode
12
+ end
13
+
14
+ def set_variables
15
+ @proxy_pacs = options[:proxy_pac].map { |p| ProxyPacFile.new source: p }
16
+ @loader = ProxyPacLoader.new
17
+ @linter = ProxyPacLinter.new
18
+ end
19
+
20
+ def test_proxy_pac
21
+ @proxy_pacs.each do |p|
22
+ @loader.load(p)
23
+ @linter.lint(p)
24
+
25
+ if p.valid?
26
+ $stderr.puts %(proxy.pac "#{p.source}" is of type #{p.type} and is valid.)
27
+ true
28
+ else
29
+ $stderr.puts %(proxy.pac "#{p.source}" is of type #{p.type} and is invalid.)
30
+
31
+ false
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -7,6 +7,8 @@ module ProxyPacRb
7
7
  map '-v' => :version
8
8
  map '--version' => :version
9
9
 
10
+ class_option :debug_mode, type: :boolean, default: false, desc: 'Enable debug mode'
11
+
10
12
  desc 'find', 'Find something'
11
13
  subcommand 'find', Find
12
14
 
@@ -16,6 +18,9 @@ module ProxyPacRb
16
18
  desc 'show', 'Show something'
17
19
  subcommand 'show', Show
18
20
 
21
+ desc 'lint', 'Lint something'
22
+ subcommand 'lint', Lint
23
+
19
24
  desc 'version', 'version', hide: true
20
25
  def version
21
26
  invoke 'proxy_pac_rb:cli:show:version'
@@ -3,6 +3,10 @@ module ProxyPacRb
3
3
  module Cli
4
4
  # Shared methods for all cli commands
5
5
  module Shared
6
+ # Enable debug mode
7
+ def enable_debug_mode
8
+ ProxyPacRb.enable_debug_mode if options[:debug_mode] == true
9
+ end
6
10
  end
7
11
  end
8
12
  end
@@ -14,10 +14,24 @@ module ProxyPacRb
14
14
  def validate
15
15
  exit_with_message 'You need to provide at least one url. Multiple urls need to be separated by a space.' if empty_url?
16
16
  exit_with_message 'You need to provide a proxy pac file.' if empty_pac_file?
17
+ exit_with_message %(You need to provide a path to an existing proxy pac file. The file "#{options[:proxy_pac]}" does not exist.) if non_existing_proxy_pac_file?
17
18
  end
18
19
 
19
20
  private
20
21
 
22
+ def proxy_pac_url?
23
+ url = Addressable::URI.parse(options[:proxy_pac])
24
+ return true if url.host
25
+
26
+ false
27
+ rescue StandardError
28
+ false
29
+ end
30
+
31
+ def non_existing_proxy_pac_file?
32
+ !proxy_pac_url? && !File.file?(options[:proxy_pac])
33
+ end
34
+
21
35
  def empty_url?
22
36
  options[:urls].blank?
23
37
  end
@@ -0,0 +1,19 @@
1
+ module ProxyPacRb
2
+ # Raised if proxy pac is invalid
3
+ class LinterError < StandardError; end
4
+
5
+ # raise on error
6
+ class ProgramError < StandardError; end
7
+
8
+ # raise on java script runtime error
9
+ class RuntimeUnavailableError < StandardError; end
10
+
11
+ # raise on invalid argument
12
+ class InvalidArgumentError < StandardError; end
13
+
14
+ # raise on invalid argument
15
+ class UrlInvalidError < StandardError; end
16
+
17
+ # raise if proxy pac could not be compiled
18
+ class ParserError < StandardError; end
19
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+ module ProxyPacRb
3
+ # Parse Proxy pac to file system
4
+ class Javascript
5
+ private
6
+
7
+ attr_reader :context
8
+
9
+ public
10
+
11
+ def initialize(context)
12
+ @context = context
13
+ end
14
+
15
+ def method_missing(*args, &_block)
16
+ context.call(args.shift, *args)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ # Main
3
+ module ProxyPacRb
4
+ @debug_mode = false
5
+
6
+ class << self
7
+ private
8
+
9
+ attr_accessor :debug_mode
10
+
11
+ public
12
+
13
+ def debug_mode_enabled?
14
+ debug_mode == true
15
+ end
16
+
17
+ def enable_debug_mode
18
+ self.debug_mode = true
19
+ %w(pry byebug).each { |l| require l }
20
+ end
21
+ end
22
+ end
@@ -13,42 +13,53 @@ module ProxyPacRb
13
13
  class Parser
14
14
  private
15
15
 
16
- attr_reader :runtime, :environment
16
+ attr_reader :parser, :loader, :linter
17
17
 
18
18
  public
19
19
 
20
- def initialize(environment = Environment.new, runtime = Runtimes.autodetect)
21
- fail Exceptions::RuntimeUnavailable, "#{runtime.name} is unavailable on this system" unless runtime.available?
20
+ def initialize(*args)
21
+ if args.first.is_a? Hash
22
+ @parser = ProxyPacParser.new(**args.first)
23
+ else
24
+ $stderr.puts 'Deprecated: Use of positional parameters. Please use named parameters: environment: Environment.new.'
25
+ @parser = ProxyPacParser.new(environment: args.first)
26
+ end
22
27
 
23
- @runtime = runtime
24
- @environment = environment
28
+ @loader = ProxyPacLoader.new
29
+ @linter = ProxyPacLinter.new
25
30
  end
26
31
 
27
- def load(url, options = {})
28
- create_file(open(url, { proxy: false }.merge(options)).read)
29
- end
32
+ def parse(source)
33
+ pac_file = ProxyPacFile.new source: source
30
34
 
31
- def read(file)
32
- create_file(::File.read(file))
33
- end
35
+ loader.load(pac_file)
36
+ linter.lint(pac_file)
37
+
38
+ unless pac_file.valid?
39
+ $stderr.puts %(proxy.pac "#{pac_file.source}" is invalid.)
40
+
41
+ return
42
+ end
34
43
 
35
- def source(source)
36
- create_file(source)
44
+ parser.parse(pac_file)
37
45
  end
38
46
 
39
- private
47
+ def load(*args)
48
+ $stderr.puts 'Deprecated: #load. Please use #parse instead.'
40
49
 
41
- def compile_javascript(source)
42
- environment.prepare(source)
50
+ parse(*args)
51
+ end
43
52
 
44
- context = runtime.compile(source)
45
- context.include environment
53
+ def read(*args)
54
+ $stderr.puts 'Deprecated: #read. Please use #parse instead.'
46
55
 
47
- context
56
+ parse(*args)
48
57
  end
49
58
 
50
- def create_file(source)
51
- File.new(compile_javascript(source))
59
+ def source(*args)
60
+ $stderr.puts 'Deprecated: #source. Please use #parse instead.'
61
+
62
+ parse(*args)
52
63
  end
53
64
  end
54
65
  end
@@ -1,34 +1,24 @@
1
- # encoding: utf-8
2
1
  module ProxyPacRb
3
- # ProxyPac
2
+ # Proxy pac file
4
3
  class ProxyPac
5
4
  private
6
5
 
7
- attr_reader :path
6
+ attr_reader :javascript
8
7
 
9
8
  public
10
9
 
11
- def initialize(path)
12
- @path = path
13
- end
10
+ attr_reader :file
14
11
 
15
- def content
16
- fail
12
+ def initialize(javascript:, file:)
13
+ @javascript = javascript
14
+ @file = file
17
15
  end
18
16
 
19
- private
20
-
21
- def read_proxy_pac(path)
22
- uri = Addressable::URI.parse(path)
23
-
24
- uri.path = ::File.expand_path(uri.path) if uri.host.nil?
25
-
26
- ENV.delete 'HTTP_PROXY'
27
- ENV.delete 'HTTPS_PROXY'
28
- ENV.delete 'http_proxy'
29
- ENV.delete 'https_proxy'
17
+ def find(url)
18
+ uri = Addressable::URI.heuristic_parse(url)
19
+ fail UrlInvalidError, 'url is missing host' unless uri.host
30
20
 
31
- open(uri, proxy: false).read
21
+ javascript.FindProxyForURL(url, uri.host)
32
22
  end
33
23
  end
34
24
  end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+ module ProxyPacRb
3
+ # Compress javascript files
4
+ class ProxyPacCompressor
5
+ def compress(proxy_pac)
6
+ proxy_pac.content = Uglifier.new.compile(proxy_pac.content)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+ module ProxyPacRb
3
+ # Dump Proxy pac to file system
4
+ class ProxyPacDumper
5
+ private
6
+
7
+ attr_reader :type, :dumpers
8
+
9
+ public
10
+
11
+ def initialize
12
+ @dumpers = Hash.new { ProxyPacStringDumper.new }
13
+ @dumpers[:template] = ProxyPacTemplateDumper.new
14
+ @dumpers[:string] = ProxyPacStringDumper.new
15
+ end
16
+
17
+ def dump(proxy_pac, type:)
18
+ dumpers[type].dump(proxy_pac)
19
+ end
20
+ end
21
+
22
+ # Dump string to file
23
+ class ProxyPacStringDumper
24
+ private
25
+
26
+ attr_reader :default_file_name
27
+
28
+ public
29
+
30
+ def initialize
31
+ @default_file_name = 'proxy.pac'
32
+ end
33
+
34
+ def dump(proxy_pac)
35
+ ::File.write(default_file_name, proxy_pac.content)
36
+ end
37
+ end
38
+
39
+ # Dump proxy pac based on template
40
+ class ProxyPacTemplateDumper
41
+ def dump(proxy_pac)
42
+ ::File.write(output_path(proxy_pac.source), proxy_pac.content)
43
+ end
44
+
45
+ private
46
+
47
+ def in_extension
48
+ '.in'
49
+ end
50
+
51
+ def out_extension
52
+ '.out'
53
+ end
54
+
55
+ def output_path(path)
56
+ if ::File.exist?(path.gsub(/#{in_extension}*$/, '') + in_extension)
57
+ return path.gsub(/#{in_extension}*$/, '')
58
+ elsif ::File.exist? path
59
+ return path + out_extension
60
+ else
61
+ fail Errno::ENOENT, "Both paths \"#{path.gsub(/#{in_extension}*$/, '') + in_extension}\" and \"#{path}\" do not exist."
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,9 +1,26 @@
1
1
  # encoding: utf-8
2
2
  module ProxyPacRb
3
- # Pac file not for parsing
4
- class ProxyPacFile < ProxyPac
3
+ # Pac file
4
+ class ProxyPacFile
5
+ attr_accessor :valid, :type, :message
6
+ attr_reader :source
7
+ attr_writer :content
8
+
9
+ def initialize(source:)
10
+ @source = source
11
+ @valid = false
12
+ end
13
+
5
14
  def content
6
- read_proxy_pac(path)
15
+ @content.dup
16
+ end
17
+
18
+ def type?(t)
19
+ type == t
20
+ end
21
+
22
+ def valid?
23
+ valid == true
7
24
  end
8
25
  end
9
26
  end
@@ -0,0 +1,82 @@
1
+ # encoding: utf-8
2
+ module ProxyPacRb
3
+ # Dump Proxy pac to file system
4
+ class ProxyPacLinter
5
+ private
6
+
7
+ attr_reader :rules, :silent
8
+
9
+ public
10
+
11
+ def initialize(silent: false)
12
+ @rules = []
13
+ @rules << Rules::ContainerProxyPacFunction.new
14
+ @rules << Rules::CanBeParsed.new
15
+
16
+ @silent = silent
17
+ end
18
+
19
+ # Load proxy pac
20
+ #
21
+ # @param [#source] proxy_pac
22
+ # The proxy.pac
23
+ def lint(proxy_pac)
24
+ rules.each { |r| r.lint(proxy_pac) }
25
+
26
+ proxy_pac.valid = true
27
+ rescue LinterError => err
28
+ $stderr.puts err.message unless silent
29
+
30
+ proxy_pac.message = err.message
31
+ proxy_pac.valid = false
32
+ end
33
+ end
34
+
35
+ module Rules
36
+ # A proxy pac needs to contain FindProxyForURL
37
+ class ContainerProxyPacFunction
38
+ def lint(proxy_pac)
39
+ message = if proxy_pac.type? :string
40
+ %(proxy.pac is only given as string "#{proxy_pac.source}" and does not contain "FindProxyForURL".)
41
+ elsif proxy_pac.type? :url
42
+ %(proxy.pac-url "#{proxy_pac.source}" does not contain "FindProxyForURL".)
43
+ else
44
+ %(proxy.pac-file "#{proxy_pac.source}" does not contain "FindProxyForURL".)
45
+ end
46
+
47
+ fail LinterError, message unless proxy_pac.content.include?('FindProxyForURL')
48
+
49
+ self
50
+ end
51
+ end
52
+
53
+ # A proxy pac needs be parsable
54
+ class CanBeParsed
55
+ private
56
+
57
+ attr_reader :parser
58
+
59
+ public
60
+
61
+ def initialize
62
+ @parser = ProxyPacParser.new
63
+ end
64
+
65
+ def lint(proxy_pac)
66
+ parser.parse(proxy_pac)
67
+
68
+ self
69
+ rescue => err
70
+ message = if proxy_pac.type? :string
71
+ %(proxy.pac is only given as string "#{proxy_pac.source}" cannot be parsed:\n#{err.message}".)
72
+ elsif proxy_pac.type? :url
73
+ %(proxy.pac-url "#{proxy_pac.source}" cannot be parsed:\n#{err.message}".)
74
+ else
75
+ %(proxy.pac-file "#{proxy_pac.source}" cannot be parsed:\n#{err.message}".)
76
+ end
77
+
78
+ raise LinterError, message
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,98 @@
1
+ # encoding: utf-8
2
+ module ProxyPacRb
3
+ # Dump Proxy pac to file system
4
+ class ProxyPacLoader
5
+ private
6
+
7
+ attr_reader :loaders, :default_loader
8
+
9
+ public
10
+
11
+ def initialize
12
+ @loaders = []
13
+ @loaders << ProxyPacStringLoader.new
14
+ @loaders << ProxyPacFileLoader.new
15
+ @loaders << ProxyPacUriLoader.new
16
+
17
+ @default_loader = -> { ProxyPacStringLoader.new }
18
+ end
19
+
20
+ # Load proxy pac
21
+ #
22
+ # @param [#source] proxy_pac
23
+ # The proxy.pac
24
+ def load(proxy_pac)
25
+ loaders.find(default_loader) { |l| l.suitable_for? proxy_pac }.load(proxy_pac)
26
+ end
27
+ end
28
+
29
+ # Load proxy pac from string
30
+ class ProxyPacStringLoader
31
+ def load(proxy_pac)
32
+ proxy_pac.content = proxy_pac.source.to_s
33
+ proxy_pac.type = :string
34
+
35
+ self
36
+ end
37
+
38
+ def suitable_for?(proxy_pac)
39
+ proxy_pac.source.include? 'FindProxyForURL'
40
+ end
41
+ end
42
+
43
+ # Load proxy pac from file
44
+ class ProxyPacFileLoader
45
+ def load(proxy_pac)
46
+ proxy_pac.content = ::File.read(proxy_pac.source).chomp
47
+ proxy_pac.type = :file
48
+ end
49
+
50
+ def suitable_for?(proxy_pac)
51
+ ::File.file? proxy_pac.source
52
+ end
53
+ end
54
+
55
+ # Load proxy pac from url
56
+ class ProxyPacUriLoader
57
+ def load(proxy_pac)
58
+ proxy_pac.content = Net::HTTP.get(URI(proxy_pac.source))
59
+ proxy_pac.type = :url
60
+ end
61
+
62
+ def suitable_for?(proxy_pac)
63
+ uri = Addressable::URI.parse(proxy_pac.source)
64
+
65
+ return true unless uri.host.blank?
66
+
67
+ false
68
+ rescue StandardError
69
+ false
70
+ end
71
+
72
+ private
73
+
74
+ def with_proxy_environment_variables
75
+ backup = []
76
+
77
+ %w(
78
+ http_proxy
79
+ https_proxy
80
+ HTTP_PROXY
81
+ HTTPS_PROXY
82
+ ).each do |v|
83
+ backup << ENV.delete(v)
84
+ end
85
+
86
+ yield
87
+ ensure
88
+ %w(
89
+ http_proxy
90
+ https_proxy
91
+ HTTP_PROXY
92
+ HTTPS_PROXY
93
+ ).each do |v|
94
+ ENV[v] = backup.shift
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+ module ProxyPacRb
3
+ # Parse Proxy pac to file system
4
+ class ProxyPacParser
5
+ private
6
+
7
+ attr_reader :environment, :runtime
8
+
9
+ public
10
+
11
+ def initialize(
12
+ environment: Environment.new,
13
+ runtime: Runtimes.autodetect
14
+ )
15
+ @runtime = runtime
16
+ @environment = environment
17
+ end
18
+
19
+ def parse(proxy_pac)
20
+ fail Exceptions::RuntimeUnavailable, "#{runtime.name} is unavailable on this system" unless runtime.available?
21
+
22
+ ProxyPac.new(
23
+ javascript: compile_javascript(proxy_pac.content),
24
+ file: proxy_pac
25
+ )
26
+ end
27
+
28
+ private
29
+
30
+ def compile_javascript(content)
31
+ environment.prepare(content)
32
+
33
+ context = runtime.compile(content)
34
+ context.include environment
35
+
36
+ Javascript.new(context)
37
+ rescue StandardError => err
38
+ raise ParserError, err.message
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,68 @@
1
+ require 'proxy_pac_rb'
2
+ require 'rack'
3
+
4
+ module ProxyPacRb
5
+ module Rack
6
+ # Rack Middleware to compress proxy pac
7
+ #
8
+ # @example Sinatra's <server>.rb
9
+ # require 'proxy_pac_rb/rack/proxy_pac_compressor'
10
+ # use ProxyPacRb::Rack::ProxyPacCompressor
11
+ #
12
+ # @example Rack's config.ru
13
+ # require 'proxy_pac_rb/rack/proxy_pac_compressor'
14
+ # use ProxyPacRb::Rack::ProxyPacCompressor
15
+ #
16
+ # @example Middleman's config.rb
17
+ # require 'proxy_pac_rb/rack/proxy_pac_compressor'
18
+ # use ProxyPacRb::Rack::ProxyPacCompressor
19
+ #
20
+ class ProxyPacCompressor
21
+ private
22
+
23
+ attr_reader :compressor, :loader, :enabled
24
+
25
+ public
26
+
27
+ def initialize(
28
+ app,
29
+ enabled: true
30
+ )
31
+ @app = app
32
+ @compressor = ProxyPacRb::ProxyPacCompressor.new
33
+ @loader = ProxyPacRb::ProxyPacLoader.new
34
+ @enabled = enabled
35
+ end
36
+
37
+ def call(env)
38
+ status, headers, body = @app.call(env)
39
+
40
+ return [status, headers, body] if enabled == false
41
+ # rubocop:disable Style/CaseEquality
42
+ return [status, headers, body] unless headers.key?('Content-Type') \
43
+ && %r{application/x-ns-proxy-autoconfig} === headers['Content-Type']
44
+ # rubocop:enable Style/CaseEquality
45
+
46
+ content = [body].flatten.each_with_object('') { |e, a| a << e.to_s }
47
+
48
+ begin
49
+ proxy_pac = ProxyPacFile.new(source: content)
50
+
51
+ loader.load(proxy_pac)
52
+ compressor.compress(proxy_pac)
53
+
54
+ content = proxy_pac.content
55
+ rescue => err
56
+ status = 500
57
+ content = err.message
58
+ end
59
+
60
+ headers['Content-Length'] = content.bytesize.to_s if headers['Content-Length']
61
+
62
+ [status, headers, [content]]
63
+ ensure
64
+ body.close if body.respond_to? :close
65
+ end
66
+ end
67
+ end
68
+ end