module Roda::RodaPlugins::Sessions::RequestMethods

def _deserialize_rack_session(data)

hmac and coder.
serialized session using the default Rack::Session::Cookie
Interpret given cookie data as a Rack::Session::Cookie
def _deserialize_rack_session(data)
  opts = roda_class.opts[:sessions]
  data, digest = data.split("--", 2)
  unless digest
    return _session_serialization_error("Not decoding Rack::Session::Cookie session: invalid format")
  end
  unless Rack::Utils.secure_compare(digest, OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, opts[:upgrade_from_rack_session_cookie_secret], data))
    return _session_serialization_error("Not decoding Rack::Session::Cookie session: HMAC invalid")
  end
  begin
    session = Marshal.load(data.unpack('m').first)
  rescue
    return _session_serialization_error("Error decoding Rack::Session::Cookie session: not base64 encoded marshal dump")
  end
  # Mark rack session cookie for deletion on success
  env[SESSION_DELETE_RACK_COOKIE] = true
  # Delete the session id before serializing it.  Starting in rack 2.0.8,
  # this is an object and not just a string, and calling to_s on it raises
  # a RuntimeError.
  session.delete("session_id")
  # Convert the rack session by roundtripping it through
  # the parser and serializer, so that you would get the
  # same result as you would if the session was handled
  # by this plugin.
  env[SESSION_SERIALIZED] = data = opts[:serializer].call(session)
  env[SESSION_CREATED_AT] = Time.now.to_i
  opts[:parser].call(data)
end

def _deserialize_session(data)

Interpret given cookie data as a Rack::Session::Cookie
def _deserialize_session(data)
  opts = roda_class.opts[:sessions]
  begin
    data = Base64_.urlsafe_decode64(data)
  rescue ArgumentError
    return _session_serialization_error("Unable to decode session: invalid base64")
  end
  case version = data.getbyte(0)
  when 1
    per_cookie_secret = true
    # minimum length (1+32+16+12+32) (version+random_data+cipher_iv+minimum session+hmac)
    # 1 : version
    # 32 : random_data (if per_cookie_cipher_secret)
    # 16 : cipher_iv
    # 12 : minimum_session
    #      2 : bitmap for gzip + padding info
    #      4 : creation time
    #      4 : update time
    #      2 : data
    # 32 : HMAC-SHA-256
    min_data_length = 93
  when 0
    per_cookie_secret = false
    # minimum length (1+16+12+32) (version+cipher_iv+minimum session+hmac)
    min_data_length = 61
  when nil
    return _session_serialization_error("Unable to decode session: no data")
  else
    return _session_serialization_error("Unable to decode session: version marker unsupported")
  end
  length = data.bytesize
  if data.length < min_data_length
    return _session_serialization_error("Unable to decode session: data too short")
  end
  encrypted_data = data.slice!(0, length-32)
  unless Rack::Utils.secure_compare(data, OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, opts[:hmac_secret], encrypted_data+opts[:key]))
    if opts[:old_hmac_secret] && Rack::Utils.secure_compare(data, OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, opts[:old_hmac_secret], encrypted_data+opts[:key]))
      use_old_cipher_secret = true
    else
      return _session_serialization_error("Not decoding session: HMAC invalid")
    end
  end
  # Remove version
  encrypted_data.slice!(0)
  cipher_secret = opts[use_old_cipher_secret ? :old_cipher_secret : :cipher_secret]
  if per_cookie_secret
    cipher_secret = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, cipher_secret, encrypted_data.slice!(0, 32))
  end
  cipher_iv = encrypted_data.slice!(0, 16)
  cipher = OpenSSL::Cipher.new("aes-256-ctr")
  # Not rescuing cipher errors.  If there is an error in the decryption, that's
  # either a bug in the plugin that needs to be fixed, or an attacker is already
  # able to forge a valid HMAC, in which case the error should be raised to
  # alert the application owner about the problem.
  cipher.decrypt
  cipher.key = cipher_secret
  cipher.iv = cipher_iv
  data = cipher.update(encrypted_data) << cipher.final
  bitmap, created_at, updated_at = data.unpack('vVV')
  padding_bytes = bitmap & PADDING_MASK
  now = Time.now.to_i
  if (max = opts[:max_seconds]) && now > created_at + max
    return _session_serialization_error("Not returning session: maximum session time expired")
  end
  if (max = opts[:max_idle_seconds]) && now > updated_at + max
    return _session_serialization_error("Not returning session: maximum session idle time expired")
  end
  data = data.slice(10+padding_bytes, data.bytesize)
  if bitmap & DEFLATE_BIT > 0
    data = Zlib::Inflate.inflate(data)
  end
  env = @env
  env[SESSION_CREATED_AT] = created_at
  env[SESSION_UPDATED_AT] = updated_at
  env[SESSION_SERIALIZED] = data
  env[SESSION_VERSION_NUM] = version
  opts[:parser].call(data)
end

def _load_session

