Class: Session
- Inherits:
-
Object
- Object
- Session
- Defined in:
- lib/fresco/runtime/runtime.rb
Overview
Signed session cookie store. Wire format: ‘urlencoded_payload.hexhmac`. The payload is visible to the client (not encrypted) but the HMAC binds it to the server’s secret — modifying the payload requires forging a 32-byte SHA-256 HMAC, which is infeasible without the key.
Spinel-shape constraints:
- Custom `[]` / `[]=` on a user class collapses callers to mrb_int
params and mismatches the underlying String/String slots. So we
expose only named methods (`get`, `set`, `has?`); use those rather
than `session[k]`.
- `@data` is pinned to StrStrHash via the seed-and-clear trick used
elsewhere in this file. `clear` reseeds the same way.
Instance Attribute Summary collapse
-
#data ⇒ Object
readonly
Returns the value of attribute data.
-
#dirty ⇒ Object
readonly
Returns the value of attribute dirty.
Class Method Summary collapse
-
.last_dot(s) ⇒ Object
Last ‘.` position in s, or -1 if none.
-
.timing_safe_eq(a, b) ⇒ Object
Constant-time string equality.
Instance Method Summary collapse
- #clear ⇒ Object
-
#get(k = "") ⇒ Object
‘|| “”` keeps the missing-key semantics consistent under CRuby (where Hash#[] returns nil) and Spinel (where the StrStrHash returns “” already — the coalesce is a no-op there).
- #has?(k = "") ⇒ Boolean
-
#initialize ⇒ Session
constructor
A new instance of Session.
- #length ⇒ Object
-
#load_from(cookie_value, secret) ⇒ Object
Verify + decode an inbound cookie value.
-
#mark_dirty ⇒ Object
Force the dirty flag from outside the class (used by flash promotion, which mutates @data directly via the reader to clear consumed entries).
-
#parse_payload!(payload) ⇒ Object
Walk a ‘k=v&k=v` payload into @data.
- #set(k = "", v = "") ⇒ Object
-
#to_cookie_value(secret) ⇒ Object
Sign + serialise the current data into a cookie value.
Constructor Details
#initialize ⇒ Session
Returns a new instance of Session.
204 205 206 207 208 |
# File 'lib/fresco/runtime/runtime.rb', line 204 def initialize @data = { "__t" => "" } @data.delete("__t") @dirty = false end |
Instance Attribute Details
#data ⇒ Object (readonly)
Returns the value of attribute data.
202 203 204 |
# File 'lib/fresco/runtime/runtime.rb', line 202 def data @data end |
#dirty ⇒ Object (readonly)
Returns the value of attribute dirty.
202 203 204 |
# File 'lib/fresco/runtime/runtime.rb', line 202 def dirty @dirty end |
Class Method Details
.last_dot(s) ⇒ Object
Last ‘.` position in s, or -1 if none. Spinel doesn’t have a stdlib rindex equivalent we can rely on; the explicit walk lowers cleanly.
278 279 280 281 282 283 284 285 |
# File 'lib/fresco/runtime/runtime.rb', line 278 def self.last_dot(s) i = s.length - 1 while i >= 0 return i if s[i] == "." i -= 1 end -1 end |
.timing_safe_eq(a, b) ⇒ Object
Constant-time string equality. Avoids leaking the matching prefix length via early-exit timing. Spinel doesn’t have a stdlib crypto- safe compare, so we walk byte by byte.
290 291 292 293 294 295 296 297 298 299 |
# File 'lib/fresco/runtime/runtime.rb', line 290 def self.timing_safe_eq(a, b) return false if a.length != b.length diff = 0 i = 0 while i < a.length diff = diff | (a[i].bytes[0] ^ b[i].bytes[0]) i += 1 end diff == 0 end |
Instance Method Details
#clear ⇒ Object
219 220 221 222 223 |
# File 'lib/fresco/runtime/runtime.rb', line 219 def clear @data = { "__t" => "" } @data.delete("__t") @dirty = true end |
#get(k = "") ⇒ Object
‘|| “”` keeps the missing-key semantics consistent under CRuby (where Hash#[] returns nil) and Spinel (where the StrStrHash returns “” already — the coalesce is a no-op there). Callers can then write `req.session.get(“x”).length > 0` portably.
214 |
# File 'lib/fresco/runtime/runtime.rb', line 214 def get(k = ""); @data[k] || ""; end |
#has?(k = "") ⇒ Boolean
216 |
# File 'lib/fresco/runtime/runtime.rb', line 216 def has?(k = ""); @data.key?(k); end |
#length ⇒ Object
217 |
# File 'lib/fresco/runtime/runtime.rb', line 217 def length; @data.length; end |
#load_from(cookie_value, secret) ⇒ Object
Verify + decode an inbound cookie value. Returns true on success (data populated), false on missing / malformed / tampered. We don’t raise so a forged cookie just looks like “no session” to the app.
228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/fresco/runtime/runtime.rb', line 228 def load_from(, secret) return false if .length == 0 || secret.length == 0 dot = Session.last_dot() return false if dot < 0 payload = [0, dot] sig = [dot + 1, .length - dot - 1] expect = Sock.sphttp_hmac_sha256_hex(secret, payload) return false unless Session.timing_safe_eq(sig, expect) parse_payload!(payload) true end |
#mark_dirty ⇒ Object
Force the dirty flag from outside the class (used by flash promotion, which mutates @data directly via the reader to clear consumed entries).
304 305 306 |
# File 'lib/fresco/runtime/runtime.rb', line 304 def mark_dirty @dirty = true end |
#parse_payload!(payload) ⇒ Object
Walk a ‘k=v&k=v` payload into @data. Sibling of parse_query_string! but writes Str keys directly (no url_decode().to_sym), so it’s private to Session.
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/fresco/runtime/runtime.rb', line 256 def parse_payload!(payload) pairs = payload.split("&") i = 0 while i < pairs.length pair = pairs[i] if pair.length > 0 eq = str_idx(pair, "=") if eq < 0 @data[url_decode(pair)] = "" else k = url_decode(pair[0, eq]) v = url_decode(pair[eq + 1, pair.length - eq - 1]) @data[k] = v end end i += 1 end end |
#set(k = "", v = "") ⇒ Object
215 |
# File 'lib/fresco/runtime/runtime.rb', line 215 def set(k = "", v = ""); @data[k] = v; @dirty = true; end |
#to_cookie_value(secret) ⇒ Object
Sign + serialise the current data into a cookie value. Caller decides when (typically only when @dirty).
242 243 244 245 246 247 248 249 250 251 |
# File 'lib/fresco/runtime/runtime.rb', line 242 def (secret) payload = "" first = true @data.each do |k, v| payload += "&" unless first payload += url_encode(k) + "=" + url_encode(v) first = false end payload + "." + Sock.sphttp_hmac_sha256_hex(secret, payload) end |