Skip to content

Instantly share code, notes, and snippets.

@moro
Created October 6, 2009 11:59
Show Gist options
  • Save moro/202951 to your computer and use it in GitHub Desktop.
Save moro/202951 to your computer and use it in GitHub Desktop.
require 'rack'
autoload 'Iconv', 'iconv'
module Rack
class IconvVerifier
def initialize(encoding = 'UTF-8')
@encoding = encoding
end
def verify(str)
begin
Iconv.conv(@encoding, @encoding, str)
true
rescue Iconv::IllegalSequence, Iconv::InvalidCharacter
false
end
end
end
module UnicodeUnpackVerifier
def verify(str)
begin
str.unpack("U*")
true
rescue ArgumentError
false
end
end
module_function :verify
end
class Ruby19Verifier
def initialize(encoding = 'UTF-8')
@encoding = encoding
end
def verify(str)
str.force_encoding(@encoding).valid_encoding?
end
end
class InvalidCharBlocker
class << self
attr_reader :verifiers
def detect_engine(encoding)
factory = if Object.const_defined?("ActiveSupport") then :activesupport
elsif "ruby1.9".respond_to?(:valid_encoding?) then :ruby19
elsif encoding == 'UTF-8' then :unpack
else :iconv
end
verifiers[factory] && verifiers[factory].call(encoding)
end
end
@verifiers = {}
verifiers[:activesupport] = lambda{|*ignore|
require 'activesupport' # require only once.
(verifiers[:activesupport] = lambda{|*ignore| ActiveSupport::Multibyte }).call
}
verifiers[:iconv] = lambda{|enc, *ignore| IconvVerifier.new(enc) }
verifiers[:ruby19] = lambda{|enc, *ignore| "ruby1.9".respond_to?(:valid_encoding?) && Ruby19Verifier.new(enc) }
verifiers[:unpack] = lambda{|enc, *ignore| enc == 'UTF-8' && UnicodeUnpackVerifier }
def initialize(app, engine = :auto, encoding = 'UTF-8', *args)
@app = app
@verifier = if (engine == :auto)
self.class.detect_engine(encoding)
else
factory = self.class.verifiers[engine]
factory && factory.call(encoding, *args)
end
raise ArgumentError.new("can't find valid verifier") if @verifier.nil?
end
def call(env)
req = Rack::Request.new(env)
if [req.GET, req.POST].any?{|param| param && include_invalid_char?(param) }
return Rack::Response.new(['Bad Request'], 400).finish
else
@app.call(env)
end
end
private
def include_invalid_char?(param)
param.any?{|k,v| !validate(k, v) }
end
def validate(*vars)
vars.all? do |var|
case var
when Hash
var[:tempfile].respond_to?(:read) ? true : !include_invalid_char?(var)
when Array
validate(*var)
else
str = if 'ruby19'.respond_to?(:force_encoding)
var.dup.to_s.force_encoding(Encoding.default_external)
else
var.to_s
end
@verifier.verify(str)
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment