module Sinatra::Helpers

def attachment(filename = nil, disposition = :attachment)

instructing the user agents to prompt to save.
Set the Content-Disposition to "attachment" with the specified filename,
def attachment(filename = nil, disposition = :attachment)
  response['Content-Disposition'] = disposition.to_s.dup
  return unless filename
  params = format('; filename="%s"', File.basename(filename).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE))
  response['Content-Disposition'] << params
  ext = File.extname(filename)
  content_type(ext) unless response['content-type'] || ext.empty?
end

def back

Sugar for redirect (example: redirect back)
def back
  request.referer
end

def bad_request?

whether or not the status is set to 400
def bad_request?
  status == 400
end

def body(value = nil, &block)

evaluation is deferred until the body is read with #each.
Set or retrieve the response body. When a block is given,
def body(value = nil, &block)
  if block_given?
    def block.each; yield(call) end
    response.body = block
  elsif value
    unless request.head? || value.is_a?(Rack::Files::BaseIterator) || value.is_a?(Stream)
      headers.delete 'content-length'
    end
    response.body = value
  else
    response.body
  end
end

def cache_control(*values)

http://tools.ietf.org/html/rfc2616#section-14.9.1
See RFC 2616 / 14.9 for more on standard cache control directives:

=> Cache-Control: public, must-revalidate, max-age=60
cache_control :public, :must_revalidate, :max_age => 60

