class SBSM::Session

Constants

CAP_MAX_THRESHOLD
DEFAULT_FLAVOR
DEFAULT_LANGUAGE
DEFAULT_STATE
DEFAULT_ZONE
EXPIRES
LF_FACTORY
LOOKANDFEEL
MAX_STATES
SERVER_NAME
UNKNOWN_USER

Attributes

active_thread[R]
app[RW]
attended_states[R]
cgi[R]
key[R]
mtime[R]
post_content[R]
rack_request[RW]
request_method[R]
request_origin[R]
request_params[R]
request_path[R]
request_post[R]
server_name[R]
server_port[R]
trans_handler[RW]
unsafe_input[R]
user[R]
user_agent[R]
valid_input[R]
validator[RW]

Public Class Methods

new(app:, trans_handler: nil, validator: nil, unknown_user: SBSM::UnknownUser.new, cookie_name: nil, multi_threaded: false) click to toggle source

Session: It will be initialized indirectly whenever SessionStore cannot find a session in it cache. The parameters are given via SBSM::App.new which calls SessionStore.new

optional arguments

  • validator - A Ruby class overriding the SBSM::Validator class

  • trans_handler - A Ruby class overriding the SBSM::TransHandler class

  • cookie_name - The cookie to save persistent user data

Examples

Look at steinwies.ch

# File lib/sbsm/session.rb, line 109
def initialize(app:,
               trans_handler: nil,
               validator: nil,
                unknown_user: SBSM::UnknownUser.new,
              cookie_name: nil,
               multi_threaded: false)
  SBSM.info "initialize th #{trans_handler} validator #{validator} app #{app.class} multi_threaded #{multi_threaded}"
  @app = app
  @unknown_user = unknown_user.is_a?(Class) ? unknown_user.new : unknown_user
  @validator = validator if validator.is_a?(SBSM::Validator)
  @validator ||= (validator && validator.new) || Validator.new
  fail "invalid validator #{@validator}" unless @validator.is_a?(SBSM::Validator)
  @trans_handler = trans_handler || TransHandler.instance
  fail "invalid trans_handler #{@trans_handler}" unless @trans_handler.is_a?(SBSM::TransHandler)
  @persistent_cookie_name =  cookie_name
  @persistent_cookie_name ||= self.class::PERSISTENT_COOKIE_NAME
  @attended_states = {}
  @persistent_user_input = {}
  touch()
  reset_input()
  reset_cookie()
  @user  = SBSM::UnknownUser.new
  @variables = {}
  @cgi = CGI.initialize_without_offline_prompt('html4')
  @multi_threaded = multi_threaded
  @mutex = multi_threaded ? Mutex.new: @@mutex
  @active_thread = nil
  SBSM.debug "session initialized #{self} with @cgi #{@cgi} multi_threaded #{multi_threaded} app #{app.object_id} and user #{@user.class} @unknown_user #{@unknown_user.class}"
end
reset_stats() click to toggle source
# File lib/sbsm/session.rb, line 58
def Session.reset_stats
  @@stats = {}
end
show_stats(ptrn=@@stats_ptrn) click to toggle source
# File lib/sbsm/session.rb, line 63
def Session.show_stats ptrn=@@stats_ptrn
  if ptrn.is_a?(String)
    ptrn = /#{ptrn}/i
  end
  puts sprintf("%8s %8s %8s %6s %10s Request-Path",
               "Min", "Max", "Avg", "Num", "Total")
  grand_total = requests = all_max = all_min = 0
  @@stats.collect do |path, times|
    total = times.inject do |a, b| a + b end
    grand_total += total
    size = times.size
    requests += size
    max = times.max
    all_max = max > all_max ? max : all_max
    min = times.min
    all_min = min < all_min ? min : all_min
    [min, max, total / size, size, total, path]
  end.sort.each do |data|
    line = sprintf("%8.2f %8.2f %8.2f %6i %10.2f %s", *data)
    if ptrn.match(line)
      puts line
    end
  end
  puts sprintf("%8s %8s %8s %6s %10s Request-Path",
               "Min", "Max", "Avg", "Num", "Total")
  puts sprintf("%8.2f %8.2f %8.2f %6i %10.2f",
               all_min, all_max,
               requests > 0 ? grand_total / requests : 0,
               requests, grand_total)
  ''
end

Public Instance Methods

<=>(other) click to toggle source
# File lib/sbsm/session.rb, line 613
def <=>(other)
        self.weighted_mtime <=> other.weighted_mtime
