module Roda::RodaPlugins::RouteCsrf::InstanceMethods

def check_csrf!(opts=OPTS, &block)

if a block is not given, use the :csrf_failure option to determine how to handle it.
Otherwise, if a block is given, treat it as a routing block and yield to it, and
If the CSRF token is valid or the request does not require a CSRF token, return nil.
Check that the submitted CSRF token is valid, if the request requires a CSRF token.
def check_csrf!(opts=OPTS, &block)
  if msg = csrf_invalid_message(opts)
    if block
      @_request.on(&block)
    end
    
    case failure_action = opts.fetch(:csrf_failure, csrf_options[:csrf_failure])
    when :raise
      raise InvalidToken, msg
    when :empty_403
      @_response.status = 403
      headers = @_response.headers
      headers.clear
      headers[RodaResponseHeaders::CONTENT_TYPE] = 'text/html'
      headers[RodaResponseHeaders::CONTENT_LENGTH] ='0'
      throw :halt, @_response.finish_with_body([])
    when :clear_session
      session.clear
    when :csrf_failure_method
      @_request.on{_roda_route_csrf_failure(@_request)}
    when Proc
      RodaPlugins.warn "Passing a Proc as the :csrf_failure option value to check_csrf! is deprecated"
      @_request.on{instance_exec(@_request, &failure_action)} # Deprecated
    else
      raise RodaError, "Unsupported :csrf_failure option: #{failure_action.inspect}"
    end
  end
end

def csrf_compare(s1, s2)

Perform a constant-time comparison of the two strings, returning true if they match and false otherwise.
def csrf_compare(s1, s2)
  Rack::Utils.secure_compare(s1, s2)
end

def csrf_field

for the meta tag.
The name of the hidden input tag containing the CSRF token. Also used as the name
def csrf_field
  csrf_options[:field]
end

def csrf_formaction_tag(path, *args)

path argument is required.
depending on which button was clicked. See csrf_token for arguments, but the
with formaction, so the same form can be used to submit to multiple endpoints
An HTML hidden input tag string containing the CSRF token, used for inputs
def csrf_formaction_tag(path, *args)
  "<input type=\"hidden\" name=\"#{csrf_options[:formaction_field]}[#{Rack::Utils.escape_html(path)}]\" value=\"#{csrf_token(path, *args)}\" \/>"
end

def csrf_header

such support is enabled (it is not by default).
The HTTP header name to use when submitting CSRF tokens in an HTTP header, if
def csrf_header
  csrf_options[:header]
end

def csrf_hmac(random_data, method, path)

Return the HMAC-SHA-256 for the secret and the given arguments.
def csrf_hmac(random_data, method, path)
  OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, csrf_secret, "#{method.to_s.upcase}#{path}#{random_data}")
end

def csrf_invalid_message(opts)

Returns nil if the CSRF token is valid.
Returns error message string if the CSRF token is not valid.
def csrf_invalid_message(opts)
  opts = opts.empty? ? csrf_options : csrf_options.merge(opts)
  method = request.request_method
  unless opts[:check_request_methods].include?(method)
    return
  end
  path = @_request.path
  unless encoded_token = opts[:token]
    encoded_token = case opts[:check_header]
    when :only
      env[opts[:env_header]]
    when true
      return (csrf_invalid_message(opts.merge(:check_header=>false)) && csrf_invalid_message(opts.merge(:check_header=>:only)))
    else
      params = @_request.params
      ((formactions = params[opts[:formaction_field]]).is_a?(Hash) && (formactions[path])) || params[opts[:field]]
    end
  end
  unless encoded_token.is_a?(String)
    return "encoded token is not a string"
  end
  if (rack_csrf_key = opts[:upgrade_from_rack_csrf_key]) && (rack_csrf_value = session[rack_csrf_key]) && csrf_compare(rack_csrf_value, encoded_token)
    return
  end
  # 31 byte random initialization vector
  # 32 byte HMAC
  # 63 bytes total
  # 84 bytes when base64 encoded
  unless encoded_token.bytesize == 84
    return "encoded token length is not 84"
  end
  begin
    submitted_hmac = Base64_.decode64(encoded_token)
  rescue ArgumentError
    return "encoded token is not valid base64"
  end
  random_data = submitted_hmac.slice!(0...31)
  if csrf_compare(csrf_hmac(random_data, method, path), submitted_hmac)
    return
  end
  if opts[:require_request_specific_tokens]
    "decoded token is not valid for request method and path"
  else
    unless csrf_compare(csrf_hmac(random_data, '', ''), submitted_hmac)
      "decoded token is not valid for either request method and path or for blank method and path"
    end
  end
end

def csrf_metatag

It is not recommended to use this, as it doesn't support request-specific tokens.
An HTML meta tag string containing a CSRF token that is not request-specific.
def csrf_metatag
  "<meta name=\"#{csrf_field}\" content=\"#{csrf_token}\" \/>"
end

def csrf_options

Helper for getting the plugin options.
def csrf_options
  opts[:route_csrf]
end

def csrf_path(action)

paths, URLs, empty paths).
worry about the different types of form actions (relative paths, absolute
This makes it easier to generate request-specific tokens without having to
Given a form action, return the appropriate path to use for the CSRF token.
def csrf_path(action)
  case action
  when nil, '', /\A[#?]/
    # use current path
    request.path
  when /\A(?:https?:\/)?\//
    # Either full URI or absolute path, extract just the path
    URI.parse(action).path
  else
    # relative path, join to current path
    URI.join(request.url, action).path
  end
end

def csrf_secret

JSON is used for session serialization).
secret, stored base64 encoded in the session (to handle cases where
If a secret has not already been specified, generate a random 32-byte
def csrf_secret
  key = session[csrf_options[:key]] ||= SecureRandom.base64(32)
  Base64_.decode64(key)
end

def csrf_tag(*args)

arguments.
An HTML hidden input tag string containing the CSRF token. See csrf_token for
def csrf_tag(*args)
  "<input type=\"hidden\" name=\"#{csrf_field}\" value=\"#{csrf_token(*args)}\" \/>"
end

def csrf_token(path=nil, method=('POST' if path))

method as the second argument.
be assumed. To generate a token for a non-POST request method, pass the
argument. By default, it a path is provided, the POST request method will
The value of the csrf token. For a path specific token, provide a path
def csrf_token(path=nil, method=('POST' if path))
  token = SecureRandom.random_bytes(31)
  token << csrf_hmac(token, method, path)
  [token].pack("m0")
end

def use_request_specific_csrf_tokens?

Whether request-specific CSRF tokens should be used by default.
def use_request_specific_csrf_tokens?
  csrf_options[:require_request_specific_tokens]
end

def valid_csrf?(opts=OPTS)

request does not require a CSRF token.
Whether the submitted CSRF token is valid for the request. True if the
def valid_csrf?(opts=OPTS)
  csrf_invalid_message(opts).nil?
end