proxy_pac_rb 0.3.8 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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