lib/action_dispatch/middleware/remote_ip.rb



module ActionDispatch
  class RemoteIp
    class IpSpoofAttackError < StandardError ; end

    class RemoteIpGetter
      def initialize(env, check_ip_spoofing, trusted_proxies)
        @env = env
        @check_ip_spoofing = check_ip_spoofing
        @trusted_proxies = trusted_proxies
      end

      def remote_addrs
        @remote_addrs ||= begin
          list = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : []
          list.reject { |addr| addr =~ @trusted_proxies }
        end
      end

      def to_s
        return remote_addrs.first if remote_addrs.any?

        forwarded_ips = @env['HTTP_X_FORWARDED_FOR'] ? @env['HTTP_X_FORWARDED_FOR'].strip.split(/[,\s]+/) : []

        if client_ip = @env['HTTP_CLIENT_IP']
          if @check_ip_spoofing && !forwarded_ips.include?(client_ip)
            # We don't know which came from the proxy, and which from the user
            raise IpSpoofAttackError, "IP spoofing attack?!" \
              "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \
              "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
          end
          return client_ip
        end

        return forwarded_ips.reject { |ip| ip =~ @trusted_proxies }.last || @env["REMOTE_ADDR"]
      end
    end

    def initialize(app, check_ip_spoofing = true, trusted_proxies = nil)
      @app = app
      @check_ip_spoofing = check_ip_spoofing
      regex = '(^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.)'
      regex << "|(#{trusted_proxies})" if trusted_proxies
      @trusted_proxies = Regexp.new(regex, "i")
    end

    def call(env)
      env["action_dispatch.remote_ip"] = RemoteIpGetter.new(env, @check_ip_spoofing, @trusted_proxies)
      @app.call(env)
    end
  end
end