a Hash of value directives (:max_age, :s_maxage).
:no_store, :must_revalidate, :proxy_revalidate) may be passed along with
Any number of non-value directives (:public, :private, :no_cache,
Specify response freshness policy for HTTP caches (Cache-Control header).
def cache_control(*values)
  if values.last.is_a?(Hash)
    hash = values.pop
    hash.reject! { |_k, v| v == false }
    hash.reject! { |k, v| values << k if v == true }
  else
    hash = {}
  end
  values.map! { |value| value.to_s.tr('_', '-') }
  hash.each do |key, value|
    key = key.to_s.tr('_', '-')
    value = value.to_i if %w[max-age s-maxage].include? key
    values << "#{key}=#{value}"
  end
  response['Cache-Control'] = values.join(', ') if values.any?
end

def client_error?

whether or not the status is set to 4xx
def client_error?
  status.between? 400, 499
end

def content_type(type = nil, params = {})

extension.
Set the content-type of the response body given a media type or file
def content_type(type = nil, params = {})
  return response['content-type'] unless type
  default = params.delete :default
  mime_type = mime_type(type) || default
  raise format('Unknown media type: %p', type) if mime_type.nil?
  mime_type = mime_type.dup
  unless params.include?(:charset) || settings.add_charset.all? { |p| !(p === mime_type) }
    params[:charset] = params.delete('charset') || settings.default_encoding
  end
  params.delete :charset if mime_type.include? 'charset'
  unless params.empty?
    mime_type << (mime_type.include?(';') ? ', ' : ';')
    mime_type << params.map do |key, val|
      val = val.inspect if val =~ /[";,]/
      "#{key}=#{val}"
    end.join(', ')
  end
  response['content-type'] = mime_type
end

def error(code, body = nil)

Halt processing and return the error status provided.
def error(code, body = nil)
  if code.respond_to? :to_str
    body = code.to_str
    code = 500
  end
  response.body = body unless body.nil?
  halt code
end

def etag(value, options = {})

GET or HEAD, a '304 Not Modified' response is sent.
matching etag, execution is immediately halted. If the request method is
When the current request includes an 'If-None-Match' header with a

cache validator.
indicates whether the etag should be used as a :strong (default) or :weak
identifies the current version of the resource. The +kind+ argument
GET matches. The +value+ argument is an identifier that uniquely
Set the response entity tag (HTTP 'ETag' header) and halt if conditional
def etag(value, options = {})
  # Before touching this code, please double check RFC 2616 14.24 and 14.26.
  options      = { kind: options } unless Hash === options
  kind         = options[:kind] || :strong
  new_resource = options.fetch(:new_resource) { request.post? }
  unless ETAG_KINDS.include?(kind)
    raise ArgumentError, ':strong or :weak expected'
  end
  value = format('"%s"', value)
  value = "W/#{value}" if kind == :weak
  response['ETag'] = value
  return unless success? || status == 304
  if etag_matches?(env['HTTP_IF_NONE_MATCH'], new_resource)
    halt(request.safe? ? 304 : 412)
  end
  if env['HTTP_IF_MATCH']
    return if etag_matches?(env['HTTP_IF_MATCH'], new_resource)
    halt 412
  end
  nil
end

def etag_matches?(list, new_resource = request.post?)

Helper method checking if a ETag value list includes the current ETag.
def etag_matches?(list, new_resource = request.post?)
  return !new_resource if list == '*'
  list.to_s.split(/\s*,\s*/).include? response['ETag']
end

def expires(amount, *values)


=> Expires: Mon, 08 Jun 2009 08:50:17 GMT
=> Cache-Control: public, must-revalidate, max-age=500
expires 500, :public, :must_revalidate

"values" arguments are passed to the #cache_control helper:
indicating when the response should be considered "stale". The remaining
can be an integer number of seconds in the future or a Time object
Set the Expires header and Cache-Control/max-age directive. Amount
def expires(amount, *values)
  values << {} unless values.last.is_a?(Hash)
  if amount.is_a? Integer
    time    = Time.now + amount.to_i
    max_age = amount
  else
    time    = time_for amount
    max_age = time - Time.now
  end
  values.last.merge!(max_age: max_age) { |_key, v1, v2| v1 || v2 }
  cache_control(*values)
  response['Expires'] = time.httpdate
end

def headers(hash = nil)

Set multiple response headers with Hash.
def headers(hash = nil)
  response.headers.merge! hash if hash
  response.headers
end

def informational?

whether or not the status is set to 1xx
def informational?
  status.between? 100, 199
end

def last_modified(time)

with a '304 Not Modified' response.
equal or later than the time specified, execution is immediately halted
When the current request includes an 'If-Modified-Since' header that is

DateTime, or other object that responds to +to_time+.
and halt if conditional GET matches. The +time+ argument is a Time,
Set the last modified time of the resource (HTTP 'Last-Modified' header)
def last_modified(time)
  return unless time
  time = time_for time
  response['Last-Modified'] = time.httpdate
  return if env['HTTP_IF_NONE_MATCH']
  if (status == 200) && env['HTTP_IF_MODIFIED_SINCE']
    # compare based on seconds since epoch
    since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
    halt 304 if since >= time.to_i
  end
  if (success? || (status == 412)) && env['HTTP_IF_UNMODIFIED_SINCE']
    # compare based on seconds since epoch
    since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
    halt 412 if since < time.to_i
  end
rescue ArgumentError
end

def logger

Access shared logger object.
def logger
  request.logger
end

def mime_type(type)

Look up a media type by file extension in Rack's mime registry.
def mime_type(type)
  Base.mime_type(type)
end

def not_found(body = nil)

Halt processing and return a 404 Not Found.
def not_found(body = nil)
  error 404, body
end

def not_found?

whether or not the status is set to 404
def not_found?
  status == 404
end

def redirect(uri, *args)

Halt processing and redirect to the URI provided.
def redirect(uri, *args)
  # SERVER_PROTOCOL is required in Rack 3, fall back to HTTP_VERSION
  # for servers not updated for Rack 3 (like Puma 5)
  http_version = env['SERVER_PROTOCOL'] || env['HTTP_VERSION']
  if (http_version == 'HTTP/1.1') && (env['REQUEST_METHOD'] != 'GET')
    status 303
  else
    status 302
  end
  # According to RFC 2616 section 14.30, "the field value consists of a
  # single absolute URI"
  response['Location'] = uri(uri.to_s, settings.absolute_redirects?, settings.prefixed_redirects?)
  halt(*args)
end

def redirect?

whether or not the status is set to 3xx
def redirect?
  status.between? 300, 399
end

def send_file(path, opts = {})

Use the contents of the file at +path+ as the response body.
def send_file(path, opts = {})
  if opts[:type] || !response['content-type']
    content_type opts[:type] || File.extname(path), default: 'application/octet-stream'
  end
  disposition = opts[:disposition]
  filename    = opts[:filename]
  disposition = :attachment if disposition.nil? && filename
  filename    = path        if filename.nil?
  attachment(filename, disposition) if disposition
  last_modified opts[:last_modified] if opts[:last_modified]
  file   = Rack::Files.new(File.dirname(settings.app_file))
  result = file.serving(request, path)
  result[1].each { |k, v| headers[k] ||= v }
  headers['content-length'] = result[1]['content-length']
  opts[:status] &&= Integer(opts[:status])
  halt (opts[:status] || result[0]), result[2]
rescue Errno::ENOENT
  not_found
end

def server_error?

whether or not the status is set to 5xx
def server_error?
  status.between? 500, 599
end

def session

Access the underlying Rack session.
def session
  request.session
end

def status(value = nil)

Set or retrieve the response status code.
def status(value = nil)
  response.status = Rack::Utils.status_code(value) if value
  response.status
end

def stream(keep_open = false)

after the block has been executed.
The close parameter specifies whether Stream#close should be called

the response body have not yet been generated.
Allows to start sending data to the client even though later parts of
def stream(keep_open = false)
  scheduler = env['async.callback'] ? EventMachine : Stream
  current   = @params.dup
  stream = if scheduler == Stream  && keep_open
    Stream.new(scheduler, false) do |out|
      until out.closed?
        with_params(current) { yield(out) }
      end
    end
  else
    Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
  end
  body stream
end

def success?

whether or not the status is set to 2xx
def success?
  status.between? 200, 299
end

def time_for(value)

Used by #expires and #last_modified.
Generates a Time object from the given value.
def time_for(value)
  if value.is_a? Numeric
    Time.at value
  elsif value.respond_to? :to_s
    Time.parse value.to_s
  else
    value.to_time
  end
rescue ArgumentError => e
  raise e
rescue Exception
  raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
end

def uri(addr = nil, absolute = true, add_script_name = true)

Takes Rack routers and reverse proxies into account.
Generates the absolute URI for a given path in the app.
def uri(addr = nil, absolute = true, add_script_name = true)
  return addr if addr.to_s =~ /\A[a-z][a-z0-9+.\-]*:/i
  uri = [host = String.new]
  if absolute
    host << "http#{'s' if request.secure?}://"
    host << if request.forwarded? || (request.port != (request.secure? ? 443 : 80))
              request.host_with_port
            else
              request.host
            end
  end
  uri << request.script_name.to_s if add_script_name
  uri << (addr || request.path_info).to_s
  File.join uri
end

def with_params(temp_params)

def with_params(temp_params)
  original = @params
  @params = temp_params
  yield
ensure
  @params = original if original
end