lib/rack/protection/escaped_params.rb



# frozen_string_literal: true

require 'rack/protection'
require 'rack/utils'
require 'tempfile'

begin
  require 'escape_utils'
rescue LoadError
end

module Rack
  module Protection
    ##
    # Prevented attack::   XSS
    # Supported browsers:: all
    # More infos::         http://en.wikipedia.org/wiki/Cross-site_scripting
    #
    # Automatically escapes Rack::Request#params so they can be embedded in HTML
    # or JavaScript without any further issues.
    #
    # Options:
    # escape:: What escaping modes to use, should be Symbol or Array of Symbols.
    #          Available: :html (default), :javascript, :url
    class EscapedParams < Base
      extend Rack::Utils

      class << self
        alias escape_url escape
        public :escape_html
      end

      default_options escape: :html,
                      escaper: defined?(EscapeUtils) ? EscapeUtils : self

      def initialize(*)
        super

        modes       = Array options[:escape]
        @escaper    = options[:escaper]
        @html       = modes.include? :html
        @javascript = modes.include? :javascript
        @url        = modes.include? :url

        return unless @javascript && (!@escaper.respond_to? :escape_javascript)

        raise('Use EscapeUtils for JavaScript escaping.')
      end

      def call(env)
        request  = Request.new(env)
        get_was  = handle(request.GET)
        post_was = begin
          handle(request.POST)
        rescue StandardError
          nil
        end
        app.call env
      ensure
        request.GET.replace  get_was  if get_was
        request.POST.replace post_was if post_was
      end

      def handle(hash)
        was = hash.dup
        hash.replace escape(hash)
        was
      end

      def escape(object)
        case object
        when Hash   then escape_hash(object)
        when Array  then object.map { |o| escape(o) }
        when String then escape_string(object)
        when Tempfile then object
        end
      end

      def escape_hash(hash)
        hash = hash.dup
        hash.each { |k, v| hash[k] = escape(v) }
        hash
      end

      def escape_string(str)
        str = @escaper.escape_url(str)        if @url
        str = @escaper.escape_html(str)       if @html
        str = @escaper.escape_javascript(str) if @javascript
        str
      end
    end
  end
end