class Rack::Session::XFile
Constants
- DEFAULT_OPTIONS
- File
- SIDCharacters
- VERSION
Public Class Methods
# File lib/rack/session/xfile.rb, line 20 def initialize(app, options = {}) super @mutex = Mutex.new @session_dir = default_options[:session_dir] @ua_filter = default_options[:user_agent_filter] @sid_nbytes = default_options[:sidbits] / 8 setup_directories check_permissions end
Public Instance Methods
# File lib/rack/session/xfile.rb, line 47 def delete_session(req, sid, options) critical_section(req) do File.unlink(session_file(sid)) rescue nil generate_sid unless options[:drop] end end
# File lib/rack/session/xfile.rb, line 36 def find_session(req, sid) return [nil, {}] if filter_request(req, sid) critical_section(req) do unless sid and session = read_session_file(sid) sid, session = generate_sid, {} end [sid, session] end end
# File lib/rack/session/xfile.rb, line 54 def session_count Dir[File.join(@session_dir, '?', '*')].count end
# File lib/rack/session/xfile.rb, line 30 def write_session(req, sid, session, options) critical_section(req) do write_session_file(sid, session) end end
Private Instance Methods
Ensure session directory has secure permissions, else raise exception.
# File lib/rack/session/xfile.rb, line 167 def check_permissions perms = File.stat(@session_dir).mode msg = "#{self.class}: #{@session_dir} sessions directory is " raise Errno::EACCES, msg + 'not owned' unless File.owned? @session_dir raise Errno::EACCES, msg + 'not accessible' if perms & 0700 != 0700 raise SecurityError, msg + 'world accessible' if perms & 0007 != 0 if perms & 070 != 0 if File.grpowned? @session_dir warn msg + 'group owned/accessible' else raise SecurityError, msg + 'group accessible' end end end
Provide thread-safety for critical sections.
# File lib/rack/session/xfile.rb, line 66 def critical_section(req) @mutex.lock if req.multithread? yield ensure @mutex.unlock if @mutex.locked? end
Skip session processing if the request matches a filter.
User agent filter¶ ↑
User agents that do not utilize sessions, such as bots, may be filtered.
SID filter¶ ↑
The sid
is provided by untrusted clients. A tampered sid could result in directory traversal, pathname, or guessing exploits. Do not waste resources on a client that sends an invalid sid.
NOTE: Whitelist all valid sid characters. It's probably not sufficient to blacklist only potentially dangerous chars such as '..', '/' or ''.
sid bit range: 62^10-62^50 (approx. 2^60-2^297)
# File lib/rack/session/xfile.rb, line 137 def filter_request(req, sid) if @ua_filter and req.user_agent =~ @ua_filter return req.session.options[:skip] = true end if sid and sid !~ /^[a-zA-Z0-9]{10,50}$/ warn "#{self.class} SID=#{sid}: tampered sid detected." req.session.options[:skip] = true end end
Atomically generate a unique session ID and allocate the resource file. Returns the session ID. – The sid is also a filename. Only SIDCharacters
are allowed in the sid. If super
is called it returns a HEX sid, which is a subset.
# File lib/rack/session/xfile.rb, line 80 def generate_sid sid = SecureRandom.urlsafe_base64(@sid_nbytes) rescue super sid.tr!('_-', SIDCharacters.sample(2).join) File.open(session_file(sid), File::WRONLY|File::CREAT|File::EXCL).close sid rescue Errno::EEXIST retry end
Read session from file using a shared lock.
# File lib/rack/session/xfile.rb, line 92 def read_session_file(sid) File.open(session_file(sid)) do |f| f.flock(File::LOCK_SH) begin f.size == 0 ? {} : Marshal.load(f) rescue ArgumentError, TypeError => e warn "#{self.class} PATH=#{session_file(sid)}: #{e.message}" {} end end rescue Errno::ENOENT end
# File lib/rack/session/xfile.rb, line 148 def session_file(sid) File.join @session_dir, sid[0], sid end
Create directory structure for session file distribution.
# File lib/rack/session/xfile.rb, line 155 def setup_directories Dir.mkdir(@session_dir, 0700) unless Dir.exist? @session_dir SIDCharacters.each do |char| dir = File.join @session_dir, char Dir.mkdir(dir, 0700) unless Dir.exist? dir end end
Write session to file using an exclusive lock.
# File lib/rack/session/xfile.rb, line 108 def write_session_file(sid, session) File.open(session_file(sid), 'r+') do |f| f.flock(File::LOCK_EX) f.write(Marshal.dump(session)) f.flush f.truncate(f.pos) end sid rescue => e warn "#{self.class} cannot write session: #{e.message}" false end