end
==(other) click to toggle source
Calls superclass method
# File lib/sbsm/session.rb, line 610
def ==(other)
        super
end
[](key) click to toggle source
# File lib/sbsm/session.rb, line 616
def [](key)
        @variables[key]
end
[]=(key, val) click to toggle source
# File lib/sbsm/session.rb, line 619
def []=(key, val)
        @variables[key] = val
end
__checkout() click to toggle source
# File lib/sbsm/session.rb, line 167
def __checkout
        @attended_states.each_value { |state| state.__checkout }
        @attended_states.clear
        flavor = @persistent_user_input[:flavor]
        lang = @persistent_user_input[:language]
        @persistent_user_input.clear
        @persistent_user_input.store(:flavor, flavor)
        @persistent_user_input.store(:language, lang)
        @valid_input.clear
        @unsafe_input.clear
        true
end
age(now=Time.now) click to toggle source
# File lib/sbsm/session.rb, line 150
def age(now=Time.now)
  now - @mtime
end
async(&block) click to toggle source
# File lib/sbsm/session.rb, line 622
def async(&block)
  @@async.add(Thread.new(&block))
end
cap_max_states() click to toggle source
# File lib/sbsm/session.rb, line 153
        def cap_max_states
                if(@attended_states.size > self::class::CAP_MAX_THRESHOLD)
                        SBSM.info "too many states in session! Keeping only #{self::class::MAX_STATES}"
                        sorted = @attended_states.values.sort
                        sorted[0...(-self::class::MAX_STATES)].each { |state|
                                state.__checkout
                                @attended_states.delete(state.object_id)
                        }
@attended_states.size
                end
        end
client_activex?() click to toggle source
# File lib/sbsm/session.rb, line 181
          def client_activex?
(ua = user_agent) && @@msie_ptrn.match(ua) && @@win_ptrn.match(ua)
          end
client_nt5?() click to toggle source
# File lib/sbsm/session.rb, line 185
          def client_nt5?
(ua = user_agent) \
  && (match = @@nt5_ptrn.match(user_agent)) \
  && (match[1].to_f >= 5)
          end
close() click to toggle source
# File lib/sbsm/session.rb, line 594
def close
                    #@unix_socket.stop_service
  # nothing
end
default_language() click to toggle source
# File lib/sbsm/session.rb, line 200
def default_language
        self::class::DEFAULT_LANGUAGE
end
delete() click to toggle source
# File lib/sbsm/session.rb, line 598
def delete
  @app.delete_session(@key)
end
direct_event() click to toggle source
# File lib/sbsm/session.rb, line 203
          def direct_event
# used when
                  @state.direct_event
          end
error(key) click to toggle source
# File lib/sbsm/session.rb, line 284
def error(key)
        @state.error(key) if @state.respond_to?(:error)
end
error?() click to toggle source
# File lib/sbsm/session.rb, line 290
def error?
        @state.error? if @state.respond_to?(:error?)
end
errors() click to toggle source
# File lib/sbsm/session.rb, line 287
def errors
        @state.errors.values if @state.respond_to?(:errors)
end
event() click to toggle source
# File lib/sbsm/session.rb, line 293
def event
        @valid_input[:event]
end
event_bound_user_input(key) click to toggle source
# File lib/sbsm/session.rb, line 296
def event_bound_user_input(key)
  @event_user_input ||= {}
  evt = state.direct_event
  @event_user_input[evt] ||= {}
  if(val = user_input(key))
    @event_user_input[evt][key] = val
  else
    @event_user_input[evt][key]
  end
end
expired?(now=Time.now) click to toggle source
# File lib/sbsm/session.rb, line 306
          def expired?(now=Time.now)
age(now) > EXPIRES
          end
flavor() click to toggle source
# File lib/sbsm/session.rb, line 433
def flavor
        @flavor ||= begin
                user_input = persistent_user_input(:flavor)
                user_input ||= @valid_input[:default_flavor]
                lf_factory = self::class::LF_FACTORY
                if(lf_factory && lf_factory.include?(user_input))
                        user_input
                else
                        self::class::DEFAULT_FLAVOR
                end
        end
end
force_login(user) click to toggle source
# File lib/sbsm/session.rb, line 309
def force_login(user)
        @user = user
end
get_passthru() click to toggle source
# File lib/sbsm/session.rb, line 477
def get_passthru
  @passthru ? [@passthru, @disposition] : []
end
http_headers() click to toggle source
# File lib/sbsm/session.rb, line 458
          def http_headers
  @state.http_headers
          rescue NameError, StandardError => err
