Module: Cybertrain::Cookies

Defined in:
cybertrain/http/cookies.rb

Overview

Cookie header parsing and Set-Cookie serialization. Values round-trip through the same www-form percent-encoding Query uses, so a value with a space, “=” or “;” survives both directions. Unlike a query string, a cookie that does not decode is NOT a client fault: SessionStore parses every cookie on every request but reads only its own, so a foreign one (“promo=50%off”, set by another app on the parent domain) must not turn every dynamic page into a 400. Its raw value is kept, as Rack does.

Plain cookies besides the session (there is no cookies jar in controllers, and no signed or encrypted cookie other than the session).

Examples:

theme = Cybertrain::Cookies.parse(request.cookie_header)["theme"]
response.add_cookie(Cybertrain::Cookies.serialize("theme", "dark", max_age: 31_536_000))
response.add_cookie(Cybertrain::Cookies.serialize("theme", "", max_age: 0))   # delete

Class Method Summary collapse

Class Method Details

.parse(header) ⇒ Hash{String => String}

The cookies in a Cookie header, values decoded. The first of two cookies with one name wins.

Parameters:

Returns:

  • (Hash{String => String})


45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'cybertrain/http/cookies.rb', line 45

def self.parse(header)
  cookies = {}
  header.to_s.split(";").each do |pair|
    trimmed = pair.strip
    next if trimmed.empty?

    eq = trimmed.index("=")
    next if eq.nil?

    name = trimmed[0, eq]
    next if cookies.key?(name)

    value = trimmed[(eq + 1)..-1].to_s
    cookies[name] = decode(value)
  end
  cookies
end

.serialize(name, value, path: "/", max_age: -1,, http_only: true, same_site: "Lax", secure: false, partitioned: false) ⇒ String

A Set-Cookie value for Response#add_cookie, the value percent-encoded. same_site "None" and partitioned both require Secure (browsers drop such a cookie without it), so either adds Secure whatever secure says, and Secure is written once.

Parameters:

  • name (String)
  • value (String)
  • path (String) (defaults to: "/")
  • max_age (Integer) (defaults to: -1,) —

    seconds; -1 leaves it out (a browser-session cookie), 0 deletes the cookie

  • http_only (Boolean) (defaults to: true)
  • same_site (String) (defaults to: "Lax") —

    "Lax", "Strict" or "None"

  • secure (Boolean) (defaults to: false)
  • partitioned (Boolean) (defaults to: false) —

    adds Partitioned (and Secure)

Returns:

  • (String)


78
79
80
81
82
83
84
85
86
87
88
# File 'cybertrain/http/cookies.rb', line 78

def self.serialize(name, value, path: "/", max_age: -1, http_only: true, same_site: "Lax", secure: false,
                   partitioned: false)
  out = +"#{name}=#{URI.encode_www_form_component(value)}"
  out << "; Path=#{path}"
  out << "; HttpOnly" if http_only
  out << "; SameSite=#{same_site}"
  out << "; Max-Age=#{max_age}" if max_age >= 0
  out << "; Secure" if secure || partitioned || same_site == "None"
  out << "; Partitioned" if partitioned
  out
end