back to the rack session cookie if configured.
Load the session by looking for the appropriate cookie, or falling
def _load_session
  opts = roda_class.opts[:sessions]
  cs = cookies
  if data = cs[opts[:key]]
    _deserialize_session(data)
  elsif (key = opts[:upgrade_from_rack_session_cookie_key]) && (data = cs[key])
    _deserialize_rack_session(data)
  end || {}
end

def _serialize_session(session)

def _serialize_session(session)
  opts = roda_class.opts[:sessions]
  env = @env
  now = Time.now.to_i
  json_data = opts[:serializer].call(session).force_encoding('BINARY')
  if (serialized_session = env[SESSION_SERIALIZED]) &&
     (opts[:session_version_num] == env[SESSION_VERSION_NUM]) &&
     (updated_at = env[SESSION_UPDATED_AT]) &&
     (now - updated_at < opts[:skip_within]) &&
     (serialized_session == json_data)
    return
  end
  bitmap = 0
  json_length = json_data.bytesize
  gzip_over = opts[:gzip_over]
  if gzip_over && json_length > gzip_over
    json_data = Zlib.deflate(json_data)
    json_length = json_data.bytesize
    bitmap |= DEFLATE_BIT
  end
  # When calculating padding bytes to use, include 10 bytes for bitmap and
  # session create/update times, so total size of encrypted data is a
  # multiple of pad_size.
  if (pad_size = opts[:pad_size]) && (padding_bytes = (json_length+10) % pad_size) != 0
    padding_bytes = pad_size - padding_bytes
    bitmap |= padding_bytes
    padding_data = SecureRandom.random_bytes(padding_bytes)
  end
  session_create_time = env[SESSION_CREATED_AT]
  serialized_data = [bitmap, session_create_time||now, now].pack('vVV')
  serialized_data << padding_data if padding_data
  serialized_data << json_data
  cipher_secret = opts[:cipher_secret]
  if opts[:per_cookie_cipher_secret]
    version = "\1"
    per_cookie_secret_base = SecureRandom.random_bytes(32)
    cipher_secret = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, cipher_secret, per_cookie_secret_base)
  else
    version = "\0"
  end
  cipher = OpenSSL::Cipher.new("aes-256-ctr")
  cipher.encrypt
  cipher.key = cipher_secret
  cipher_iv = cipher.random_iv
  encrypted_data = cipher.update(serialized_data) << cipher.final
  data = String.new
  data << version
  data << per_cookie_secret_base if per_cookie_secret_base
  data << cipher_iv
  data << encrypted_data
  data << OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, opts[:hmac_secret], data+opts[:key])
  data = Base64_.urlsafe_encode64(data)
  if data.bytesize >= 4096
    raise CookieTooLarge, "attempted to create cookie larger than 4096 bytes"
  end
  data
end

def _session_serialization_error(msg)

such as improper session cookies.
This is used for errors that shouldn't be raised as exceptions,
If 'rack.errors' is set, write the error message to it.
def _session_serialization_error(msg)
  return unless error_stream = @env['rack.errors']
  error_stream.puts(msg)
  nil
end

def persist_session(headers, session)

sent in the future.
Rack::Session::Cookie, mark the related cookie for expiration so it isn't
Persist the session data as a cookie. If transparently upgrading from
def persist_session(headers, session)
  opts = roda_class.opts[:sessions]
  if session.empty?
    if env[SESSION_SERIALIZED]
      # If session was submitted and is now empty, remove the cookie
      Rack::Utils.delete_cookie_header!(headers, opts[:key], opts[:remove_cookie_options])
    # else
      # If no session was submitted, and the session is empty
      # then there is no need to do anything
    end
  elsif cookie_value = _serialize_session(session)
    cookie = Hash[opts[:cookie_options]]
    cookie[:value] = cookie_value
    cookie[:secure] = true if !cookie.has_key?(:secure) && ssl?
    Rack::Utils.set_cookie_header!(headers, opts[:key], cookie)
  end
  
  if env[SESSION_DELETE_RACK_COOKIE]
    Rack::Utils.delete_cookie_header!(headers, opts[:upgrade_from_rack_session_cookie_key], opts[:upgrade_from_rack_session_cookie_options])
  end
  nil
end

def session

but that does not happen until this method is called.
this method stores the session in 'rack.session' in the request environment,
For maximum compatibility with other software that uses rack sessions,
trying to access the session directly through the request environment.
plugin, you must call this method to get the session, instead of
Load the session information from the cookie. With the sessions
def session
  @env['rack.session'] ||= _load_session
end

def session_created_at

The time the session was originally created. nil if there is no active session.
def session_created_at
  session
  Time.at(@env[SESSION_CREATED_AT]) if @env[SESSION_SERIALIZED]
end

def session_updated_at

The time the session was last updated. nil if there is no active session.
def session_updated_at
  session
  Time.at(@env[SESSION_UPDATED_AT]) if @env[SESSION_SERIALIZED]
end