SBSM.info "NameError, StandardError: #@request_path"
                  {'Content-Type' => 'text/plain'}
          end
http_protocol() click to toggle source
# File lib/sbsm/session.rb, line 464
def http_protocol
@http_protocol ||= if(@server_port && @server_port.to_i == 443) || ENV['SERVER_PORT']
                                                                                                'https'
                                                                                        else
                                                                                                'http'
                                                                                        end
end
import_cookies(request) click to toggle source
# File lib/sbsm/session.rb, line 312
          def import_cookies(request)
                  reset_cookie()
if(cuki = request.cookies[@persistent_cookie_name])
  cuki.split(';').each { |cuki_str|
    CGI.parse(CGI.unescape(cuki_str)).each { |key, val|
      key = key.intern
      valid = @validator.validate(key, val.compact.last)
      @cookie_input.store(key, valid)
    }
  }
end
request.cookies.each do |key, value|
  next if key.to_s.eql?(@persistent_cookie_name.to_s)
  key = key.intern
  valid = @validator.validate(key, value)
  @cookie_input.store(key, valid)
end if false
SBSM.debug "@cookie_input now #{@cookie_input}"
          end
import_user_input(rack_req) click to toggle source
# File lib/sbsm/session.rb, line 334
def import_user_input(rack_req)
                    return if(@user_input_imported)
  hash = rack_req.env.merge rack_req.params
  hash.merge! rack_req.POST if rack_req.POST
  hash.delete('rack.request.form_hash')
  # SBSM.debug "hash has #{hash.size } items #{hash.keys}"
  hash.each do |key, value|
    next if /^rack\./.match(key)
                            index = nil
                            @unsafe_input.push([key.to_s.dup, value.to_s.dup])
                            unless(key.nil? || key.empty?)
      if value.is_a?(Hash)
        key_sym = key.to_sym
        if @validator.validate(key_sym, value)
          @valid_input[key_sym] ||= {}
          value.each{ |k, v|
                      @valid_input[key_sym][k] = v
                    }
        end
        next
      end
      # Next for
                                    if match = @@hash_ptrn.match(key)
                                            key = match[1]
                                            index = match[2]
        # puts "key #{key} index #{index}  value #{value}"
                                    end
                                    key = key.intern
                                    if(key == :confirm_pass)
                                            pass = rack_req.params["pass"]
                                            SBSM.debug "pass:#{pass} - confirm:#{value}"
                                            @valid_input[key] = @valid_input[:set_pass] \
                                                    = @validator.set_pass(pass, value)
                                    else
                                            valid = @validator.validate(key, value)
        # SBSM.debug "Checking #{key} -> #{value}  valid #{valid.inspect} index #{index.inspect}"
                                            if(index)
          target = (@valid_input[key] ||= {})
          indices = []
          index.scan(@@index_ptrn) { |idx|
            indices.push idx
          }
          last = indices.pop
          indices.each { |idx|
            target = (target[idx] ||= {})
          }
          target.store(last, valid)
                                            else
                                                    @valid_input[key] = valid
                                            end
                                    end
                            end
  end
                    @user_input_imported = true
end
info?() click to toggle source
# File lib/sbsm/session.rb, line 392
def info?
        @state.info? if @state.respond_to?(:info?)
end
infos() click to toggle source
# File lib/sbsm/session.rb, line 389
def infos
        @state.infos if @state.respond_to?(:infos)
end
input_keys() click to toggle source
# File lib/sbsm/session.rb, line 471
def input_keys
        @valid_input.keys
end
is_crawler?() click to toggle source
# File lib/sbsm/session.rb, line 395
def is_crawler?
  crawler_pattern = /archiver|slurp|bot|crawler|jeeves|spider|\.{6}/i
  !!(@rack_request && crawler_pattern.match(@rack_request.user_agent))
end
key=(key) click to toggle source
# File lib/sbsm/session.rb, line 164
def key=(key)
  @key = key
end
language() click to toggle source
# File lib/sbsm/session.rb, line 399
def language
        cookie_set_or_get(:language) || default_language
end
logged_in?() click to toggle source
# File lib/sbsm/session.rb, line 402
def logged_in?
        !@user.is_a?(SBSM::UnknownUser)
end
login() click to toggle source
# File lib/sbsm/session.rb, line 405
          def login
                  if(user = (@app && @app.respond_to?(:login) && @app.login(self)))
    SBSM.debug "user is #{user.class}  #{request_path.inspect}"
                          @user = user
else
  SBSM.debug "login no user #{request_path.inspect}"
                  end
          end
