sinatra/sinatra-contrib/lib/sinatra/cookies.rb

346 lines
6.9 KiB
Ruby

# frozen_string_literal: true
require 'sinatra/base'
module Sinatra
# = Sinatra::Cookies
#
# Easy way to deal with cookies
#
# == Usage
#
# Allows you to read cookies:
#
# get '/' do
# "value: #{cookies[:something]}"
# end
#
# And of course to write cookies:
#
# get '/set' do
# cookies[:something] = 'foobar'
# redirect to('/')
# end
#
# And generally behaves like a hash:
#
# get '/demo' do
# cookies.merge! 'foo' => 'bar', 'bar' => 'baz'
# cookies.keep_if { |key, value| key.start_with? 'b' }
# foo, bar = cookies.values_at 'foo', 'bar'
# "size: #{cookies.length}"
# end
#
# === Classic Application
#
# In a classic application simply require the helpers, and start using them:
#
# require "sinatra"
# require "sinatra/cookies"
#
# # The rest of your classic application code goes here...
#
# === Modular Application
#
# In a modular application you need to require the helpers, and then tell
# the application to use them:
#
# require "sinatra/base"
# require "sinatra/cookies"
#
# class MyApp < Sinatra::Base
# helpers Sinatra::Cookies
#
# # The rest of your modular application code goes here...
# end
#
module Cookies
class Jar
include Enumerable
attr_reader :options
def initialize(app)
@response_string = nil
@response_hash = {}
@response = app.response
@request = app.request
@deleted = []
@options = {
path: @request.script_name.to_s.empty? ? '/' : @request.script_name,
domain: @request.host == 'localhost' ? nil : @request.host,
secure: @request.secure?,
httponly: true
}
return unless app.settings.respond_to? :cookie_options
@options.merge! app.settings.cookie_options
end
def ==(other)
other.respond_to? :to_hash and to_hash == other.to_hash
end
def [](key)
response_cookies[key.to_s] || request_cookies[key.to_s]
end
def []=(key, value)
set(key, value: value)
end
if Hash.method_defined? :assoc
def assoc(key)
to_hash.assoc(key.to_s)
end
end
def clear
each_key { |k| delete(k) }
end
def compare_by_identity?
false
end
def default
nil
end
alias default_proc default
def delete(key)
result = self[key]
@response.delete_cookie(key.to_s, @options)
result
end
def delete_if
return enum_for(__method__) unless block_given?
each { |k, v| delete(k) if yield(k, v) }
self
end
def each(&block)
return enum_for(__method__) unless block_given?
to_hash.each(&block)
end
def each_key(&block)
return enum_for(__method__) unless block_given?
to_hash.each_key(&block)
end
alias each_pair each
def each_value(&block)
return enum_for(__method__) unless block_given?
to_hash.each_value(&block)
end
def empty?
to_hash.empty?
end
def fetch(key, &block)
response_cookies.fetch(key.to_s) do
request_cookies.fetch(key.to_s, &block)
end
end
if Hash.method_defined? :flatten
def flatten
to_hash.flatten
end
end
def has_key?(key)
response_cookies.key? key.to_s or request_cookies.key? key.to_s
end
def has_value?(value)
response_cookies.value? value or request_cookies.value? value
end
def hash
to_hash.hash
end
alias include? has_key?
alias member? has_key?
def inspect
"<##{self.class}: #{to_hash.inspect[1..-2]}>"
end
if Hash.method_defined? :invert
def invert
to_hash.invert
end
end
def keep_if
return enum_for(__method__) unless block_given?
delete_if { |*a| !yield(*a) }
end
def key(value)
to_hash.key(value)
end
alias key? has_key?
def keys
to_hash.keys
end
def length
to_hash.length
end
def merge(other, &block)
to_hash.merge(other, &block)
end
def merge!(other)
other.each_pair do |key, value|
self[key] = if block_given? && include?(key)
yield(key.to_s, self[key], value)
else
value
end
end
end
def rassoc(value)
to_hash.rassoc(value)
end
def rehash
response_cookies.rehash
request_cookies.rehash
self
end
def reject(&block)
return enum_for(__method__) unless block_given?
to_hash.reject(&block)
end
alias reject! delete_if
def replace(other)
select! { |k, _v| other.include?(k) or other.include?(k.to_s) }
merge! other
end
def select(&block)
return enum_for(__method__) unless block_given?
to_hash.select(&block)
end
alias select! keep_if if Hash.method_defined? :select!
def set(key, options = {})
@response.set_cookie key.to_s, @options.merge(options)
end
def shift
key, value = to_hash.shift
delete(key)
[key, value]
end
alias size length
if Hash.method_defined? :sort
def sort(&block)
to_hash.sort(&block)
end
end
alias store []=
def to_hash
request_cookies.merge(response_cookies)
end
def to_a
to_hash.to_a
end
def to_s
to_hash.to_s
end
alias update merge!
alias value? has_value?
def values
to_hash.values
end
def values_at(*list)
list.map { |k| self[k] }
end
private
def warn(message)
super "#{caller.first[/^[^:]:\d+:/]} warning: #{message}"
end
def deleted
parse_response
@deleted
end
def response_cookies
parse_response
@response_hash
end
def parse_response
string = @response['Set-Cookie']
return if @response_string == string
hash = {}
string.each_line do |line|
key, value = line.split(';', 2).first.to_s.split('=', 2)
next if key.nil?
key = Rack::Utils.unescape(key)
if line =~ /expires=Thu, 01[-\s]Jan[-\s]1970/
@deleted << key
else
@deleted.delete key
hash[key] = value
end
end
@response_hash.replace hash
@response_string = string
end
def request_cookies
@request.cookies.reject { |key, _value| deleted.include? key }
end
end
def cookies
@cookies ||= Jar.new(self)
end
end
helpers Cookies
end