lib/unxf.rb in unxf-1.0.0 vs lib/unxf.rb in unxf-2.0.0

- old
+ new

@@ -1,8 +1,10 @@ # -*- encoding: binary -*- require 'rpatricia' +# Rack middleware to remove "HTTP_X_FORWARDED_FOR" in the Rack environment and +# replace "REMOTE_ADDR" with the value of the original client address. class UnXF # :stopdoc: # reduce garbage overhead by using constant strings REMOTE_ADDR = "REMOTE_ADDR".freeze HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR" @@ -15,53 +17,72 @@ RFC_1918 = %w(10.0.0.0/8 172.16.0.0/12 192.168.0.0/16) # localhost addresses (127.0.0.0/8) LOCALHOST = %w(127.0.0.0/8) + # In your Rack config.ru: + # + # use UnXF + # + # If you do not want to trust any hosts other than "0.6.6.6", + # you may only specify one host to trust: + # + # use UnXF, "0.6.6.6" + # + # If you want to trust "0.6.6.6" in addition to the default set of hosts: + # + # use UnXF, [ :RFC_1918, :LOCALHOST, "0.6.6.6" ] + # def initialize(app, trusted = [:RFC_1918, :LOCALHOST]) @app = app @trusted = Patricia.new Array(trusted).each do |mask| mask = UnXF.const_get(mask) if Symbol === mask Array(mask).each { |m| @trusted.add(m, true) } end end - def call(env) + # Rack entry point + def call(env) # :nodoc: + unxf!(env) || @app.call(env) + end + + # returns +nil+ on success and a Rack response triplet on failure + # This allows existing applications to use UnXF without putting it + # into the middleware stack (to avoid increasing stack depth and GC time) + def unxf!(env) if xff_str = env.delete(HTTP_X_FORWARDED_FOR) xff = xff_str.split(/\s*,\s*/) addr = env[REMOTE_ADDR] begin while @trusted.include?(addr) && tmp = xff.pop addr = tmp end rescue ArgumentError - return on_bad_addr(env, xff_str) + return on_broken_addr(env, xff_str) end - env[REMOTE_ADDR] = addr - # it's stupid to have https at any point other than the first # proxy in the chain, so we don't support that if xff.empty? + env[REMOTE_ADDR] = addr env.delete(HTTP_X_FORWARDED_PROTO) =~ /\Ahttps\b/ and env[RACK_URL_SCHEME] = HTTPS else - return on_bad_addr(env, xff_str) + return on_untrusted_addr(env, xff_str) end end + nil + end + + # Our default action on a broken address is to just fall back to calling + # the app without modifying the env + def on_broken_addr(env, xff_str) @app.call(env) end - # Our default action on a bad address is to just continue dispatching - # the application with the existing environment - # You may extend this object to override this error - def on_bad_addr(env, xff_str) - # be sure to inspect xff_str to escape it, control characters may be - # present in the HTTP headers may appear in the header value and - # used to exploit anyone who opens the log file. nginx doesn't - # filter/reject control characters, nor does Mongrel... - env["rack.logger"].error( - "bad XFF #{xff_str.inspect} from #{env[REMOTE_ADDR]}") - [ 400, [ %w(Content-Length 0), %w(Content-Type text/html) ], [] ] + # Our default action on an untrusted address is to just fall back to calling + # the app without modifying the env + def on_untrusted_addr(env, xff_str) + @app.call(env) end end