logout() click to toggle source
# File lib/sbsm/session.rb, line 413
          def logout
                  __checkout
@user = @unknown_user
@active_state = @state = self::class::DEFAULT_STATE.new(self, @user)
SBSM.debug "logout #{request_path.inspect} setting @state #{@state.object_id} #{@state.class} remember #{persistent_user_input(:remember).inspect} #{@user.class}"
@state.init
                  @attended_states.store(@state.object_id, @state)
          end
lookandfeel() click to toggle source
# File lib/sbsm/session.rb, line 421
def lookandfeel
        if(@lookandfeel.nil? \
                || (@lookandfeel.flavor != flavor) \
                || (@lookandfeel.language != persistent_user_input(:language)))
                @lookandfeel = if self::class::LF_FACTORY
                        self::class::LF_FACTORY.create(self)
                else
                        self::class::LOOKANDFEEL.new(self)
                end
        end
        @lookandfeel
end
method_missing(symbol, *args, &block) click to toggle source
# File lib/sbsm/session.rb, line 138
def method_missing(symbol, *args, &block) # Replaces old dispatch to DRb
  @app.send(symbol, *args, &block)
rescue => error
  puts error
  puts error.backtrace.join("\n")
  raise error
end
navigation() click to toggle source
passthru(path, disposition='attachment') click to toggle source
# File lib/sbsm/session.rb, line 480
          def passthru(path, disposition='attachment')
# the variable @passthru is set by a trusted source
@passthru    = path
@disposition = disposition
''
          end
persistent_user_input(key) click to toggle source
# File lib/sbsm/session.rb, line 486
def persistent_user_input(key)
        if(value = user_input(key))
                @persistent_user_input.store(key, value)
        else
                @persistent_user_input[key]
        end
end
process_rack(rack_request:) click to toggle source
# File lib/sbsm/session.rb, line 207
def process_rack(rack_request:)
  start = Time.now
  html = @mutex.synchronize do
    begin
      @passthru = false
      @disposition = false
      @rack_request = rack_request
      @request_path ||= rack_request.path
      @post_content = nil
      if rack_request.request_method.eql?('POST')
        rack_request.params.each do |k, v|
          # needed to test POST requests generated by curl (first parameter) or ARC (second parameter)
          if /xml/i.match(k)
            @post_content = "#{k} #{v}"
            break
          end
        end
        begin
          # needed for request generated by https://github.com/wiztools/rest-client
          rack_request.body.rewind # just to be sure
          @post_content = rack_request.body.read
        end unless @post_content
        if @post_content
          SBSM.debug "@post_content is #{@post_content}"
        else
          SBSM.debug "rack_request is #{rack_request}"
        end
      end

      rack_request.params.each { |key, val| @cgi.params.store(key, val) }
      @trans_handler.translate_uri(rack_request)
      @request_method =rack_request.request_method
      @request_path = rack_request.path
      if rack_request.env
        @request_origin  = 'http'
        if (proto = rack_request.env['SERVER_PROTOCOL'])
          @request_origin = proto.downcase.match(/^\w+/)[0]
        end
        @request_origin += '://'
        @request_origin += rack_request.env['REMOTE_ADDR'] if rack_request.env['REMOTE_ADDR']
        @remote_ip = rack_request.env['HTTP_X_FORWARDED_FOR']
      end
      @server_name = rack_request.env['SERVER_NAME']
      @server_port = rack_request.env['SERVER_PORT']
      @request_params = rack_request.params
      logout unless @active_state
      validator.reset_errors() if validator && validator.respond_to?(:reset_errors)
      import_cookies(rack_request)
      import_user_input(rack_request)
      @state = active_state.trigger(event())
      SBSM.debug "active_state.trigger state #{@state.object_id} #{@state.class} remember #{persistent_user_input(:remember).inspect}"
      #FIXME: is there a better way to distinguish returning states?
      #       ... we could simply refuse to init if event == :sort, but that
      #       would not solve the problem cleanly, I think.
      unless(@state.request_path)
        @state.request_path = @request_path
        @state.init
      end
      unless @state.volatile?
        SBSM.debug "Changing from #{@active_state.object_id} to state #{@state.class} #{@state.object_id} remember #{persistent_user_input(:remember).inspect}  #{@user.class}"
        @active_state = @state
        @attended_states.store(@state.object_id, @state)
      else
        SBSM.debug "Stay in volatile state #{@state.object_id} #{@state.class}"
      end
      @zone = @active_state.zone
      @active_state.touch
      cap_max_states
      self.process_late() if self.respond_to?(:process_late) # needed for ODDB.org limit_request
    ensure
      @user_input_imported = false
    end
    to_html
  end
  (@@stats[@request_path] ||= []).push(Time.now - start)
  html
