[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [ncr]\n"
  },
  {
    "path": ".gitignore",
    "content": "pkg/*\n*.gem\n.bundle\n"
  },
  {
    "path": ".travis.yml",
    "content": "cache: bundler\nlanguage: ruby\nbefore_install:\n  - \"echo 'gem: --no-ri --no-rdoc' > ~/.gemrc\"\n  - gem install bundler\n  - gem update bundler\nscript: bundle exec rake test\nrvm:\n  - 2.0.0\n  - 2.1.5\n  - 2.2.2\n  - 2.2.3\n  - 2.3.0\n  - 2.3.1\nenv:\n - RAILS_ENV=test RACK_ENV=test\nnotifications:\n  email: false\n"
  },
  {
    "path": "Gemfile",
    "content": "source \"https://rubygems.org\"\n\ngem 'rake'\n\n# Specify your gem's dependencies in rack-proxy.gemspec\ngemspec\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2013 Jacek Becela jacek.becela@gmail.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "A request/response rewriting HTTP proxy. A Rack app. Subclass `Rack::Proxy` and provide your `rewrite_env` and `rewrite_response` methods.\n\nInstallation\n----\n\nAdd the following to your `Gemfile`:\n\n```\ngem 'rack-proxy', '~> 0.7.7'\n```\n\nOr install:\n\n```\ngem install rack-proxy\n```\n\nUse Cases\n----\n\nBelow are some examples of real world use cases for Rack-Proxy. If you have done something interesting, add it to the list below and send a PR.\n\n* Allowing one app to act as central trust authority\n  * handle accepting self-sign certificates for internal apps\n  * authentication / authorization prior to proxying requests to a blindly trusting backend\n  * avoiding CORs complications by proxying from same domain to another backend\n* subdomain based pass-through to multiple apps\n* Complex redirect rules\n   * redirect pages with different extensions (ex: `.php`) to another app\n   * useful for handling awkward redirection rules for moved pages\n* fan Parallel Requests: turning a single API request to [multiple concurrent backend requests](https://github.com/typhoeus/typhoeus#making-parallel-requests) & merging results.\n* inserting or stripping headers required or problematic for certain clients\n\nOptions\n----\n\nOptions can be set when initializing the middleware or overriding a method.\n\n\n* `:streaming` - defaults to `true`, but does not work on all Ruby versions, recommend to set to `false`\n* `:ssl_verify_none` - tell `Net::HTTP` to not validate certs\n* `:ssl_version` - tell `Net::HTTP` to set a specific `ssl_version`\n* `:backend` - the URI parseable format of host and port of the target proxy backend. If not set it will assume the backend target is the same as the source.\n* `:read_timeout` - set proxy timeout it defaults to 60 seconds\n\nTo pass in options, when you configure your middleware you can pass them in as an optional hash.\n\n```ruby\nRails.application.config.middleware.use ExampleServiceProxy, backend: 'http://guides.rubyonrails.org', streaming: false\n```\n\nExamples\n----\n\nSee and run the examples below from `lib/rack_proxy_examples/`. To mount any example into an existing Rails app:\n\n1. create `config/initializers/proxy.rb`\n2. modify the file to require the example file\n```ruby\nrequire 'rack_proxy_examples/forward_host'\n```\n\n### Forward request to Host and Insert Header\n\nTest with `require 'rack_proxy_examples/forward_host'`\n\n```ruby\nclass ForwardHost < Rack::Proxy\n\n  def rewrite_env(env)\n    env[\"HTTP_HOST\"] = \"example.com\"\n    env\n  end\n\n  def rewrite_response(triplet)\n    status, headers, body = triplet\n\n    # example of inserting an additional header\n    headers[\"X-Foo\"] = \"Bar\"\n\n    # if you rewrite env, it appears that content-length isn't calculated correctly\n    # resulting in only partial responses being sent to users\n    # you can remove it or recalculate it here\n    headers[\"content-length\"] = nil\n\n    triplet\n  end\n\nend\n```\n\n### Disable SSL session verification when proxying a server with e.g. self-signed SSL certs\n\nTest with `require 'rack_proxy_examples/trusting_proxy'`\n\n```ruby\nclass TrustingProxy < Rack::Proxy\n\n  def rewrite_env(env)\n    env[\"HTTP_HOST\"] = \"self-signed.badssl.com\"\n\n    # We are going to trust the self-signed SSL\n    env[\"rack.ssl_verify_none\"] = true\n    env\n  end\n\n  def rewrite_response(triplet)\n    status, headers, body = triplet\n\n    # if you rewrite env, it appears that content-length isn't calculated correctly\n    # resulting in only partial responses being sent to users\n    # you can remove it or recalculate it here\n    headers[\"content-length\"] = nil\n\n    triplet\n  end\n\nend\n```\n\nThe same can be achieved for *all* requests going through the `Rack::Proxy` instance by using\n\n```ruby\nRack::Proxy.new(ssl_verify_none: true)\n```\n\n### Rails middleware example\n\nTest with `require 'rack_proxy_examples/example_service_proxy'`\n\n```ruby\n###\n# This is an example of how to use Rack-Proxy in a Rails application.\n#\n# Setup:\n# 1. rails new test_app\n# 2. cd test_app\n# 3. install Rack-Proxy in `Gemfile`\n#    a. `gem 'rack-proxy', '~> 0.7.7'`\n# 4. install gem: `bundle install`\n# 5. create `config/initializers/proxy.rb` adding this line `require 'rack_proxy_examples/example_service_proxy'`\n# 6. run: `SERVICE_URL=http://guides.rubyonrails.org rails server`\n# 7. open in browser: `http://localhost:3000/example_service`\n#\n###\nENV['SERVICE_URL'] ||= 'http://guides.rubyonrails.org'\n\nclass ExampleServiceProxy < Rack::Proxy\n  def perform_request(env)\n    request = Rack::Request.new(env)\n\n    # use rack proxy for anything hitting our host app at /example_service\n    if request.path =~ %r{^/example_service}\n        backend = URI(ENV['SERVICE_URL'])\n        # most backends required host set properly, but rack-proxy doesn't set this for you automatically\n        # even when a backend host is passed in via the options\n        env[\"HTTP_HOST\"] = backend.host\n\n        # This is the only path that needs to be set currently on Rails 5 & greater\n        env['PATH_INFO'] = ENV['SERVICE_PATH'] || '/configuring.html'\n\n        # don't send your sites cookies to target service, unless it is a trusted internal service that can parse all your cookies\n        env['HTTP_COOKIE'] = ''\n        super(env)\n    else\n      @app.call(env)\n    end\n  end\nend\n```\n\n### Using as middleware to forward only some extensions to another Application\n\nTest with `require 'rack_proxy_examples/rack_php_proxy'`\n\nExample: Proxying only requests that end with \".php\" could be done like this:\n\n```ruby\n###\n# Open http://localhost:3000/test.php to trigger proxy\n###\nclass RackPhpProxy < Rack::Proxy\n\n  def perform_request(env)\n    request = Rack::Request.new(env)\n    if request.path =~ %r{\\.php}\n      env[\"HTTP_HOST\"] = ENV[\"HTTP_HOST\"] ? URI(ENV[\"HTTP_HOST\"]).host : \"localhost\"\n      ENV[\"PHP_PATH\"] ||= '/manual/en/tutorial.firstpage.php'\n\n      # Rails 3 & 4\n      env[\"REQUEST_PATH\"] = ENV[\"PHP_PATH\"] || \"/php/#{request.fullpath}\"\n      # Rails 5 and above\n      env['PATH_INFO'] = ENV[\"PHP_PATH\"] || \"/php/#{request.fullpath}\"\n\n      env['content-length'] = nil\n\n      super(env)\n    else\n      @app.call(env)\n    end\n  end\n\n  def rewrite_response(triplet)\n    status, headers, body = triplet\n\n    # if you proxy depending on the backend, it appears that content-length isn't calculated correctly\n    # resulting in only partial responses being sent to users\n    # you can remove it or recalculate it here\n    headers[\"content-length\"] = nil\n\n    triplet\n  end\nend\n```\n\nTo use the middleware, please consider the following:\n\n1) For Rails we could add a configuration in `config/application.rb`\n\n```ruby\n  config.middleware.use RackPhpProxy, {ssl_verify_none: true}\n```\n\n2) For Sinatra or any Rack-based application:\n\n```ruby\nclass MyAwesomeSinatra < Sinatra::Base\n   use  RackPhpProxy, {ssl_verify_none: true}\nend\n```\n\nThis will allow to run the other requests through the application and only proxy the requests that match the condition from the middleware.\n\nSee tests for more examples.\n\n### SSL proxy for SpringBoot applications debugging\n\nWhenever you need to debug communication with external services with HTTPS protocol (like OAuth based) you have to be able to access to your local web app through HTTPS protocol too. Typical way is to use nginx or Apache httpd as a reverse proxy but it might be inconvinuent for development environment. Simple proxy server is a better way in this case. The only what we need is to unpack incoming SSL queries and proxy them to a backend. We can prepare minimal set of files to create autonomous proxy server.\n\nCreate `config.ru` file:\n```ruby\n#\n# config.ru\n#\nrequire 'rack'\nrequire 'rack-proxy'\n\nclass ForwardHost < Rack::Proxy\n  def rewrite_env(env)\n    env['HTTP_X_FORWARDED_HOST'] = env['SERVER_NAME']\n    env['HTTP_X_FORWARDED_PROTO'] = env['rack.url_scheme']\n    env\n  end\nend\n\nrun ForwardHost.new(backend: 'http://localhost:8080')\n```\n\nCreate `Gemfile` file:\n```ruby\nsource \"https://rubygems.org\"\n\ngem 'thin'\ngem 'rake'\ngem 'rack-proxy'\n```\n\nCreate `config.yml` file with configuration of web server `thin`:\n```yml\n---\nssl: true\nssl-key-file: keys/domain.key\nssl-cert-file: keys/domain.crt\nssl-disable-verify: false\n```\n\nCreate 'keys' directory and generate SSL key and certificates files `domain.key` and `domain.crt`\n\nRun `bundle exec thin start` for running it with `thin`'s default port.\n\nOr use `sudo -E thin start -C config.yml -p 443` for running with default for `https://` port.\n\nDon't forget to enable processing of `X-Forwarded-...` headers on your application side. Just add following strings to your `resources/application.yml` file.\n```yml\n---\nserver:\n  tomcat:\n    remote-ip-header: x-forwarded-for\n    protocol-header:  x-forwarded-proto\n  use-forward-headers:  true\n```\n\nAdd some domain name like `debug.your_app.com` into your local `/etc/hosts` file like\n```\n127.0.0.1\tdebug.your_app.com\n```\n\nNext start the proxy and your app. And now you can access to your Spring application through SSL connection via `https://debug.your_app.com` URI in a browser.\n\n### Using SSL/TLS certificates with HTTP connection\nThis may be helpful, when third-party API has authentication by client TLS certificates and you need to proxy your requests and sign them with certificate.\n\nJust specify Rack::Proxy SSL options and your request will use TLS HTTP connection:\n```ruby\n# config.ru\n. . .\n\ncert_raw = File.read('./certs/rootCA.crt')\nkey_raw = File.read('./certs/key.pem')\n\ncert = OpenSSL::X509::Certificate.new(cert_raw)\nkey = OpenSSL::PKey.read(key_raw)\n\nuse TLSProxy, cert: cert, key: key, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_PEER, ssl_version: 'TLSv1_2'\n```\n\nAnd rewrite host for example:\n```ruby\n# tls_proxy.rb\nclass TLSProxy < Rack::Proxy\n  attr_accessor :original_request, :query_params\n\n  def rewrite_env(env)\n    env[\"HTTP_HOST\"] = \"client-tls-auth-api.com:443\"\n    env\n  end\nend\n```\n\nWARNING\n----\n\nDoesn't work with `fakeweb`/`webmock`. Both libraries monkey-patch net/http code.\n\nTodos\n----\n\n* Make the docs up to date with the current use case for this code: everything except streaming which involved a rather ugly monkey patch and only worked in 1.8, but does not work now.\n* Improve and validate requirements for Host and Path rewrite rules\n* Ability to inject logger and set log level\n"
  },
  {
    "path": "Rakefile",
    "content": "require 'rubygems'\nrequire 'bundler'\nBundler::GemHelper.install_tasks\n\nrequire \"rake/testtask\"\ntask :test do\n  Rake::TestTask.new do |t|\n    t.libs << \"test\"\n    t.test_files = FileList['test/*_test.rb']\n    t.verbose = true\n  end\nend\n\ntask :default => :test\n"
  },
  {
    "path": "lib/net_http_hacked.rb",
    "content": "# We are hacking net/http to change semantics of streaming handling\n# from \"block\" semantics to regular \"return\" semantics.\n# We need it to construct a streamable rack triplet:\n#\n# [status, headers, streamable_body]\n#\n# See http://github.com/zerowidth/rack-streaming-proxy\n# for alternative that uses additional process.\n#\n# BTW I don't like monkey patching either\n# but this is not real monkey patching.\n# I just added some methods and named them very uniquely\n# to avoid eventual conflicts. You're safe. Trust me.\n#\n# Also, in Ruby 1.9.2 you could use Fibers to avoid hacking net/http.\n\nrequire 'net/https'\n\nclass Net::HTTP\n  # Original #request with block semantics.\n  #\n  # def request(req, body = nil, &block)\n  #   unless started?\n  #     start {\n  #       req['connection'] ||= 'close'\n  #       return request(req, body, &block)\n  #     }\n  #   end\n  #   if proxy_user()\n  #     unless use_ssl?\n  #       req.proxy_basic_auth proxy_user(), proxy_pass()\n  #     end\n  #   end\n  #\n  #   req.set_body_internal body\n  #   begin_transport req\n  #     req.exec @socket, @curr_http_version, edit_path(req.path)\n  #     begin\n  #       res = HTTPResponse.read_new(@socket)\n  #     end while res.kind_of?(HTTPContinue)\n  #     res.reading_body(@socket, req.response_body_permitted?) {\n  #       yield res if block_given?\n  #     }\n  #   end_transport req, res\n  #\n  #   res\n  # end\n\n  def begin_request_hacked(req)\n    begin_transport req\n    req.exec @socket, @curr_http_version, edit_path(req.path)\n    begin\n      res = Net::HTTPResponse.read_new(@socket)\n    end while res.kind_of?(Net::HTTPContinue)\n    res.begin_reading_body_hacked(@socket, req.response_body_permitted?)\n    @req_hacked, @res_hacked = req, res\n    @res_hacked\n  end\n\n  def end_request_hacked\n    @res_hacked.end_reading_body_hacked\n    end_transport @req_hacked, @res_hacked\n    @res_hacked\n  end\nend\n\nclass Net::HTTPResponse\n  # Original #reading_body with block semantics\n  #\n  # def reading_body(sock, reqmethodallowbody)  #:nodoc: internal use only\n  #   @socket = sock\n  #   @body_exist = reqmethodallowbody && self.class.body_permitted?\n  #   begin\n  #     yield\n  #     self.body   # ensure to read body\n  #   ensure\n  #     @socket = nil\n  #   end\n  # end\n\n  def begin_reading_body_hacked(sock, reqmethodallowbody)\n    @socket = sock\n    @body_exist = reqmethodallowbody && self.class.body_permitted?\n  end\n\n  def end_reading_body_hacked\n    self.body\n    @socket = nil\n  end\nend\n"
  },
  {
    "path": "lib/rack/http_streaming_response.rb",
    "content": "require \"net_http_hacked\"\nrequire \"stringio\"\n\nmodule Rack\n  # Wraps the hacked net/http in a Rack way.\n  class HttpStreamingResponse\n    STATUSES_WITH_NO_ENTITY_BODY = {\n      204 => true,\n      205 => true,\n      304 => true\n    }.freeze\n\n    attr_accessor :use_ssl, :verify_mode, :read_timeout, :ssl_version, :cert, :key\n\n    def initialize(request, host, port = nil)\n      @request, @host, @port = request, host, port\n    end\n\n    def body\n      self\n    end\n\n    def code\n      response.code.to_i.tap do |response_code|\n        STATUSES_WITH_NO_ENTITY_BODY[response_code] && close_connection\n      end\n    end\n    # #status is deprecated\n    alias_method :status, :code\n\n    def headers\n      Rack::Proxy.build_header_hash(response.to_hash)\n    end\n\n    # Can be called only once!\n    def each(&block)\n      return if connection_closed\n\n      response.read_body(&block)\n    ensure\n      close_connection\n    end\n\n    def to_s\n      @to_s ||= StringIO.new.tap { |io| each { |line| io << line } }.string\n    end\n\n    protected\n\n    # Net::HTTPResponse\n    def response\n      @response ||= session.begin_request_hacked(request)\n    end\n\n    # Net::HTTP\n    def session\n      @session ||= Net::HTTP.new(host, port).tap do |http|\n        http.use_ssl = use_ssl\n        http.verify_mode = verify_mode\n        http.read_timeout = read_timeout\n        http.ssl_version = ssl_version if ssl_version\n        http.cert = cert if cert\n        http.key = key if key\n        http.start\n      end\n    end\n\n    private\n\n    attr_reader :request, :host, :port\n\n    attr_accessor :connection_closed\n\n    def close_connection\n      return if connection_closed\n\n      session.end_request_hacked\n      session.finish\n      self.connection_closed = true\n    end\n  end\nend\n"
  },
  {
    "path": "lib/rack/proxy.rb",
    "content": "require \"net_http_hacked\"\nrequire \"rack/http_streaming_response\"\n\nmodule Rack\n\n  # Subclass and bring your own #rewrite_request and #rewrite_response\n  class Proxy\n    VERSION = \"0.7.7\".freeze\n\n    HOP_BY_HOP_HEADERS = {\n      'connection' => true,\n      'keep-alive' => true,\n      'proxy-authenticate' => true,\n      'proxy-authorization' => true,\n      'te' => true,\n      'trailer' => true,\n      'transfer-encoding' => true,\n      'upgrade' => true\n    }.freeze\n\n    class << self\n      def extract_http_request_headers(env)\n        headers = env.reject do |k, v|\n          !(/^HTTP_[A-Z0-9_\\.]+$/ === k) || v.nil?\n        end.map do |k, v|\n          [reconstruct_header_name(k), v]\n        end.then { |pairs| build_header_hash(pairs) }\n\n        x_forwarded_for = (headers['X-Forwarded-For'].to_s.split(/, +/) << env['REMOTE_ADDR']).join(', ')\n\n        headers.merge!('X-Forwarded-For' => x_forwarded_for)\n      end\n\n      def normalize_headers(headers)\n        mapped = headers.map do |k, v|\n          [titleize(k), if v.is_a? Array then v.join(\"\\n\") else v end]\n        end\n        build_header_hash Hash[mapped]\n      end\n\n      def build_header_hash(pairs)\n        if Rack.const_defined?(:Headers)\n          # Rack::Headers is only available from Rack 3 onward\n          Headers.new.tap { |headers| pairs.each { |k, v| headers[k] = v } }\n        else\n          # Rack::Utils::HeaderHash is deprecated from Rack 3 onward and is to be removed in 3.1\n          Utils::HeaderHash.new(pairs)\n        end\n      end\n\n      protected\n\n      def reconstruct_header_name(name)\n        titleize(name.sub(/^HTTP_/, \"\").gsub(\"_\", \"-\"))\n      end\n\n      def titleize(str)\n        str.split(\"-\").map(&:capitalize).join(\"-\")\n      end\n    end\n\n    # @option opts [String, URI::HTTP] :backend Backend host to proxy requests to\n    def initialize(app = nil, opts= {})\n      if app.is_a?(Hash)\n        opts = app\n        @app = nil\n      else\n        @app = app\n      end\n\n      @streaming = opts.fetch(:streaming, true)\n      @ssl_verify_none = opts.fetch(:ssl_verify_none, false)\n      @backend = opts[:backend] ? URI(opts[:backend]) : nil\n      @read_timeout = opts.fetch(:read_timeout, 60)\n      @ssl_version = opts[:ssl_version]\n      @cert = opts[:cert]\n      @key = opts[:key]\n      @verify_mode = opts[:verify_mode]\n\n      @username = opts[:username]\n      @password = opts[:password]\n\n      @opts = opts\n    end\n\n    def call(env)\n      rewrite_response(perform_request(rewrite_env(env)))\n    end\n\n    # Return modified env\n    def rewrite_env(env)\n      env\n    end\n\n    # Return a rack triplet [status, headers, body]\n    def rewrite_response(triplet)\n      triplet\n    end\n\n    protected\n\n    def perform_request(env)\n      source_request = Rack::Request.new(env)\n\n      # Initialize request\n      if source_request.fullpath == \"\"\n        full_path = URI.parse(env['REQUEST_URI']).request_uri\n      else\n        full_path = source_request.fullpath\n      end\n\n      target_request = Net::HTTP.const_get(source_request.request_method.capitalize, false).new(full_path)\n\n      # Setup headers\n      target_request.initialize_http_header(self.class.extract_http_request_headers(source_request.env))\n\n      # Setup body\n      if target_request.request_body_permitted? && source_request.body\n        target_request.body_stream    = source_request.body\n        target_request.content_length = source_request.content_length.to_i\n        target_request.content_type   = source_request.content_type if source_request.content_type\n        target_request.body_stream.rewind\n      end\n\n      # Use basic auth if we have to\n      target_request.basic_auth(@username, @password) if @username && @password\n\n      backend = env.delete('rack.backend') || @backend || source_request\n      use_ssl = backend.scheme == \"https\" || @cert\n      read_timeout = env.delete('http.read_timeout') || @read_timeout\n\n      # Create the response\n      if @streaming\n        # streaming response (the actual network communication is deferred, a.k.a. streamed)\n        target_response = HttpStreamingResponse.new(target_request, backend.host, backend.port)\n        target_response.use_ssl = use_ssl\n        target_response.read_timeout = read_timeout\n        target_response.ssl_version = @ssl_version if @ssl_version\n        target_response.verify_mode = (@verify_mode || OpenSSL::SSL::VERIFY_NONE) if use_ssl\n        target_response.cert = @cert if @cert\n        target_response.key = @key if @key\n      else\n        http = Net::HTTP.new(backend.host, backend.port)\n        http.use_ssl = use_ssl if use_ssl\n        http.read_timeout = read_timeout\n        http.ssl_version = @ssl_version if @ssl_version\n        http.verify_mode = (@verify_mode || OpenSSL::SSL::VERIFY_NONE if use_ssl) if use_ssl\n        http.cert = @cert if @cert\n        http.key = @key if @key\n\n        target_response = http.start do\n          http.request(target_request)\n        end\n      end\n\n      code    = target_response.code\n      headers = self.class.normalize_headers(target_response.respond_to?(:headers) ? target_response.headers : target_response.to_hash)\n      body    = target_response.body || [\"\"]\n      body    = [body] unless body.respond_to?(:each)\n\n      # According to https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-14#section-7.1.3.1Acc\n      # should remove hop-by-hop header fields\n      headers.reject! { |k| HOP_BY_HOP_HEADERS[k.downcase] }\n\n      [code, headers, body]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/rack-proxy.rb",
    "content": "require \"rack/proxy\""
  },
  {
    "path": "lib/rack_proxy_examples/example_service_proxy.rb",
    "content": "###\n# This is an example of how to use Rack-Proxy in a Rails application.\n#\n# Setup:\n# 1. rails new test_app\n# 2. cd test_app\n# 3. install Rack-Proxy in `Gemfile`\n#    a. `gem 'rack-proxy', '~> 0.7.7'`\n# 4. install gem: `bundle install`\n# 5. create `config/initializers/proxy.rb` adding this line `require 'rack_proxy_examples/example_service_proxy'`\n# 6. run: `SERVICE_URL=http://guides.rubyonrails.org rails server`\n# 7. open in browser: `http://localhost:3000/example_service`\n#\n###\nENV['SERVICE_URL'] ||= 'http://guides.rubyonrails.org'\n\nclass ExampleServiceProxy < Rack::Proxy\n  def perform_request(env)\n    request = Rack::Request.new(env)\n\n    # use rack proxy for anything hitting our host app at /example_service\n    if request.path =~ %r{^/example_service}\n        backend = URI(ENV['SERVICE_URL'])\n        # most backends required host set properly, but rack-proxy doesn't set this for you automatically\n        # even when a backend host is passed in via the options\n        env[\"HTTP_HOST\"] = backend.host\n\n        # This is the only path that needs to be set currently on Rails 5 & greater\n        env['PATH_INFO'] = ENV['SERVICE_PATH'] || '/configuring.html'\n\n        # don't send your sites cookies to target service, unless it is a trusted internal service that can parse all your cookies\n        env['HTTP_COOKIE'] = ''\n        super(env)\n    else\n      @app.call(env)\n    end\n  end\nend\n\nRails.application.config.middleware.use ExampleServiceProxy, backend: ENV['SERVICE_URL'], streaming: false\n"
  },
  {
    "path": "lib/rack_proxy_examples/forward_host.rb",
    "content": "class ForwardHost < Rack::Proxy\n\n  def rewrite_env(env)\n    env[\"HTTP_HOST\"] = \"example.com\"\n    env\n  end\n\n  def rewrite_response(triplet)\n    status, headers, body = triplet\n\n    # example of inserting an additional header\n    headers[\"X-Foo\"] = \"Bar\"\n    \n    # if you rewrite env, it appears that content-length isn't calculated correctly\n    # resulting in only partial responses being sent to users\n    # you can remove it or recalculate it here\n    headers[\"content-length\"] = nil\n\n    triplet\n  end\n\nend\n\nRails.application.config.middleware.use ForwardHost, backend: 'http://example.com', streaming: false\n"
  },
  {
    "path": "lib/rack_proxy_examples/rack_php_proxy.rb",
    "content": "###\n# Open http://localhost:3000/test.php to trigger proxy\n###\nclass RackPhpProxy < Rack::Proxy\n\n  def perform_request(env)\n    request = Rack::Request.new(env)\n    if request.path =~ %r{\\.php}\n      env[\"HTTP_HOST\"] = ENV[\"HTTP_HOST\"] ? URI(ENV[\"HTTP_HOST\"]).host : \"localhost\"\n      ENV[\"PHP_PATH\"] ||= '/manual/en/tutorial.firstpage.php'\n       \n      # Rails 3 & 4\n      env[\"REQUEST_PATH\"] = ENV[\"PHP_PATH\"] || \"/php/#{request.fullpath}\"\n      # Rails 5 and above\n      env['PATH_INFO'] = ENV[\"PHP_PATH\"] || \"/php/#{request.fullpath}\"\n\n      env['content-length'] = nil\n      \n      super(env)\n    else\n      @app.call(env)\n    end\n  end\n\n  def rewrite_response(triplet)\n    status, headers, body = triplet\n    \n    # if you proxy depending on the backend, it appears that content-length isn't calculated correctly\n    # resulting in only partial responses being sent to users\n    # you can remove it or recalculate it here\n    headers[\"content-length\"] = nil\n\n    triplet\n  end\nend\n\nRails.application.config.middleware.use RackPhpProxy, backend: ENV[\"HTTP_HOST\"]='http://php.net', streaming: false\n"
  },
  {
    "path": "lib/rack_proxy_examples/trusting_proxy.rb",
    "content": "class TrustingProxy < Rack::Proxy\n\n  def rewrite_env(env)\n    env[\"HTTP_HOST\"] = \"self-signed.badssl.com\"\n\n    # We are going to trust the self-signed SSL \n    env[\"rack.ssl_verify_none\"] = true\n    env\n  end\n\n  def rewrite_response(triplet)\n    status, headers, body = triplet\n    \n    # if you rewrite env, it appears that content-length isn't calculated correctly\n    # resulting in only partial responses being sent to users\n    # you can remove it or recalculate it here\n    headers[\"content-length\"] = nil\n\n    triplet\n  end\n\nend\n\nRails.application.config.middleware.use TrustingProxy, backend: 'https://self-signed.badssl.com', streaming: false\n"
  },
  {
    "path": "rack-proxy.gemspec",
    "content": "# -*- encoding: utf-8 -*-\n$:.push File.expand_path(\"../lib\", __FILE__)\nrequire \"rack-proxy\"\n\nGem::Specification.new do |s|\n  s.name        = \"rack-proxy\"\n  s.version     = Rack::Proxy::VERSION\n  s.platform    = Gem::Platform::RUBY\n  s.license     = 'MIT'\n  s.authors     = [\"Jacek Becela\"]\n  s.email       = [\"jacek.becela@gmail.com\"]\n  s.homepage    = \"https://github.com/ncr/rack-proxy\"\n  s.summary     = %q{A request/response rewriting HTTP proxy. A Rack app.}\n  s.description = %q{A Rack app that provides request/response rewriting proxy capabilities with streaming.}\n  s.required_ruby_version = '>= 2.6'\n\n  s.files         = `git ls-files`.split(\"\\n\")\n  s.test_files    = `git ls-files -- {test,spec,features}/*`.split(\"\\n\")\n  s.executables   = `git ls-files -- bin/*`.split(\"\\n\").map{ |f| File.basename(f) }\n  s.require_paths = [\"lib\"]\n\n  s.add_dependency(\"rack\")\n  s.add_development_dependency(\"rack-test\")\n  s.add_development_dependency(\"test-unit\")\nend\n"
  },
  {
    "path": "test/http_streaming_response_test.rb",
    "content": "require \"test_helper\"\nrequire \"rack/http_streaming_response\"\n\nclass HttpStreamingResponseTest < Test::Unit::TestCase\n\n  def setup\n    host, req = \"example.com\", Net::HTTP::Get.new(\"/\")\n    @response = Rack::HttpStreamingResponse.new(req, host, 443)\n    @response.use_ssl = true\n  end\n\n  def test_streaming\n    # Response status\n    assert_equal 200, @response.status\n    assert_equal 200, @response.status\n\n    # Headers\n    headers = @response.headers\n\n    assert headers.size.positive?\n\n    assert_match %r{text/html; ?charset=utf-8}, headers[\"content-type\"].first.downcase\n    assert_equal headers[\"content-type\"], headers[\"CoNtEnT-TyPe\"]\n    assert headers[\"content-length\"].first.to_i.positive?\n\n    # Body\n    chunks = []\n    @response.body.each do |chunk|\n      chunks << chunk\n    end\n\n    assert chunks.size.positive?\n    chunks.each do |chunk|\n      assert chunk.is_a?(String)\n    end\n\n  end\n\n  def test_to_s\n    assert_equal @response.headers[\"Content-Length\"].first.to_i, @response.body.to_s.bytesize\n  end\n\n  def test_to_s_called_twice\n    body = @response.body\n    assert_equal body.to_s, body.to_s\n  end\n\nend\n"
  },
  {
    "path": "test/net_http_hacked_test.rb",
    "content": "require \"test_helper\"\nrequire \"net_http_hacked\"\n\nclass NetHttpHackedTest < Test::Unit::TestCase\n  \n  def test_net_http_hacked\n    req = Net::HTTP::Get.new(\"/\")\n    http = Net::HTTP.start(\"www.iana.org\", \"80\")\n\n    # Response code\n    res = http.begin_request_hacked(req)\n    assert res.code == \"200\"\n\n    # Headers\n    headers = {}\n    res.each_header { |k, v| headers[k] = v }\n\n    assert headers.size > 0\n    assert headers[\"content-type\"] == \"text/html; charset=UTF-8\"\n    assert !headers[\"date\"].nil?\n\n    # Body\n    chunks = []\n    res.read_body do |chunk|\n      chunks << chunk\n    end\n\n    assert chunks.size > 0\n    chunks.each do |chunk|\n      assert chunk.is_a?(String)\n    end\n\n    http.end_request_hacked\n  end\n  \nend\n"
  },
  {
    "path": "test/rack_proxy_test.rb",
    "content": "require \"test_helper\"\nrequire \"rack/proxy\"\n\nclass RackProxyTest < Test::Unit::TestCase\n  class HostProxy < Rack::Proxy\n    attr_accessor :host\n\n    def rewrite_env(env)\n      env[\"HTTP_HOST\"] = self.host || 'example.com'\n      env\n    end\n  end\n\n  def app(opts = {})\n    return @app ||= HostProxy.new(opts)\n  end\n\n  def test_http_streaming\n    get \"/\"\n    assert last_response.ok?\n\n    assert_match(/Example Domain/, last_response.body)\n  end\n\n  def test_http_full_request\n    app(:streaming => false)\n    get \"/\"\n    assert last_response.ok?\n    assert_match(/Example Domain/, last_response.body)\n  end\n\n  def test_http_full_request_headers\n    app(:streaming => false)\n    app.host = 'www.google.com'\n    get \"/\"\n    assert !Array(last_response['Set-Cookie']).empty?, 'Google always sets a cookie, yo. Where my cookies at?!'\n  end\n\n  def test_https_streaming\n    app.host = 'www.apple.com'\n    get 'https://example.com'\n    assert last_response.ok?\n    assert_match(/(itunes|iphone|ipod|mac|ipad)/, last_response.body)\n  end\n\n  def test_https_streaming_tls\n    app(:ssl_version => :TLSv1).host = 'www.apple.com'\n    get 'https://example.com'\n    assert last_response.ok?\n    assert_match(/(itunes|iphone|ipod|mac|ipad)/, last_response.body)\n  end\n\n  def test_https_full_request\n    app(:streaming => false).host = 'www.apple.com'\n    get 'https://example.com'\n    assert last_response.ok?\n    assert_match(/(itunes|iphone|ipod|mac|ipad)/, last_response.body)\n  end\n\n  def test_https_full_request_tls\n    app({:streaming => false, :ssl_version => :TLSv1}).host = 'www.apple.com'\n    get 'https://example.com'\n    assert last_response.ok?\n    assert_match(/(itunes|iphone|ipod|mac|ipad)/, last_response.body)\n  end\n\n  def test_normalize_headers\n    proxy_class = Rack::Proxy\n    headers = { 'header_array' => ['first_entry'], 'header_non_array' => :entry }\n\n    normalized_headers = proxy_class.send(:normalize_headers, headers)\n    assert normalized_headers.instance_of?(Rack::Utils::HeaderHash)\n    assert normalized_headers['header_array'] == 'first_entry'\n    assert normalized_headers['header_non_array'] == :entry\n  end\n\n  def test_header_reconstruction\n    proxy_class = Rack::Proxy\n\n    header = proxy_class.send(:reconstruct_header_name, \"HTTP_ABC\")\n    assert header == \"Abc\"\n\n    header = proxy_class.send(:reconstruct_header_name, \"HTTP_ABC_D\")\n    assert header == \"Abc-D\"\n  end\n\n  def test_extract_http_request_headers\n    proxy_class = Rack::Proxy\n    env = {\n      'NOT-HTTP-HEADER' => 'test-value',\n      'HTTP_ACCEPT' => 'text/html',\n      'HTTP_CONNECTION' => nil,\n      'HTTP_CONTENT_MD5' => 'deadbeef',\n      'HTTP_HEADER.WITH.PERIODS' => 'stillmooing'\n    }\n\n    headers = proxy_class.extract_http_request_headers(env)\n    assert headers.key?('ACCEPT')\n    assert headers.key?('CONTENT-MD5')\n    assert headers.key?('HEADER.WITH.PERIODS')\n    assert !headers.key?('CONNECTION')\n    assert !headers.key?('NOT-HTTP-HEADER')\n  end\n\n  def test_duplicate_headers\n    proxy_class = Rack::Proxy\n    env = { 'Set-Cookie' => [\"cookie1=foo\", \"cookie2=bar\"] }\n\n    headers = proxy_class.normalize_headers(env)\n    assert headers['Set-Cookie'].include?('cookie1=foo'), \"Include the first value\"\n    assert headers['Set-Cookie'].include?(\"\\n\"), \"Join multiple cookies with newlines\"\n    assert headers['Set-Cookie'].include?('cookie2=bar'), \"Include the second value\"\n  end\n\n\n  def test_handles_missing_content_length\n    assert_nothing_thrown do\n      post \"/\", nil, \"CONTENT_LENGTH\" => nil\n    end\n  end\n\n  def test_response_header_included_Hop_by_hop\n    app({:streaming => true}).host = 'mockapi.io'\n    get 'https://example.com/oauth2/token/info?access_token=123'\n    assert !last_response.headers.key?('transfer-encoding')\n  end\nend\n"
  },
  {
    "path": "test/test_helper.rb",
    "content": "require \"rubygems\"\nrequire 'bundler/setup'\nrequire 'bundler/gem_tasks'\nrequire \"test/unit\"\n\nrequire \"rack\"\nrequire \"rack/test\"\n\nTest::Unit::TestCase.class_eval do\n  include Rack::Test::Methods\nend\n"
  }
]