require "ipaddr" module Rack module Throttle class Rules < TimeWindow ## # @param [#call] app # @param [Hash{Symbol => Object}] options # @option options [Integer] :max (1) def initialize(app, options = {}) super end def rules @rules ||= begin rs = options[:rules] rs.sort_by { |r| [r[:proc].to_s, r[:path].to_s] }.reverse end end def retry_after @min ||= (options[:min] || 3600) end def default_limit @default_limit ||= options[:default] || 1_000_000_000 end def ips @ips ||= (options[:ip_whitelist] || []).map { |ip| IPAddr.new(ip) } || [] end def whitelisted?(request) return true if ip_whitelisted?(IPAddr.new(ip(request))) return true if rule_whitelisted?(request) false end def ip_whitelisted?(request_ip) !!ips.find { |ip| ip.include?(request_ip) } end def rule_whitelisted?(request) rule = rule_for(request) rule ? rule[:whitelisted] : false end def rule_for(request) rules.find do |rule| next unless rule[:method] == request.request_method.to_s next if rule[:proc] && rule[:proc].call(request) == false next if rule[:path] && !path_matches?(rule, request.path.to_s) rule end end def path_matches?(rule, path) return true unless rule[:path] return true if path.to_s.match(rule[:path]) false end def max_per_window(request) rule = rule_for(request) rule ? rule[:limit] : default_limit end def client_identifier(request) if (rule = rule_for(request)) client_identifier_for_rule(request, rule) else ip(request) end end def client_identifier_for_rule(request, rule) if rule[:proc] "#{rule[:method]}_#{rule[:proc].call(request)}" elsif rule[:path] "#{ip(request)}_#{rule[:method]}_#{rule[:path]}" elsif rule[:method] "#{ip(request)}_#{rule[:method]}" else raise NotImplementedError end end def ip(request) request.ip.to_s end def cache_key(request) [super, Time.now.strftime(time_string)].join(':') end def time_string @time_string ||= case options[:time_window] when :second then '%Y-%m-%dT%H:%M:%S' when :minute then '%Y-%m-%dT%H:%M' when :hour then '%Y-%m-%dT%H' when :day then '%Y-%m-%d' else '%Y-%m-%dT%H:%M:%S' end end end end end