end
remote_addr() click to toggle source
# File lib/sbsm/session.rb, line 511
def remote_addr
        @remote_addr ||= if @request.respond_to?(:remote_addr)
                @request.remote_addr
        end
end
remote_ip() click to toggle source
# File lib/sbsm/session.rb, line 516
          def remote_ip
                  @remote_ip ||= if(@request.respond_to?(:remote_host))
                          @request.remote_host
                  end
if @remote_ip.to_s.index(',')
  saved = @remote_ip.clone
  @remote_ip = @remote_ip.first if @remote_ip.is_a?(Array)
  @remote_ip = @remote_ip.split(',').first.gsub(/[\[\]]/, '') if @remote_ip.is_a?(String)
  SBSM.info("remote_ip is weird #{saved.inspect} => #{@remote_ip.inspect}")
end
@remote_ip
          end
reset() click to toggle source
# File lib/sbsm/session.rb, line 493
          def reset
if @redirected
  SBSM.debug "reached Session::reset"
  @redirected = false
else
  reset_input()
end
          end
reset_input() click to toggle source
# File lib/sbsm/session.rb, line 504
def reset_input
  @valid_input = {}
                    @processing_errors = {}
                    @http_protocol = nil
                    @flavor = nil
                    @unsafe_input = []
end
restore() click to toggle source

CGI::SessionHandler compatibility ruby-doc.org/stdlib-2.3.1/libdoc/cgi/rdoc/CGI.html should restore the values from a file, we return simply nothing

# File lib/sbsm/session.rb, line 588
def restore
  {}
end
state(event=nil) click to toggle source
# File lib/sbsm/session.rb, line 539
def state(event=nil)
        @active_state
end
to_html() click to toggle source
# File lib/sbsm/session.rb, line 546
def to_html
        @state.to_html(cgi)
end
touch() click to toggle source
# File lib/sbsm/session.rb, line 542
def touch
  @mtime = Time.now
  self
end
unknown_user() click to toggle source
# File lib/sbsm/session.rb, line 145
def unknown_user
  @unknown_user || SBSM::UnknownUser.new
  puts "unknown_user set to #{@unknown_user} class #{ @unknown_user.is_a?(Class)}"
  @unknown_user = @unknown_user.new if @unknown_user.is_a?(Class)
end
update() click to toggle source
# File lib/sbsm/session.rb, line 591
def update
  # nothing
end
user_input(*keys) click to toggle source
# File lib/sbsm/session.rb, line 553
def user_input(*keys)
                    if(keys.size == 1)
                            index = nil
                            key = keys.first.to_s
                            if match = @@input_ptrn.match(key)
                                    key = match[1]
                                    index = match[2]
                            end
                            key_sym = key.to_sym
                            valid = @valid_input[key_sym]
                            if(index && valid.respond_to?(:[]))
                                    valid[index]
                            else
                                    valid
                            end
                    else
                            keys.inject({}) { |inj, key|
                                    inj.store(key, user_input(key))
                                    inj
                            }
                    end
end
valid_values(key) click to toggle source
# File lib/sbsm/session.rb, line 575
def valid_values(key)
        vals = @validator.valid_values(key) unless @validator.nil?
        vals || []
end
warning?() click to toggle source
# File lib/sbsm/session.rb, line 582
def warning?
        @state.warning? if @state.respond_to?(:warning?)
end
warnings() click to toggle source
# File lib/sbsm/session.rb, line 579
def warnings
        @state.warnings if @state.respond_to?(:warnings)
end
zone() click to toggle source
# File lib/sbsm/session.rb, line 601
def zone
        @valid_input[:zone] || (@state && @state.zone) || self::class::DEFAULT_ZONE
end
zone_navigation() click to toggle source
# File lib/sbsm/session.rb, line 607
def zone_navigation
        @state.zone_navigation if @state
end
zones() click to toggle source
# File lib/sbsm/session.rb, line 604
def zones
        @active_state.zones
end

Protected Instance Methods

weighted_mtime() click to toggle source
# File lib/sbsm/session.rb, line 633
def weighted_mtime
        @mtime + @user.session_weight
end

Private Instance Methods

active_state() click to toggle source
# File lib/sbsm/session.rb, line 626
def active_state
        if(state_id = @valid_input[:state_id])
                @attended_states[state_id]
        end || @active_state
end