|
# # # # |
|
# Rack::NoTags removes < and > from all incoming requests |
|
# http://rbjl.net/12-rack-notags |
|
# |
|
# (c) 2009 - 2010 Jan Lelis |
|
# This software is licensed under the MIT license. |
|
# # # # |
|
|
|
module Rack |
|
|
|
# Sometimes, simple approaches to solve a problem are the best, |
|
# because of the danger, that complex ones have holes... |
|
# |
|
# Usage (Rails) |
|
# |
|
# In config/environment.rb add: |
|
# require 'path/to/rack-notags.rb' |
|
# config.middleware.use Rack::NoTags |
|
# |
|
# You can activate a different filter mode with: |
|
# config.middleware.use Rack::NoTags, :paranoid |
|
|
|
class NoTags |
|
PATTERNS = { # replacement => [ array, of, patterns ] |
|
:brackets_only => { |
|
'<' => %w[ < %3C ], |
|
'>' => %w[ > %3E ] |
|
}, |
|
|
|
# similar to Racks escape_html + url_encoded variants |
|
:valid_xml => { |
|
'<' => %w[ < %3C ], |
|
'>' => %w[ > %3E ], |
|
'&' => %w[ & %26 ], |
|
''' => %w[ ' %27 ], |
|
'"' => %w[ " %22 ] |
|
}, |
|
|
|
# encodings which might be interpreted as < or > in some situations |
|
:paranoid => { |
|
'' => %w[ < > %3C %3E ] + [ |
|
/&[lg]t;?/i, |
|
/�{0,5}6[02];?/, |
|
/�{0,5}3[ce];?/i ] |
|
} |
|
} |
|
|
|
def initialize(app, mode = :brackets_only, ignore = {}) |
|
@app = app |
|
@patterns = PATTERNS[mode.to_sym] # mode selects the right pattern set |
|
@ignore = ignore # if one entry of the ignore list matches a post param, |
|
# nothing will be filtered |
|
end |
|
|
|
def call(env) |
|
# get params in a nice format |
|
post_params = Rack::Utils.parse_query(env['rack.input'].read, "&") |
|
get_params = Rack::Utils.parse_query(env['QUERY_STRING'], "&") |
|
|
|
|
|
# remove @patterns |
|
unless ignore?(post_params) |
|
post_params = strip_all(post_params) |
|
get_params = strip_all(get_params) |
|
end |
|
|
|
# update envirionment |
|
env['rack.input'] = StringIO.new(Rack::Utils.build_query(post_params)) |
|
env['QUERY_STRING'] = Rack::Utils.build_query(get_params) |
|
|
|
# call framework |
|
@app.call(env) |
|
end |
|
|
|
private |
|
|
|
# check if param is on ignore list |
|
def ignore?(params) |
|
ret = false |
|
|
|
@ignore.each{ |ign_param, ign_value| |
|
params.each{ |param, value| |
|
if !value.is_a?(Array) && |
|
ign_param.to_s == param.to_s && |
|
ign_value.to_s == value.to_s |
|
|
|
ret = true |
|
end |
|
} |
|
} |
|
|
|
ret |
|
end |
|
|
|
# applies each 'to-substitute'-pattern to the string |
|
def strip(string) |
|
begin |
|
@patterns.each{ |replacement, patterns| |
|
patterns.each{ |pattern| |
|
string = string.gsub(pattern, replacement) |
|
} |
|
} |
|
end while catch :still_some do |
|
# check if there is still any pattern that needs to be aplied |
|
@patterns.each{ |_, patterns| |
|
patterns.each{ |pattern| |
|
if string[pattern] # like =~ but =~ is not |
|
# defined for two strings |
|
throw :still_some, true |
|
end |
|
} |
|
} |
|
false |
|
end |
|
|
|
string |
|
end |
|
|
|
# looks at every param-element an sends it to the strip method |
|
def strip_all(params) |
|
ret = {} |
|
params.each{ |param, value| |
|
ret[strip(param)] = value.is_a?(Array) ? value.map{|v|strip(v)} : strip(value) |
|
} |
|
|
|
ret |
|
end |
|
|
|
end |
|
end |
|
|