diff options
Diffstat (limited to 'actionpack/lib/action_dispatch/request')
| -rw-r--r-- | actionpack/lib/action_dispatch/request/session.rb | 225 | ||||
| -rw-r--r-- | actionpack/lib/action_dispatch/request/utils.rb | 67 |
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 + |
