aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/request
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch/request')
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb225
-rw-r--r--actionpack/lib/action_dispatch/request/utils.rb67
2 files changed, 292 insertions, 0 deletions
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
new file mode 100644
index 0000000000..9e7fcbd849
--- /dev/null
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -0,0 +1,225 @@
+require 'rack/session/abstract/id'
+
+module ActionDispatch
+ class Request
+ # Session is responsible for lazily loading the session from store.
+ class Session # :nodoc:
+ ENV_SESSION_KEY = Rack::RACK_SESSION # :nodoc:
+ ENV_SESSION_OPTIONS_KEY = Rack::RACK_SESSION_OPTIONS # :nodoc:
+
+ # Singleton object used to determine if an optional param wasn't specified
+ Unspecified = Object.new
+
+ # Creates a session hash, merging the properties of the previous session if any
+ def self.create(store, req, default_options)
+ session_was = find req
+ session = Request::Session.new(store, req)
+ session.merge! session_was if session_was
+
+ set(req, session)
+ Options.set(req, Request::Session::Options.new(store, default_options))
+ session
+ end
+
+ def self.find(req)
+ req.get_header ENV_SESSION_KEY
+ end
+
+ def self.set(req, session)
+ req.set_header ENV_SESSION_KEY, session
+ end
+
+ class Options #:nodoc:
+ def self.set(req, options)
+ req.set_header ENV_SESSION_OPTIONS_KEY, options
+ end
+
+ def self.find(req)
+ req.get_header ENV_SESSION_OPTIONS_KEY
+ end
+
+ def initialize(by, default_options)
+ @by = by
+ @delegate = default_options.dup
+ end
+
+ def [](key)
+ @delegate[key]
+ end
+
+ def id(req)
+ @delegate.fetch(:id) {
+ @by.send(:extract_session_id, req)
+ }
+ end
+
+ def []=(k,v); @delegate[k] = v; end
+ def to_hash; @delegate.dup; end
+ def values_at(*args); @delegate.values_at(*args); end
+ end
+
+ def initialize(by, req)
+ @by = by
+ @req = req
+ @delegate = {}
+ @loaded = false
+ @exists = nil # we haven't checked yet
+ end
+
+ def id
+ options.id(@req)
+ end
+
+ def options
+ Options.find @req
+ end
+
+ def destroy
+ clear
+ options = self.options || {}
+ @by.send(:delete_session, @req, options.id(@req), options)
+
+ # Load the new sid to be written with the response
+ @loaded = false
+ load_for_write!
+ end
+
+ # Returns value of the key stored in the session or
+ # nil if the given key is not found in the session.
+ def [](key)
+ load_for_read!
+ @delegate[key.to_s]
+ end
+
+ # Returns true if the session has the given key or false.
+ def has_key?(key)
+ load_for_read!
+ @delegate.key?(key.to_s)
+ end
+ alias :key? :has_key?
+ alias :include? :has_key?
+
+ # Returns keys of the session as Array.
+ def keys
+ @delegate.keys
+ end
+
+ # Returns values of the session as Array.
+ def values
+ @delegate.values
+ end
+
+ # Writes given value to given key of the session.
+ def []=(key, value)
+ load_for_write!
+ @delegate[key.to_s] = value
+ end
+
+ # Clears the session.
+ def clear
+ load_for_write!
+ @delegate.clear
+ end
+
+ # Returns the session as Hash.
+ def to_hash
+ load_for_read!
+ @delegate.dup.delete_if { |_,v| v.nil? }
+ end
+
+ # Updates the session with given Hash.
+ #
+ # session.to_hash
+ # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2"}
+ #
+ # session.update({ "foo" => "bar" })
+ # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"}
+ #
+ # session.to_hash
+ # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"}
+ def update(hash)
+ load_for_write!
+ @delegate.update stringify_keys(hash)
+ end
+
+ # Deletes given key from the session.
+ def delete(key)
+ load_for_write!
+ @delegate.delete key.to_s
+ end
+
+ # Returns value of given key from the session, or raises +KeyError+
+ # if can't find given key in case of not setted dafault value.
+ # Returns default value if specified.
+ #
+ # session.fetch(:foo)
+ # # => KeyError: key not found: "foo"
+ #
+ # session.fetch(:foo, :bar)
+ # # => :bar
+ #
+ # session.fetch(:foo) do
+ # :bar
+ # end
+ # # => :bar
+ def fetch(key, default=Unspecified, &block)
+ load_for_read!
+ if default == Unspecified
+ @delegate.fetch(key.to_s, &block)
+ else
+ @delegate.fetch(key.to_s, default, &block)
+ end
+ end
+
+ def inspect
+ if loaded?
+ super
+ else
+ "#<#{self.class}:0x#{(object_id << 1).to_s(16)} not yet loaded>"
+ end
+ end
+
+ def exists?
+ return @exists unless @exists.nil?
+ @exists = @by.send(:session_exists?, @req)
+ end
+
+ def loaded?
+ @loaded
+ end
+
+ def empty?
+ load_for_read!
+ @delegate.empty?
+ end
+
+ def merge!(other)
+ load_for_write!
+ @delegate.merge!(other)
+ end
+
+ private
+
+ def load_for_read!
+ load! if !loaded? && exists?
+ end
+
+ def load_for_write!
+ load! unless loaded?
+ end
+
+ def load!
+ id, session = @by.load_session @req
+ options[:id] = id
+ @delegate.replace(stringify_keys(session))
+ @loaded = true
+ end
+
+ def stringify_keys(other)
+ other.each_with_object({}) { |(key, value), hash|
+ hash[key.to_s] = value
+ }
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb
new file mode 100644
index 0000000000..bb3df3c311
--- /dev/null
+++ b/actionpack/lib/action_dispatch/request/utils.rb
@@ -0,0 +1,67 @@
+module ActionDispatch
+ class Request
+ class Utils # :nodoc:
+
+ mattr_accessor :perform_deep_munge
+ self.perform_deep_munge = true
+
+ def self.normalize_encode_params(params)
+ if perform_deep_munge
+ NoNilParamEncoder.normalize_encode_params params
+ else
+ ParamEncoder.normalize_encode_params params
+ end
+ end
+
+ def self.check_param_encoding(params)
+ case params
+ when Array
+ params.each { |element| check_param_encoding(element) }
+ when Hash
+ params.each_value { |value| check_param_encoding(value) }
+ when String
+ unless params.valid_encoding?
+ # Raise Rack::Utils::InvalidParameterError for consistency with Rack.
+ # ActionDispatch::Request#GET will re-raise as a BadRequest error.
+ raise Rack::Utils::InvalidParameterError, "Non UTF-8 value: #{params}"
+ end
+ end
+ end
+
+ class ParamEncoder # :nodoc:
+ # Convert nested Hash to HashWithIndifferentAccess.
+ #
+ def self.normalize_encode_params(params)
+ case params
+ when Array
+ handle_array params
+ when Hash
+ if params.has_key?(:tempfile)
+ ActionDispatch::Http::UploadedFile.new(params)
+ else
+ params.each_with_object({}) do |(key, val), new_hash|
+ new_hash[key] = normalize_encode_params(val)
+ end.with_indifferent_access
+ end
+ else
+ params
+ end
+ end
+
+ def self.handle_array(params)
+ params.map! { |el| normalize_encode_params(el) }
+ end
+ end
+
+ # Remove nils from the params hash
+ class NoNilParamEncoder < ParamEncoder # :nodoc:
+ def self.handle_array(params)
+ list = super
+ list.compact!
+ list
+ end
+ end
+ end
+ end
+end
+