1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
|
# frozen_string_literal: true
require "cgi"
require "secure_headers/utils/cookies_config"
module SecureHeaders
class CookiesConfigError < StandardError; end
class Cookie
class << self
def validate_config!(config)
CookiesConfig.new(config).validate!
end
end
attr_reader :raw_cookie, :config
COOKIE_DEFAULTS = {
httponly: true,
secure: true,
samesite: { lax: true },
}.freeze
def initialize(cookie, config)
@raw_cookie = cookie
unless config == OPT_OUT
config ||= {}
config = COOKIE_DEFAULTS.merge(config)
end
@config = config
@attributes = {
httponly: nil,
samesite: nil,
secure: nil,
}
parse(cookie)
end
def to_s
@raw_cookie.dup.tap do |c|
c << "; secure" if secure?
c << "; HttpOnly" if httponly?
c << "; #{samesite_cookie}" if samesite?
end
end
def secure?
flag_cookie?(:secure) && !already_flagged?(:secure)
end
def httponly?
flag_cookie?(:httponly) && !already_flagged?(:httponly)
end
def samesite?
flag_samesite? && !already_flagged?(:samesite)
end
private
def parsed_cookie
@parsed_cookie ||= CGI::Cookie.parse(raw_cookie)
end
def already_flagged?(attribute)
@attributes[attribute]
end
def flag_cookie?(attribute)
return false if config == OPT_OUT
case config[attribute]
when TrueClass
true
when Hash
conditionally_flag?(config[attribute])
else
false
end
end
def conditionally_flag?(configuration)
if (Array(configuration[:only]).any? && (Array(configuration[:only]) & parsed_cookie.keys).any?)
true
elsif (Array(configuration[:except]).any? && (Array(configuration[:except]) & parsed_cookie.keys).none?)
true
else
false
end
end
def samesite_cookie
if flag_samesite_lax?
"SameSite=Lax"
elsif flag_samesite_strict?
"SameSite=Strict"
elsif flag_samesite_none?
"SameSite=None"
end
end
def flag_samesite?
return false if config == OPT_OUT || config[:samesite] == OPT_OUT
flag_samesite_lax? || flag_samesite_strict? || flag_samesite_none?
end
def flag_samesite_lax?
flag_samesite_enforcement?(:lax)
end
def flag_samesite_strict?
flag_samesite_enforcement?(:strict)
end
def flag_samesite_none?
flag_samesite_enforcement?(:none)
end
def flag_samesite_enforcement?(mode)
return unless config[:samesite]
if config[:samesite].is_a?(TrueClass) && mode == :lax
return true
end
case config[:samesite][mode]
when Hash
conditionally_flag?(config[:samesite][mode])
when TrueClass
true
else
false
end
end
def parse(cookie)
return unless cookie
cookie.split(/[;,]\s?/).each do |pairs|
name, values = pairs.split("=", 2)
name = CGI.unescape(name)
attribute = name.downcase.to_sym
if @attributes.has_key?(attribute)
@attributes[attribute] = values || true
end
end
end
end
end
|