From 3da1b94d07fbbd6cff342a822af1631ae167a9b8 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 19 Dec 2008 15:05:51 -0600 Subject: Use status response accessor instead of the 'Status' header --- actionpack/lib/action_controller/base.rb | 4 ++-- actionpack/lib/action_controller/benchmarking.rb | 2 +- actionpack/lib/action_controller/caching/actions.rb | 2 +- actionpack/lib/action_controller/caching/pages.rb | 2 +- actionpack/lib/action_controller/integration.rb | 3 ++- actionpack/lib/action_controller/rack_process.rb | 6 ------ actionpack/lib/action_controller/rescue.rb | 2 +- actionpack/lib/action_controller/response.rb | 8 +++----- actionpack/lib/action_controller/test_process.rb | 2 +- 9 files changed, 12 insertions(+), 19 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 454ef4ffac..eae17d6dd5 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -990,7 +990,7 @@ module ActionController #:nodoc: @performed_redirect = false response.redirected_to = nil response.redirected_to_method_params = nil - response.headers['Status'] = DEFAULT_RENDER_STATUS_CODE + response.status = DEFAULT_RENDER_STATUS_CODE response.headers.delete('Location') end @@ -1171,7 +1171,7 @@ module ActionController #:nodoc: def render_for_text(text = nil, status = nil, append_response = false) #:nodoc: @performed_render = true - response.headers['Status'] = interpret_status(status || DEFAULT_RENDER_STATUS_CODE) + response.status = interpret_status(status || DEFAULT_RENDER_STATUS_CODE) if append_response response.body ||= '' diff --git a/actionpack/lib/action_controller/benchmarking.rb b/actionpack/lib/action_controller/benchmarking.rb index 732f774fbc..47377e5fa9 100644 --- a/actionpack/lib/action_controller/benchmarking.rb +++ b/actionpack/lib/action_controller/benchmarking.rb @@ -83,7 +83,7 @@ module ActionController #:nodoc: end end - log_message << " | #{headers["Status"]}" + log_message << " | #{response.status}" log_message << " [#{complete_request_uri rescue "unknown"}]" logger.info(log_message) diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb index 7c803a9830..34e1c3527f 100644 --- a/actionpack/lib/action_controller/caching/actions.rb +++ b/actionpack/lib/action_controller/caching/actions.rb @@ -113,7 +113,7 @@ module ActionController #:nodoc: end def caching_allowed(controller) - controller.request.get? && controller.response.headers['Status'].to_i == 200 + controller.request.get? && controller.response.status.to_i == 200 end def cache_layout? diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb index 22e4fbec43..bd3b5a5875 100644 --- a/actionpack/lib/action_controller/caching/pages.rb +++ b/actionpack/lib/action_controller/caching/pages.rb @@ -145,7 +145,7 @@ module ActionController #:nodoc: private def caching_allowed - request.get? && response.headers['Status'].to_i == 200 + request.get? && response.status.to_i == 200 end end end diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb index 7590d5d710..8c2e3197df 100644 --- a/actionpack/lib/action_controller/integration.rb +++ b/actionpack/lib/action_controller/integration.rb @@ -333,7 +333,8 @@ module ActionController # Decorate responses from Rack Middleware and Rails Metal # as an AbstractResponse for the purposes of integration testing @response = AbstractResponse.new - @response.headers = @headers.merge('Status' => status.to_s) + @response.status = status.to_s + @response.headers = @headers @response.body = @body end diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb index 8483f8e289..e7d00409ca 100644 --- a/actionpack/lib/action_controller/rack_process.rb +++ b/actionpack/lib/action_controller/rack_process.rb @@ -78,14 +78,8 @@ module ActionController #:nodoc: super() end - # Retrieve status from instance variable if has already been delete - def status - @status || super - end - def to_a(&block) @block = block - @status = headers.delete("Status") if [204, 304].include?(status.to_i) headers.delete("Content-Type") [status, headers.to_hash, []] diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb index b8b0175b5f..5ef79a36ce 100644 --- a/actionpack/lib/action_controller/rescue.rb +++ b/actionpack/lib/action_controller/rescue.rb @@ -102,7 +102,7 @@ module ActionController #:nodoc: # doesn't exist, the body of the response will be left empty. def render_optional_error_file(status_code) status = interpret_status(status_code) - path = "#{Rails.public_path}/#{status[0,3]}.html" + path = "#{Rails.public_path}/#{status.to_s[0,3]}.html" if File.exist?(path) render :file => path, :status => status, :content_type => Mime::HTML else diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb index 4c37f09215..e1bf5bb04d 100644 --- a/actionpack/lib/action_controller/response.rb +++ b/actionpack/lib/action_controller/response.rb @@ -33,6 +33,7 @@ module ActionController # :nodoc: DEFAULT_HEADERS = { "Cache-Control" => "no-cache" } attr_accessor :request + attr_accessor :status # The body content (e.g. HTML) of the response, as a String. attr_accessor :body # The headers of the response, as a Hash. It maps header names to header values. @@ -46,9 +47,6 @@ module ActionController # :nodoc: @body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], [] end - def status; headers['Status'] end - def status=(status) headers['Status'] = status end - def location; headers['Location'] end def location=(url) headers['Location'] = url end @@ -161,7 +159,7 @@ module ActionController # :nodoc: end def nonempty_ok_response? - ok = !status || status[0..2] == '200' + ok = !status || status.to_s[0..2] == '200' ok && body.is_a?(String) && !body.empty? end @@ -186,7 +184,7 @@ module ActionController # :nodoc: # Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice # for, say, a 2GB streaming file. def set_content_length! - unless body.respond_to?(:call) || (status && status[0..2] == '304') + unless body.respond_to?(:call) || (status && status.to_s[0..2] == '304') self.headers["Content-Length"] ||= body.size end end diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index c613d6b862..d3c66dd5f2 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -166,7 +166,7 @@ module ActionController #:nodoc: module TestResponseBehavior #:nodoc: # The response code of the request def response_code - status[0,3].to_i rescue 0 + status.to_s[0,3].to_i rescue 0 end # Returns a String to ensure compatibility with Net::HTTPResponse -- cgit v1.2.3 From a14bbd7a8574c3b485d4b71e0950b2b9ff4e90d7 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 19 Dec 2008 16:49:06 -0600 Subject: Process CGI 'cookie' header into 'Set-Cookie' for all responses. This mostly affects response.headers['cookie'] for test requests. Use response.cookies instead. --- actionpack/lib/action_controller/rack_process.rb | 19 --------- actionpack/lib/action_controller/response.rb | 51 ++++++++++++++++-------- actionpack/lib/action_controller/test_process.rb | 7 +++- 3 files changed, 41 insertions(+), 36 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb index e7d00409ca..be189b421d 100644 --- a/actionpack/lib/action_controller/rack_process.rb +++ b/actionpack/lib/action_controller/rack_process.rb @@ -121,7 +121,6 @@ module ActionController #:nodoc: convert_language! convert_expires! set_status! - set_cookies! end private @@ -147,23 +146,5 @@ module ActionController #:nodoc: def set_status! self.status ||= "200 OK" end - - def set_cookies! - # Convert 'cookie' header to 'Set-Cookie' headers. - # Because Set-Cookie header can appear more the once in the response body, - # we store it in a line break separated string that will be translated to - # multiple Set-Cookie header by the handler. - if cookie = headers.delete('cookie') - cookies = [] - - case cookie - when Array then cookie.each { |c| cookies << c.to_s } - when Hash then cookie.each { |_, c| cookies << c.to_s } - else cookies << cookie.to_s - end - - headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact - end - end end end diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb index e1bf5bb04d..f89861e5a5 100644 --- a/actionpack/lib/action_controller/response.rb +++ b/actionpack/lib/action_controller/response.rb @@ -107,11 +107,11 @@ module ActionController # :nodoc: def etag headers['ETag'] end - + def etag? headers.include?('ETag') end - + def etag=(etag) if etag.blank? headers.delete('ETag') @@ -140,22 +140,23 @@ module ActionController # :nodoc: handle_conditional_get! set_content_length! convert_content_type! + set_cookies! end private - def handle_conditional_get! - if etag? || last_modified? - set_conditional_cache_control! - elsif nonempty_ok_response? - self.etag = body - - if request && request.etag_matches?(etag) - self.status = '304 Not Modified' - self.body = '' - end - - set_conditional_cache_control! - end + def handle_conditional_get! + if etag? || last_modified? + set_conditional_cache_control! + elsif nonempty_ok_response? + self.etag = body + + if request && request.etag_matches?(etag) + self.status = '304 Not Modified' + self.body = '' + end + + set_conditional_cache_control! + end end def nonempty_ok_response? @@ -180,7 +181,7 @@ module ActionController # :nodoc: self.headers["type"] = content_type end end - + # Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice # for, say, a 2GB streaming file. def set_content_length! @@ -188,5 +189,23 @@ module ActionController # :nodoc: self.headers["Content-Length"] ||= body.size end end + + def set_cookies! + # Convert 'cookie' header to 'Set-Cookie' headers. + # Because Set-Cookie header can appear more the once in the response body, + # we store it in a line break separated string that will be translated to + # multiple Set-Cookie header by the handler. + if cookie = headers.delete('cookie') + cookies = [] + + case cookie + when Array then cookie.each { |c| cookies << c.to_s } + when Hash then cookie.each { |_, c| cookies << c.to_s } + else cookies << cookie.to_s + end + + headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact + end + end end end diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index d3c66dd5f2..1597f637fc 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -264,7 +264,12 @@ module ActionController #:nodoc: # # assert_equal ['AuthorOfNewPage'], r.cookies['author'].value def cookies - headers['cookie'].inject({}) { |hash, cookie| hash[cookie.name] = cookie; hash } + cookies = {} + Array(headers['Set-Cookie']).each do |cookie| + key, value = cookie.split(";").first.split("=") + cookies[key] = [value].compact + end + cookies end # Returns binary content (downloadable file), converted to a String -- cgit v1.2.3 From fda62ecf707b4023b30303dd0baf303f1ef8d344 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 19 Dec 2008 17:15:22 -0600 Subject: Rename AbstractResponse to Response and inheirt from Rack::Response --- actionpack/lib/action_controller.rb | 3 +- actionpack/lib/action_controller/dispatcher.rb | 2 +- actionpack/lib/action_controller/integration.rb | 8 +- actionpack/lib/action_controller/rack_process.rb | 77 ------------------- actionpack/lib/action_controller/response.rb | 95 +++++++++++++++--------- actionpack/lib/action_controller/test_process.rb | 6 +- 6 files changed, 70 insertions(+), 121 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index eaf7779f1e..ae947820b4 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -42,7 +42,6 @@ module ActionController end autoload :AbstractRequest, 'action_controller/request' - autoload :AbstractResponse, 'action_controller/response' autoload :Base, 'action_controller/base' autoload :Benchmarking, 'action_controller/benchmarking' autoload :Caching, 'action_controller/caching' @@ -61,8 +60,8 @@ module ActionController autoload :MimeResponds, 'action_controller/mime_responds' autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes' autoload :RackRequest, 'action_controller/rack_process' - autoload :RackResponse, 'action_controller/rack_process' autoload :RecordIdentifier, 'action_controller/record_identifier' + autoload :Response, 'action_controller/response' autoload :RequestForgeryProtection, 'action_controller/request_forgery_protection' autoload :Rescue, 'action_controller/rescue' autoload :Resources, 'action_controller/resources' diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index 11c4f057d8..e1eaaf7cbb 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -98,7 +98,7 @@ module ActionController def _call(env) @request = RackRequest.new(env) - @response = RackResponse.new + @response = Response.new dispatch end diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb index 8c2e3197df..d952c3489b 100644 --- a/actionpack/lib/action_controller/integration.rb +++ b/actionpack/lib/action_controller/integration.rb @@ -181,7 +181,7 @@ module ActionController # - +headers+: Additional HTTP headers to pass, as a Hash. The keys will # automatically be upcased, with the prefix 'HTTP_' added if needed. # - # This method returns an AbstractResponse object, which one can use to + # This method returns an Response object, which one can use to # inspect the details of the response. Furthermore, if this method was # called from an ActionController::IntegrationTest object, then that # object's @response instance variable will point to the same @@ -331,10 +331,10 @@ module ActionController @response = @controller.response else # Decorate responses from Rack Middleware and Rails Metal - # as an AbstractResponse for the purposes of integration testing - @response = AbstractResponse.new + # as an Response for the purposes of integration testing + @response = Response.new @response.status = status.to_s - @response.headers = @headers + @response.headers.replace(@headers) @response.body = @body end diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb index be189b421d..8c6db91dd0 100644 --- a/actionpack/lib/action_controller/rack_process.rb +++ b/actionpack/lib/action_controller/rack_process.rb @@ -70,81 +70,4 @@ module ActionController #:nodoc: @env['rack.session'] = {} end end - - class RackResponse < AbstractResponse #:nodoc: - def initialize - @writer = lambda { |x| @body << x } - @block = nil - super() - end - - def to_a(&block) - @block = block - if [204, 304].include?(status.to_i) - headers.delete("Content-Type") - [status, headers.to_hash, []] - else - [status, headers.to_hash, self] - end - end - - def each(&callback) - if @body.respond_to?(:call) - @writer = lambda { |x| callback.call(x) } - @body.call(self, self) - elsif @body.is_a?(String) - @body.each_line(&callback) - else - @body.each(&callback) - end - - @writer = callback - @block.call(self) if @block - end - - def write(str) - @writer.call str.to_s - str - end - - def close - @body.close if @body.respond_to?(:close) - end - - def empty? - @block == nil && @body.empty? - end - - def prepare! - super - - convert_language! - convert_expires! - set_status! - end - - private - def convert_language! - headers["Content-Language"] = headers.delete("language") if headers["language"] - end - - def convert_expires! - headers["Expires"] = headers.delete("") if headers["expires"] - end - - def convert_content_type! - super - headers['Content-Type'] = headers.delete('type') || "text/html" - headers['Content-Type'] += "; charset=" + headers.delete('charset') if headers['charset'] - end - - def set_content_length! - super - headers["Content-Length"] = headers["Content-Length"].to_s if headers["Content-Length"] - end - - def set_status! - self.status ||= "200 OK" - end - end end diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb index f89861e5a5..866616bac3 100644 --- a/actionpack/lib/action_controller/response.rb +++ b/actionpack/lib/action_controller/response.rb @@ -1,24 +1,25 @@ require 'digest/md5' module ActionController # :nodoc: - # Represents an HTTP response generated by a controller action. One can use an - # ActionController::AbstractResponse object to retrieve the current state of the - # response, or customize the response. An AbstractResponse object can either - # represent a "real" HTTP response (i.e. one that is meant to be sent back to the - # web browser) or a test response (i.e. one that is generated from integration - # tests). See CgiResponse and TestResponse, respectively. + # Represents an HTTP response generated by a controller action. One can use + # an ActionController::Response object to retrieve the current state + # of the response, or customize the response. An Response object can + # either represent a "real" HTTP response (i.e. one that is meant to be sent + # back to the web browser) or a test response (i.e. one that is generated + # from integration tests). See CgiResponse and TestResponse, respectively. # - # AbstractResponse is mostly a Ruby on Rails framework implement detail, and should - # never be used directly in controllers. Controllers should use the methods defined - # in ActionController::Base instead. For example, if you want to set the HTTP - # response's content MIME type, then use ActionControllerBase#headers instead of - # AbstractResponse#headers. + # Response is mostly a Ruby on Rails framework implement detail, and + # should never be used directly in controllers. Controllers should use the + # methods defined in ActionController::Base instead. For example, if you want + # to set the HTTP response's content MIME type, then use + # ActionControllerBase#headers instead of Response#headers. # - # Nevertheless, integration tests may want to inspect controller responses in more - # detail, and that's when AbstractResponse can be useful for application developers. - # Integration test methods such as ActionController::Integration::Session#get and - # ActionController::Integration::Session#post return objects of type TestResponse - # (which are of course also of type AbstractResponse). + # Nevertheless, integration tests may want to inspect controller responses in + # more detail, and that's when Response can be useful for application + # developers. Integration test methods such as + # ActionController::Integration::Session#get and + # ActionController::Integration::Session#post return objects of type + # TestResponse (which are of course also of type Response). # # For example, the following demo integration "test" prints the body of the # controller response to the console: @@ -29,22 +30,24 @@ module ActionController # :nodoc: # puts @response.body # end # end - class AbstractResponse + class Response < Rack::Response DEFAULT_HEADERS = { "Cache-Control" => "no-cache" } attr_accessor :request - attr_accessor :status - # The body content (e.g. HTML) of the response, as a String. - attr_accessor :body - # The headers of the response, as a Hash. It maps header names to header values. - attr_accessor :headers attr_accessor :session, :cookies, :assigns, :template, :layout attr_accessor :redirected_to, :redirected_to_method_params delegate :default_charset, :to => 'ActionController::Base' def initialize - @body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], [] + @status = 200 + @header = DEFAULT_HEADERS.merge("cookie" => []) + + @writer = lambda { |x| @body << x } + @block = nil + + @body = "", + @session, @assigns = [], [] end def location; headers['Location'] end @@ -140,9 +143,31 @@ module ActionController # :nodoc: handle_conditional_get! set_content_length! convert_content_type! + + convert_language! + convert_expires! set_cookies! end + def each(&callback) + if @body.respond_to?(:call) + @writer = lambda { |x| callback.call(x) } + @body.call(self, self) + elsif @body.is_a?(String) + @body.each_line(&callback) + else + @body.each(&callback) + end + + @writer = callback + @block.call(self) if @block + end + + def write(str) + @writer.call str.to_s + str + end + private def handle_conditional_get! if etag? || last_modified? @@ -171,23 +196,25 @@ module ActionController # :nodoc: end def convert_content_type! - if content_type = headers.delete("Content-Type") - self.headers["type"] = content_type - end - if content_type = headers.delete("Content-type") - self.headers["type"] = content_type - end - if content_type = headers.delete("content-type") - self.headers["type"] = content_type - end + headers['Content-Type'] ||= "text/html" + headers['Content-Type'] += "; charset=" + headers.delete('charset') if headers['charset'] end - # Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice - # for, say, a 2GB streaming file. + # Don't set the Content-Length for block-based bodies as that would mean + # reading it all into memory. Not nice for, say, a 2GB streaming file. def set_content_length! unless body.respond_to?(:call) || (status && status.to_s[0..2] == '304') self.headers["Content-Length"] ||= body.size end + headers["Content-Length"] = headers["Content-Length"].to_s if headers["Content-Length"] + end + + def convert_language! + headers["Content-Language"] = headers.delete("language") if headers["language"] + end + + def convert_expires! + headers["Expires"] = headers.delete("") if headers["expires"] end def set_cookies! diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index 1597f637fc..c4d7d52951 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -290,8 +290,8 @@ module ActionController #:nodoc: # TestResponse, which represent the HTTP response results of the requested # controller actions. # - # See AbstractResponse for more information on controller response objects. - class TestResponse < AbstractResponse + # See Response for more information on controller response objects. + class TestResponse < Response include TestResponseBehavior def recycle! @@ -435,7 +435,7 @@ module ActionController #:nodoc: end def session - @response.session + @request.session end def flash -- cgit v1.2.3 From 7b249b67e9df9f375eaad9e6eb41be73338faaa7 Mon Sep 17 00:00:00 2001 From: Matt Bauer Date: Sat, 20 Dec 2008 14:37:51 -0600 Subject: Fix reset_session with lazy cookie stores [#1601 state:resolved] Signed-off-by: Joshua Peek --- .../lib/action_controller/session/abstract_store.rb | 18 +++++++++++++----- .../lib/action_controller/session/cookie_store.rb | 12 +++++------- 2 files changed, 18 insertions(+), 12 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/session/abstract_store.rb b/actionpack/lib/action_controller/session/abstract_store.rb index d4b185aaa2..bf09fd33c5 100644 --- a/actionpack/lib/action_controller/session/abstract_store.rb +++ b/actionpack/lib/action_controller/session/abstract_store.rb @@ -53,6 +53,10 @@ module ActionController end private + def loaded? + @loaded + end + def load! @id, session = @by.send(:load_session, @env) replace(session) @@ -91,19 +95,23 @@ module ActionController def call(env) session = SessionHash.new(self, env) - original_session = session.dup env[ENV_SESSION_KEY] = session env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup response = @app.call(env) - session = env[ENV_SESSION_KEY] - unless session == original_session + session_data = env[ENV_SESSION_KEY] + if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) options = env[ENV_SESSION_OPTIONS_KEY] - sid = session.id - unless set_session(env, sid, session.to_hash) + if session_data.is_a?(AbstractStore::SessionHash) + sid = session_data.id + else + sid = generate_sid + end + + unless set_session(env, sid, session_data.to_hash) return response end diff --git a/actionpack/lib/action_controller/session/cookie_store.rb b/actionpack/lib/action_controller/session/cookie_store.rb index ba63f8521f..135bedaf50 100644 --- a/actionpack/lib/action_controller/session/cookie_store.rb +++ b/actionpack/lib/action_controller/session/cookie_store.rb @@ -89,16 +89,14 @@ module ActionController end def call(env) - session_data = AbstractStore::SessionHash.new(self, env) - original_value = session_data.dup - - env[ENV_SESSION_KEY] = session_data + env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env) env[ENV_SESSION_OPTIONS_KEY] = @default_options status, headers, body = @app.call(env) - unless env[ENV_SESSION_KEY] == original_value - session_data = marshal(env[ENV_SESSION_KEY].to_hash) + session_data = env[ENV_SESSION_KEY] + if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) + session_data = marshal(session_data.to_hash) raise CookieOverflow if session_data.size > MAX @@ -153,7 +151,7 @@ module ActionController # Marshal a session hash into safe cookie data. Include an integrity hash. def marshal(session) - @verifier.generate( persistent_session_id!(session)) + @verifier.generate(persistent_session_id!(session)) end # Unmarshal cookie data to a hash and verify its integrity. -- cgit v1.2.3 From 606cd61b9a2a710a27c2e482b5dace100cce9779 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 21 Dec 2008 02:11:39 +0000 Subject: Fix Mime::Type#=~ not using Regexp.quote Signed-off-by: Pratik Naik --- actionpack/lib/action_controller/mime_type.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/mime_type.rb b/actionpack/lib/action_controller/mime_type.rb index 43b3da8d35..017626ba27 100644 --- a/actionpack/lib/action_controller/mime_type.rb +++ b/actionpack/lib/action_controller/mime_type.rb @@ -178,7 +178,7 @@ module Mime def =~(mime_type) return false if mime_type.blank? - regexp = Regexp.new(mime_type.to_s) + regexp = Regexp.new(Regexp.quote(mime_type.to_s)) (@synonyms + [ self ]).any? do |synonym| synonym.to_s =~ regexp end -- cgit v1.2.3 From 3b317b7100c9a416f4e3545f3844f0c0743acdb2 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sat, 20 Dec 2008 21:25:09 -0600 Subject: Switch to Rack::Response#set_cookie instead of using CGI::Cookie to build cookie headers --- actionpack/lib/action_controller/base.rb | 4 +- actionpack/lib/action_controller/cookies.rb | 38 ++++++----------- actionpack/lib/action_controller/response.rb | 54 +++++++++++++++--------- actionpack/lib/action_controller/test_case.rb | 5 +-- actionpack/lib/action_controller/test_process.rb | 6 +-- 5 files changed, 52 insertions(+), 55 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index eae17d6dd5..3e001a2ed6 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -254,7 +254,7 @@ module ActionController #:nodoc: cattr_reader :protected_instance_variables # Controller specific instance variables which will not be accessible inside views. @@protected_instance_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller - @action_name @before_filter_chain_aborted @action_cache_path @_session @_cookies @_headers @_params + @action_name @before_filter_chain_aborted @action_cache_path @_session @_headers @_params @_flash @_response) # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets, @@ -1193,7 +1193,7 @@ module ActionController #:nodoc: end def assign_shortcuts(request, response) - @_request, @_params, @_cookies = request, request.parameters, request.cookies + @_request, @_params = request, request.parameters @_response = response @_response.session = request.session diff --git a/actionpack/lib/action_controller/cookies.rb b/actionpack/lib/action_controller/cookies.rb index 0e058085ec..840ceb5abd 100644 --- a/actionpack/lib/action_controller/cookies.rb +++ b/actionpack/lib/action_controller/cookies.rb @@ -64,45 +64,31 @@ module ActionController #:nodoc: # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists. def [](name) - cookie = @cookies[name.to_s] - if cookie && cookie.respond_to?(:value) - cookie.size > 1 ? cookie.value : cookie.value[0] - else - cookie - end + super(name.to_s) end # Sets the cookie named +name+. The second argument may be the very cookie # value, or a hash of options as documented above. - def []=(name, options) + def []=(key, options) if options.is_a?(Hash) - options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options } - options["name"] = name.to_s + options.symbolize_keys! else - options = { "name" => name.to_s, "value" => options } + options = { :value => options } end - set_cookie(options) + options[:path] = "/" unless options.has_key?(:path) + super(key.to_s, options[:value]) + @controller.response.set_cookie(key, options) end # Removes the cookie on the client machine by setting the value to an empty string # and setting its expiration date into the past. Like []=, you can pass in # an options hash to delete cookies with extra data such as a :path. - def delete(name, options = {}) - options.stringify_keys! - set_cookie(options.merge("name" => name.to_s, "value" => "", "expires" => Time.at(0))) + def delete(key, options = {}) + options.symbolize_keys! + options[:path] = "/" unless options.has_key?(:path) + super(key.to_s) + @controller.response.delete_cookie(key, options) end - - private - # Builds a CGI::Cookie object and adds the cookie to the response headers. - # - # The path of the cookie defaults to "/" if there's none in +options+, and - # everything is passed to the CGI::Cookie constructor. - def set_cookie(options) #:doc: - options["path"] = "/" unless options["path"] - cookie = CGI::Cookie.new(options) - @controller.logger.info "Cookie set: #{cookie}" unless @controller.logger.nil? - @controller.response.headers["cookie"] << cookie - end end end diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb index 866616bac3..64319fe102 100644 --- a/actionpack/lib/action_controller/response.rb +++ b/actionpack/lib/action_controller/response.rb @@ -34,14 +34,14 @@ module ActionController # :nodoc: DEFAULT_HEADERS = { "Cache-Control" => "no-cache" } attr_accessor :request - attr_accessor :session, :cookies, :assigns, :template, :layout + attr_accessor :session, :assigns, :template, :layout attr_accessor :redirected_to, :redirected_to_method_params delegate :default_charset, :to => 'ActionController::Base' def initialize @status = 200 - @header = DEFAULT_HEADERS.merge("cookie" => []) + @header = DEFAULT_HEADERS.dup @writer = lambda { |x| @body << x } @block = nil @@ -143,10 +143,9 @@ module ActionController # :nodoc: handle_conditional_get! set_content_length! convert_content_type! - convert_language! convert_expires! - set_cookies! + convert_cookies! end def each(&callback) @@ -168,6 +167,35 @@ module ActionController # :nodoc: str end + # Over Rack::Response#set_cookie to add HttpOnly option + def set_cookie(key, value) + case value + when Hash + domain = "; domain=" + value[:domain] if value[:domain] + path = "; path=" + value[:path] if value[:path] + # According to RFC 2109, we need dashes here. + # N.B.: cgi.rb uses spaces... + expires = "; expires=" + value[:expires].clone.gmtime. + strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires] + secure = "; secure" if value[:secure] + httponly = "; HttpOnly" if value[:http_only] + value = value[:value] + end + value = [value] unless Array === value + cookie = ::Rack::Utils.escape(key) + "=" + + value.map { |v| ::Rack::Utils.escape v }.join("&") + + "#{domain}#{path}#{expires}#{secure}#{httponly}" + + case self["Set-Cookie"] + when Array + self["Set-Cookie"] << cookie + when String + self["Set-Cookie"] = [self["Set-Cookie"], cookie] + when nil + self["Set-Cookie"] = cookie + end + end + private def handle_conditional_get! if etag? || last_modified? @@ -217,22 +245,8 @@ module ActionController # :nodoc: headers["Expires"] = headers.delete("") if headers["expires"] end - def set_cookies! - # Convert 'cookie' header to 'Set-Cookie' headers. - # Because Set-Cookie header can appear more the once in the response body, - # we store it in a line break separated string that will be translated to - # multiple Set-Cookie header by the handler. - if cookie = headers.delete('cookie') - cookies = [] - - case cookie - when Array then cookie.each { |c| cookies << c.to_s } - when Hash then cookie.each { |_, c| cookies << c.to_s } - else cookies << cookie.to_s - end - - headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact - end + def convert_cookies! + headers['Set-Cookie'] = Array(headers['Set-Cookie']).compact end end end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 79a8e1364d..7ed1a3e160 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -93,10 +93,7 @@ module ActionController # and cookies, though. For sessions, you just do: # # @request.session[:key] = "value" - # - # For cookies, you need to manually create the cookie, like this: - # - # @request.cookies["key"] = CGI::Cookie.new("key", "value") + # @request.cookies["key"] = "value" # # == Testing named routes # diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index c4d7d52951..45dcf8b2c2 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -260,14 +260,14 @@ module ActionController #:nodoc: !template_objects[name].nil? end - # Returns the response cookies, converted to a Hash of (name => CGI::Cookie) pairs + # Returns the response cookies, converted to a Hash of (name => value) pairs # - # assert_equal ['AuthorOfNewPage'], r.cookies['author'].value + # assert_equal 'AuthorOfNewPage', r.cookies['author'] def cookies cookies = {} Array(headers['Set-Cookie']).each do |cookie| key, value = cookie.split(";").first.split("=") - cookies[key] = [value].compact + cookies[key] = value end cookies end -- cgit v1.2.3 From 40247a8cbb1ec735ccd4d8490043345b86af31cc Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 21 Dec 2008 12:12:42 +0000 Subject: Remove observe_field :on option as prototype no longer supports it [#1088 state:resolved] --- actionpack/lib/action_view/helpers/prototype_helper.rb | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index 7fab3102e7..18a209dcea 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -531,11 +531,6 @@ module ActionView # is shorthand for # :with => "'name=' + value" # This essentially just changes the key of the parameter. - # :on:: Specifies which event handler to observe. By default, - # it's set to "changed" for text fields and areas and - # "click" for radio buttons and checkboxes. With this, - # you can specify it instead to be "blur" or "focus" or - # any other event. # # Additionally, you may specify any of the options documented in the # Common options section at the top of this document. @@ -548,11 +543,6 @@ module ActionView # :url => 'http://example.com/books/edit/1', # :with => 'title' # - # # Sends params: {:book_title => 'Title of the book'} when the focus leaves - # # the input field. - # observe_field 'book_title', - # :url => 'http://example.com/books/edit/1', - # :on => 'blur' # def observe_field(field_id, options = {}) if options[:frequency] && options[:frequency] > 0 @@ -1094,7 +1084,6 @@ module ActionView javascript << "#{options[:frequency]}, " if options[:frequency] javascript << "function(element, value) {" javascript << "#{callback}}" - javascript << ", '#{options[:on]}'" if options[:on] javascript << ")" javascript_tag(javascript) end -- cgit v1.2.3 From fcd58dc27a99085b161f2463988d4ee373d44ec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=3D=3Futf-8=3Fq=3FAdam=3D20Cig=3DC3=3DA1nek=3F=3D?= Date: Sun, 21 Dec 2008 18:58:55 +0000 Subject: Allow use of symbols for :type option of ActionController::Streaming#send_file/#send_data [#1232 state:resolved] Signed-off-by: Frederick Cheung --- actionpack/lib/action_controller/streaming.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/streaming.rb b/actionpack/lib/action_controller/streaming.rb index 333fb61b45..e1786913a7 100644 --- a/actionpack/lib/action_controller/streaming.rb +++ b/actionpack/lib/action_controller/streaming.rb @@ -24,7 +24,8 @@ module ActionController #:nodoc: # Options: # * :filename - suggests a filename for the browser to use. # Defaults to File.basename(path). - # * :type - specifies an HTTP content type. Defaults to 'application/octet-stream'. + # * :type - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify + # either a string or a symbol for a registered type register with Mime::Type.register, for example :json # * :length - used to manually override the length (in bytes) of the content that # is going to be sent to the client. Defaults to File.size(path). # * :disposition - specifies whether the file will be shown inline or downloaded. @@ -107,7 +108,8 @@ module ActionController #:nodoc: # # Options: # * :filename - suggests a filename for the browser to use. - # * :type - specifies an HTTP content type. Defaults to 'application/octet-stream'. + # * :type - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify + # either a string or a symbol for a registered type register with Mime::Type.register, for example :json # * :disposition - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). # * :status - specifies the status code to send with the response. Defaults to '200 OK'. @@ -143,9 +145,16 @@ module ActionController #:nodoc: disposition <<= %(; filename="#{options[:filename]}") if options[:filename] + content_type = options[:type] + if content_type.is_a?(Symbol) + raise ArgumentError, "Unknown MIME type #{options[:type]}" unless Mime::EXTENSION_LOOKUP.has_key?(content_type.to_s) + content_type = Mime::Type.lookup_by_extension(content_type.to_s) + end + content_type = content_type.to_s.strip # fixes a problem with extra '\r' with some browsers + headers.update( 'Content-Length' => options[:length], - 'Content-Type' => options[:type].to_s.strip, # fixes a problem with extra '\r' with some browsers + 'Content-Type' => content_type, 'Content-Disposition' => disposition, 'Content-Transfer-Encoding' => 'binary' ) -- cgit v1.2.3 From 858a420ce18719c720b80508b336e37ce37a20bf Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sun, 21 Dec 2008 17:23:53 -0600 Subject: Ensure the template format is always passed to the template finder. Now we can cleanup some nasty stuff. --- actionpack/lib/action_controller/base.rb | 30 +++++++++------ actionpack/lib/action_controller/layout.rb | 22 +++++++++-- actionpack/lib/action_view/base.rb | 62 +++--------------------------- actionpack/lib/action_view/partials.rb | 2 +- actionpack/lib/action_view/paths.rb | 41 +++++++------------- actionpack/lib/action_view/renderable.rb | 12 +++++- actionpack/lib/action_view/template.rb | 28 ++++++++++++++ 7 files changed, 93 insertions(+), 104 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 3e001a2ed6..4d4793c4e3 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -502,7 +502,7 @@ module ActionController #:nodoc: protected :filter_parameters end - delegate :exempt_from_layout, :to => 'ActionView::Base' + delegate :exempt_from_layout, :to => 'ActionView::Template' end public @@ -860,7 +860,7 @@ module ActionController #:nodoc: raise DoubleRenderError, "Can only render or redirect once per action" if performed? if options.nil? - return render(:file => default_template_name, :layout => true) + return render(:file => default_template, :layout => true) elsif !extra_options.is_a?(Hash) raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}" else @@ -898,7 +898,7 @@ module ActionController #:nodoc: render_for_text(@template.render(options.merge(:layout => layout)), options[:status]) elsif action_name = options[:action] - render_for_file(default_template_name(action_name.to_s), options[:status], layout) + render_for_file(default_template(action_name.to_s), options[:status], layout) elsif xml = options[:xml] response.content_type ||= Mime::XML @@ -933,7 +933,7 @@ module ActionController #:nodoc: render_for_text(nil, options[:status]) else - render_for_file(default_template_name, options[:status], layout) + render_for_file(default_template, options[:status], layout) end end end @@ -1164,7 +1164,8 @@ module ActionController #:nodoc: private def render_for_file(template_path, status = nil, layout = nil, locals = {}) #:nodoc: - logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger + path = template_path.respond_to?(:path_without_format_and_extension) ? template_path.path_without_format_and_extension : template_path + logger.info("Rendering #{path}" + (status ? " (#{status})" : '')) if logger render_for_text @template.render(:file => template_path, :locals => locals, :layout => layout), status end @@ -1241,10 +1242,17 @@ module ActionController #:nodoc: elsif respond_to? :method_missing method_missing action_name default_render unless performed? - elsif template_exists? - default_render else - raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence}", caller + begin + default_render + rescue ActionView::MissingTemplate => e + # Was the implicit template missing, or was it another template? + if e.path == default_template_name + raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence}", caller + else + raise e + end + end end end @@ -1290,10 +1298,8 @@ module ActionController #:nodoc: @_session.close if @_session && @_session.respond_to?(:close) end - def template_exists?(template_name = default_template_name) - @template.send(:_pick_template, template_name) ? true : false - rescue ActionView::MissingTemplate - false + def default_template(action_name = self.action_name) + self.view_paths.find_template(default_template_name(action_name), default_template_format) end def default_template_name(action_name = self.action_name) diff --git a/actionpack/lib/action_controller/layout.rb b/actionpack/lib/action_controller/layout.rb index 54108df06d..159c5c7326 100644 --- a/actionpack/lib/action_controller/layout.rb +++ b/actionpack/lib/action_controller/layout.rb @@ -178,9 +178,15 @@ module ActionController #:nodoc: find_layout(layout, format) end + def layout_list #:nodoc: + Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] } + end + def find_layout(layout, *formats) #:nodoc: return layout if layout.respond_to?(:render) view_paths.find_template(layout.to_s =~ /layouts\// ? layout : "layouts/#{layout}", *formats) + rescue ActionView::MissingTemplate + nil end private @@ -188,7 +194,7 @@ module ActionController #:nodoc: inherited_without_layout(child) unless child.name.blank? layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '') - child.layout(layout_match, {}, true) if child.find_layout(layout_match, :all) + child.layout(layout_match, {}, true) unless child.layout_list.grep(%r{layouts/#{layout_match}(\.[a-z][0-9a-z]*)+$}).empty? end end @@ -225,8 +231,16 @@ module ActionController #:nodoc: private def candidate_for_layout?(options) - options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing, :update).compact.empty? && - !@template.__send__(:_exempt_from_layout?, options[:template] || default_template_name(options[:action])) + template = options[:template] || default_template(options[:action]) + if options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing, :update).compact.empty? + begin + !self.view_paths.find_template(template, default_template_format).exempt_from_layout? + rescue ActionView::MissingTemplate + true + end + end + rescue ActionView::MissingTemplate + false end def pick_layout(options) @@ -235,7 +249,7 @@ module ActionController #:nodoc: when FalseClass nil when NilClass, TrueClass - active_layout if action_has_layout? && !@template.__send__(:_exempt_from_layout?, default_template_name) + active_layout if action_has_layout? && candidate_for_layout?(:template => default_template_name) else active_layout(layout) end diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 33517ffb7b..8958e61e9d 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -3,7 +3,10 @@ module ActionView #:nodoc: end class MissingTemplate < ActionViewError #:nodoc: + attr_reader :path + def initialize(paths, path, template_format = nil) + @path = path full_template_path = path.include?('.') ? path : "#{path}.erb" display_paths = paths.compact.join(":") template_type = (path =~ /layouts/i) ? 'layout' : 'template' @@ -172,17 +175,6 @@ module ActionView #:nodoc: delegate :logger, :to => 'ActionController::Base' end - # Templates that are exempt from layouts - @@exempt_from_layout = Set.new([/\.rjs$/]) - - # Don't render layouts for templates with the given extensions. - def self.exempt_from_layout(*extensions) - regexps = extensions.collect do |extension| - extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/ - end - @@exempt_from_layout.merge(regexps) - end - @@debug_rjs = false ## # :singleton-method: @@ -190,12 +182,6 @@ module ActionView #:nodoc: # that alert()s the caught exception (and then re-raises it). cattr_accessor :debug_rjs - @@warn_cache_misses = false - ## - # :singleton-method: - # A warning will be displayed whenever an action results in a cache miss on your view paths. - cattr_accessor :warn_cache_misses - attr_internal :request delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers, @@ -257,7 +243,8 @@ module ActionView #:nodoc: if options[:layout] _render_with_layout(options, local_assigns, &block) elsif options[:file] - _pick_template(options[:file]).render_template(self, options[:locals]) + tempalte = self.view_paths.find_template(options[:file], template_format) + tempalte.render_template(self, options[:locals]) elsif options[:partial] render_partial(options) elsif options[:inline] @@ -315,45 +302,6 @@ module ActionView #:nodoc: end end - def _pick_template(template_path) - return template_path if template_path.respond_to?(:render) - - path = template_path.sub(/^\//, '') - if m = path.match(/(.*)\.(\w+)$/) - template_file_name, template_file_extension = m[1], m[2] - else - template_file_name = path - end - - # OPTIMIZE: Checks to lookup template in view path - if template = self.view_paths.find_template(template_file_name, template_format) - template - elsif (first_render = @_render_stack.first) && first_render.respond_to?(:format_and_extension) && - (template = self.view_paths["#{template_file_name}.#{first_render.format_and_extension}"]) - template - else - template = Template.new(template_path, view_paths) - - if self.class.warn_cache_misses && logger - logger.debug "[PERFORMANCE] Rendering a template that was " + - "not found in view path. Templates outside the view path are " + - "not cached and result in expensive disk operations. Move this " + - "file into #{view_paths.join(':')} or add the folder to your " + - "view path list" - end - - template - end - end - memoize :_pick_template - - def _exempt_from_layout?(template_path) #:nodoc: - template = _pick_template(template_path).to_s - @@exempt_from_layout.any? { |ext| template =~ ext } - rescue ActionView::MissingTemplate - return false - end - def _render_with_layout(options, local_assigns, &block) #:nodoc: partial_layout = options.delete(:layout) diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb index bbc995a340..59e82b98a4 100644 --- a/actionpack/lib/action_view/partials.rb +++ b/actionpack/lib/action_view/partials.rb @@ -228,7 +228,7 @@ module ActionView path = "_#{partial_path}" end - _pick_template(path) + self.view_paths.find_template(path, self.template_format) end memoize :_pick_partial_template end diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index 623b9ff6b0..b030156889 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -2,13 +2,6 @@ module ActionView #:nodoc: class PathSet < Array #:nodoc: def self.type_cast(obj) if obj.is_a?(String) - if Base.warn_cache_misses && defined?(Rails) && Rails.initialized? - Base.logger.debug "[PERFORMANCE] Processing view path during a " + - "request. This an expense disk operation that should be done at " + - "boot. You can manually process this view path with " + - "ActionView::Base.process_view_paths(#{obj.inspect}) and set it " + - "as your view path" - end Path.new(obj) else obj @@ -92,7 +85,7 @@ module ActionView #:nodoc: else Dir.glob("#{@path}/#{path}*").each do |file| template = create_template(file) - if path == template.path_without_extension || path == template.path + if template.accessible_paths.include?(path) return template end end @@ -115,8 +108,9 @@ module ActionView #:nodoc: templates_in_path do |template| template.load! - @paths[template.path] = template - @paths[template.path_without_extension] ||= template + template.accessible_paths.each do |path| + @paths[path] = template + end end @paths.freeze @@ -143,28 +137,19 @@ module ActionView #:nodoc: each { |path| path.reload! } end - def [](template_path) - each do |path| - if template = path[template_path] - return template - end - end - nil - end + def find_template(original_template_path, format = nil) + return original_template_path if original_template_path.respond_to?(:render) + template_path = original_template_path.sub(/^\//, '') - def find_template(path, *formats) - if formats && formats.first == :all - formats = Mime::EXTENSION_LOOKUP.values.map(&:to_sym) - end - formats.each do |format| - if template = self["#{path}.#{format}"] + each do |load_path| + if format && (template = load_path["#{template_path}.#{format}"]) + return template + elsif template = load_path[template_path] return template end end - if template = self[path] - return template - end - nil + + Template.new(original_template_path, self) end end end diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb index 7c0e62f1d7..4a5b36d70a 100644 --- a/actionpack/lib/action_view/renderable.rb +++ b/actionpack/lib/action_view/renderable.rb @@ -22,6 +22,11 @@ module ActionView end memoize :compiled_source + def method_name_without_locals + ['_run', extension, method_segment].compact.join('_') + end + memoize :method_name_without_locals + def render(view, local_assigns = {}) compile(local_assigns) @@ -46,9 +51,12 @@ module ActionView def method_name(local_assigns) if local_assigns && local_assigns.any? - local_assigns_keys = "locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}" + method_name = method_name_without_locals.dup + method_name << "_locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}" + else + method_name = method_name_without_locals end - ['_run', extension, method_segment, local_assigns_keys].compact.join('_').to_sym + method_name.to_sym end private diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 93748638c3..5b384d0e4d 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -4,6 +4,17 @@ module ActionView #:nodoc: extend ActiveSupport::Memoizable include Renderable + # Templates that are exempt from layouts + @@exempt_from_layout = Set.new([/\.rjs$/]) + + # Don't render layouts for templates with the given extensions. + def self.exempt_from_layout(*extensions) + regexps = extensions.collect do |extension| + extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/ + end + @@exempt_from_layout.merge(regexps) + end + attr_accessor :filename, :load_path, :base_path, :name, :format, :extension delegate :to_s, :to => :path @@ -17,6 +28,18 @@ module ActionView #:nodoc: extend RenderablePartial if @name =~ /^_/ end + def accessible_paths + paths = [] + paths << path + paths << path_without_extension + if multipart? + formats = format.split(".") + paths << "#{path_without_format_and_extension}.#{formats.first}" + paths << "#{path_without_format_and_extension}.#{formats.second}" + end + paths + end + def format_and_extension (extensions = [format, extension].compact.join(".")).blank? ? nil : extensions end @@ -57,6 +80,10 @@ module ActionView #:nodoc: end memoize :relative_path + def exempt_from_layout? + @@exempt_from_layout.any? { |exempted| path =~ exempted } + end + def mtime File.mtime(filename) end @@ -94,6 +121,7 @@ module ActionView #:nodoc: def load! @loaded = true + compile({}) freeze end -- cgit v1.2.3 From 389534c38c3baaa63ce5cc2ba3bd169415419167 Mon Sep 17 00:00:00 2001 From: Sam Oliver Date: Sun, 21 Dec 2008 19:46:33 +0000 Subject: Added prompt options to date helpers [#561 state:resolved] Signed-off-by: Pratik Naik --- actionpack/lib/action_view/helpers/date_helper.rb | 79 +++++++++++++++++++++++ 1 file changed, 79 insertions(+) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index a04bb8c598..84ba5f0a8c 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -136,6 +136,10 @@ module ActionView # dates. # * :default - Set a default date if the affected date isn't set or is nil. # * :disabled - Set to true if you want show the select fields as disabled. + # * :prompt - Set to true (for a generic prompt), a prompt string or a hash of prompt strings + # for :year, :month, :day, :hour, :minute and :second. + # Setting this option prepends a select option with a generic prompt (Day, Month, Year, Hour, Minute, Seconds) + # or the given prompt string. # # If anything is passed in the +html_options+ hash it will be applied to every select tag in the set. # @@ -171,6 +175,9 @@ module ActionView # # that will have a default day of 20. # date_select("credit_card", "bill_due", :default => { :day => 20 }) # + # # Generates a date select with custom prompts + # date_select("post", "written_on", :prompt => { :day => 'Select day', :month => 'Select month', :year => 'Select year' }) + # # The selects are prepared for multi-parameter assignment to an Active Record object. # # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that @@ -210,6 +217,11 @@ module ActionView # # You can set the :minute_step to 15 which will give you: 00, 15, 30 and 45. # time_select 'game', 'game_time', {:minute_step => 15} # + # # Creates a time select tag with a custom prompt. Use :prompt => true for generic prompts. + # time_select("post", "written_on", :prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'}) + # time_select("post", "written_on", :prompt => {:hour => true}) # generic prompt for hours + # time_select("post", "written_on", :prompt => true) # generic prompts for all + # # The selects are prepared for multi-parameter assignment to an Active Record object. # # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that @@ -241,6 +253,11 @@ module ActionView # # as the written_on attribute. # datetime_select("post", "written_on", :discard_type => true) # + # # Generates a datetime select with a custom prompt. Use :prompt=>true for generic prompts. + # datetime_select("post", "written_on", :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'}) + # datetime_select("post", "written_on", :prompt => {:hour => true}) # generic prompt for hours + # datetime_select("post", "written_on", :prompt => true) # generic prompts for all + # # The selects are prepared for multi-parameter assignment to an Active Record object. def datetime_select(object_name, method, options = {}, html_options = {}) InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options) @@ -285,6 +302,11 @@ module ActionView # # prefixed with 'payday' rather than 'date' # select_datetime(my_date_time, :prefix => 'payday') # + # # Generates a datetime select with a custom prompt. Use :prompt=>true for generic prompts. + # select_datetime(my_date_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'}) + # select_datetime(my_date_time, :prompt => {:hour => true}) # generic prompt for hours + # select_datetime(my_date_time, :prompt => true) # generic prompts for all + # def select_datetime(datetime = Time.current, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_datetime end @@ -321,6 +343,11 @@ module ActionView # # prefixed with 'payday' rather than 'date' # select_date(my_date, :prefix => 'payday') # + # # Generates a date select with a custom prompt. Use :prompt=>true for generic prompts. + # select_date(my_date, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'}) + # select_date(my_date, :prompt => {:hour => true}) # generic prompt for hours + # select_date(my_date, :prompt => true) # generic prompts for all + # def select_date(date = Date.current, options = {}, html_options = {}) DateTimeSelector.new(date, options, html_options).select_date end @@ -352,6 +379,11 @@ module ActionView # # separated by ':' and includes an input for seconds # select_time(my_time, :time_separator => ':', :include_seconds => true) # + # # Generates a time select with a custom prompt. Use :prompt=>true for generic prompts. + # select_time(my_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'}) + # select_time(my_time, :prompt => {:hour => true}) # generic prompt for hours + # select_time(my_time, :prompt => true) # generic prompts for all + # def select_time(datetime = Time.current, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_time end @@ -373,6 +405,10 @@ module ActionView # # that is named 'interval' rather than 'second' # select_second(my_time, :field_name => 'interval') # + # # Generates a select field for seconds with a custom prompt. Use :prompt=>true for a + # # generic prompt. + # select_minute(14, :prompt => 'Choose seconds') + # def select_second(datetime, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_second end @@ -395,6 +431,10 @@ module ActionView # # that is named 'stride' rather than 'second' # select_minute(my_time, :field_name => 'stride') # + # # Generates a select field for minutes with a custom prompt. Use :prompt=>true for a + # # generic prompt. + # select_minute(14, :prompt => 'Choose minutes') + # def select_minute(datetime, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_minute end @@ -416,6 +456,10 @@ module ActionView # # that is named 'stride' rather than 'second' # select_hour(my_time, :field_name => 'stride') # + # # Generates a select field for hours with a custom prompt. Use :prompt => true for a + # # generic prompt. + # select_hour(13, :prompt =>'Choose hour') + # def select_hour(datetime, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_hour end @@ -437,6 +481,10 @@ module ActionView # # that is named 'due' rather than 'day' # select_day(my_time, :field_name => 'due') # + # # Generates a select field for days with a custom prompt. Use :prompt => true for a + # # generic prompt. + # select_day(5, :prompt => 'Choose day') + # def select_day(date, options = {}, html_options = {}) DateTimeSelector.new(date, options, html_options).select_day end @@ -475,6 +523,10 @@ module ActionView # # will use keys like "Januar", "Marts." # select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...)) # + # # Generates a select field for months with a custom prompt. Use :prompt => true for a + # # generic prompt. + # select_month(14, :prompt => 'Choose month') + # def select_month(date, options = {}, html_options = {}) DateTimeSelector.new(date, options, html_options).select_month end @@ -502,6 +554,10 @@ module ActionView # # has ascending year values # select_year(2006, :start_year => 2000, :end_year => 2010) # + # # Generates a select field for years with a custom prompt. Use :prompt => true for a + # # generic prompt. + # select_year(14, :prompt => 'Choose year') + # def select_year(date, options = {}, html_options = {}) DateTimeSelector.new(date, options, html_options).select_year end @@ -516,6 +572,10 @@ module ActionView :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }.freeze unless const_defined?('POSITION') + DEFAULT_PROMPTS = { + :year => 'Year', :month => 'Month', :day => 'Day', :hour => 'Hour', :minute => 'Minute', :second => 'Seconds' + }.freeze unless const_defined?('DEFAULT_PROMPTS') + def initialize(datetime, options = {}, html_options = {}) @options = options.dup @html_options = html_options.dup @@ -764,11 +824,30 @@ module ActionView select_html = "\n" select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank] + select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt] select_html << select_options_as_html.to_s content_tag(:select, select_html, select_options) + "\n" end + # Builds a prompt option tag with supplied options or from default options + # prompt_option_tag(:month, :prompt => 'Select month') + # => "" + def prompt_option_tag(type, options) + default_options = {:year => false, :month => false, :day => false, :hour => false, :minute => false, :second => false} + + case options + when Hash + prompt = default_options.merge(options)[type.to_sym] + when String + prompt = options + else + prompt = ActionView::Helpers::DateTimeSelector::DEFAULT_PROMPTS[type.to_sym] + end + + prompt ? content_tag(:option, prompt, :value => '') : '' + end + # Builds hidden input tag for date part and value # build_hidden(:year, 2008) # => "" -- cgit v1.2.3 From 70456aed31ae64b36563fc5d32ac114e0a095231 Mon Sep 17 00:00:00 2001 From: Sam Oliver Date: Mon, 22 Dec 2008 11:41:47 +0000 Subject: Use I18n for date/time select helpers prompt text [#561 state:resolved] Signed-off-by: Pratik Naik --- actionpack/lib/action_view/helpers/date_helper.rb | 8 ++------ actionpack/lib/action_view/locale/en.yml | 7 +++++++ 2 files changed, 9 insertions(+), 6 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 84ba5f0a8c..4305617ac8 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -572,10 +572,6 @@ module ActionView :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }.freeze unless const_defined?('POSITION') - DEFAULT_PROMPTS = { - :year => 'Year', :month => 'Month', :day => 'Day', :hour => 'Hour', :minute => 'Minute', :second => 'Seconds' - }.freeze unless const_defined?('DEFAULT_PROMPTS') - def initialize(datetime, options = {}, html_options = {}) @options = options.dup @html_options = html_options.dup @@ -842,10 +838,10 @@ module ActionView when String prompt = options else - prompt = ActionView::Helpers::DateTimeSelector::DEFAULT_PROMPTS[type.to_sym] + prompt = I18n.translate(('datetime.prompts.' + type.to_s).to_sym, :locale => @options[:locale]) end - prompt ? content_tag(:option, prompt, :value => '') : '' + prompt ? content_tag(:option, prompt, :value => '') : '' end # Builds hidden input tag for date part and value diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml index 9542b035aa..a880fd83ef 100644 --- a/actionpack/lib/action_view/locale/en.yml +++ b/actionpack/lib/action_view/locale/en.yml @@ -80,6 +80,13 @@ over_x_years: one: "over 1 year" other: "over {{count}} years" + prompts: + year: "Year" + month: "Month" + day: "Day" + hour: "Hour" + minute: "Minute" + second: "Seconds" activerecord: errors: -- cgit v1.2.3 From aa002c0e86afdc83693f14667a710107843f0fbd Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 22 Dec 2008 11:31:18 -0600 Subject: ActiveRecord::QueryCache middleware --- actionpack/lib/action_controller/caching.rb | 3 +-- actionpack/lib/action_controller/caching/sql_cache.rb | 18 ------------------ actionpack/lib/action_controller/dispatcher.rb | 1 + 3 files changed, 2 insertions(+), 20 deletions(-) delete mode 100644 actionpack/lib/action_controller/caching/sql_cache.rb (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index b4d251eb3c..1d14df0052 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -27,7 +27,6 @@ module ActionController #:nodoc: autoload :Actions, 'action_controller/caching/actions' autoload :Fragments, 'action_controller/caching/fragments' autoload :Pages, 'action_controller/caching/pages' - autoload :SqlCache, 'action_controller/caching/sql_cache' autoload :Sweeping, 'action_controller/caching/sweeping' def self.included(base) #:nodoc: @@ -41,7 +40,7 @@ module ActionController #:nodoc: end include Pages, Actions, Fragments - include Sweeping, SqlCache if defined?(ActiveRecord) + include Sweeping if defined?(ActiveRecord) @@perform_caching = true cattr_accessor :perform_caching diff --git a/actionpack/lib/action_controller/caching/sql_cache.rb b/actionpack/lib/action_controller/caching/sql_cache.rb deleted file mode 100644 index 139be6100d..0000000000 --- a/actionpack/lib/action_controller/caching/sql_cache.rb +++ /dev/null @@ -1,18 +0,0 @@ -module ActionController #:nodoc: - module Caching - module SqlCache - def self.included(base) #:nodoc: - if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:cache) - base.alias_method_chain :perform_action, :caching - end - end - - protected - def perform_action_with_caching - ActiveRecord::Base.cache do - perform_action_without_caching - end - end - end - end -end \ No newline at end of file diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index e1eaaf7cbb..0cfd451c04 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -48,6 +48,7 @@ module ActionController !ActionController::Base.allow_concurrency } middleware.use "ActionController::Failsafe" + middleware.use "ActiveRecord::QueryCache" if defined?(ActiveRecord) ["ActionController::Session::CookieStore", "ActionController::Session::MemCacheStore", -- cgit v1.2.3 From 0b22a96b7aa39cb7244d7cee23f3d03b6117b447 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 22 Dec 2008 12:04:32 -0600 Subject: Move default middleware stack to middlewares.rb --- actionpack/lib/action_controller/dispatcher.rb | 19 ++----------------- actionpack/lib/action_controller/middlewares.rb | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 actionpack/lib/action_controller/middlewares.rb (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index 0cfd451c04..ae9f117e3f 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -44,23 +44,8 @@ module ActionController cattr_accessor :middleware self.middleware = MiddlewareStack.new do |middleware| - middleware.use "ActionController::Lock", :if => lambda { - !ActionController::Base.allow_concurrency - } - middleware.use "ActionController::Failsafe" - middleware.use "ActiveRecord::QueryCache" if defined?(ActiveRecord) - - ["ActionController::Session::CookieStore", - "ActionController::Session::MemCacheStore", - "ActiveRecord::SessionStore"].each do |store| - middleware.use(store, ActionController::Base.session_options, - :if => lambda { - if session_store = ActionController::Base.session_store - session_store.name == store - end - } - ) - end + middlewares = File.join(File.dirname(__FILE__), "middlewares.rb") + middleware.instance_eval(File.read(middlewares)) end include ActiveSupport::Callbacks diff --git a/actionpack/lib/action_controller/middlewares.rb b/actionpack/lib/action_controller/middlewares.rb new file mode 100644 index 0000000000..e566c6fef9 --- /dev/null +++ b/actionpack/lib/action_controller/middlewares.rb @@ -0,0 +1,19 @@ +use "ActionController::Lock", :if => lambda { + !ActionController::Base.allow_concurrency +} + +use "ActionController::Failsafe" + +use "ActiveRecord::QueryCache", :if => lambda { defined?(ActiveRecord) } + +["ActionController::Session::CookieStore", + "ActionController::Session::MemCacheStore", + "ActiveRecord::SessionStore"].each do |store| + use(store, ActionController::Base.session_options, + :if => lambda { + if session_store = ActionController::Base.session_store + session_store.name == store + end + } + ) +end -- cgit v1.2.3 From faf8364050c0a3925a8b2af85b6b5c9e94090986 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 22 Dec 2008 16:58:48 -0600 Subject: Defining a new method is atomic, no mutex needed. --- actionpack/lib/action_view/renderable.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb index 4a5b36d70a..d8e72f1179 100644 --- a/actionpack/lib/action_view/renderable.rb +++ b/actionpack/lib/action_view/renderable.rb @@ -4,10 +4,6 @@ module ActionView module Renderable #:nodoc: extend ActiveSupport::Memoizable - def self.included(base) - @@mutex = Mutex.new - end - def filename 'compiled-template' end @@ -64,10 +60,8 @@ module ActionView def compile(local_assigns) render_symbol = method_name(local_assigns) - @@mutex.synchronize do - if recompile?(render_symbol) - compile!(render_symbol, local_assigns) - end + if recompile?(render_symbol) + compile!(render_symbol, local_assigns) end end -- cgit v1.2.3 From 900aad677f5bf06dc3a3ad42e68b582550f3b08a Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 22 Dec 2008 22:03:14 +0000 Subject: Remove deprecated relative_url_root --- actionpack/lib/action_controller/request.rb | 7 ------- 1 file changed, 7 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index 087fffe87d..cc079792bb 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -9,13 +9,6 @@ module ActionController class AbstractRequest extend ActiveSupport::Memoizable - def self.relative_url_root=(relative_url_root) - ActiveSupport::Deprecation.warn( - "ActionController::AbstractRequest.relative_url_root= has been renamed." + - "You can now set it with config.action_controller.relative_url_root=", caller) - ActionController::Base.relative_url_root=relative_url_root - end - HTTP_METHODS = %w(get head put post delete options) HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h } -- cgit v1.2.3 From 408ec6c0dcb901b2432c72bd3253fa691c5aede0 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 22 Dec 2008 22:15:53 +0000 Subject: Remove rack_process.rb --- actionpack/lib/action_controller.rb | 2 +- actionpack/lib/action_controller/rack_process.rb | 73 ------------------------ actionpack/lib/action_controller/request.rb | 72 +++++++++++++++++++++++ 3 files changed, 73 insertions(+), 74 deletions(-) delete mode 100644 actionpack/lib/action_controller/rack_process.rb (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index ae947820b4..a69ef42954 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -59,7 +59,7 @@ module ActionController autoload :MiddlewareStack, 'action_controller/middleware_stack' autoload :MimeResponds, 'action_controller/mime_responds' autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes' - autoload :RackRequest, 'action_controller/rack_process' + autoload :RackRequest, 'action_controller/request' autoload :RecordIdentifier, 'action_controller/record_identifier' autoload :Response, 'action_controller/response' autoload :RequestForgeryProtection, 'action_controller/request_forgery_protection' diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb deleted file mode 100644 index 8c6db91dd0..0000000000 --- a/actionpack/lib/action_controller/rack_process.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'action_controller/cgi_ext' - -module ActionController #:nodoc: - class RackRequest < AbstractRequest #:nodoc: - attr_accessor :session_options - - class SessionFixationAttempt < StandardError #:nodoc: - end - - def initialize(env) - @env = env - super() - end - - %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO - PATH_TRANSLATED REMOTE_HOST - REMOTE_IDENT REMOTE_USER SCRIPT_NAME - SERVER_NAME SERVER_PROTOCOL - - HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING - HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM - HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env| - define_method(env.sub(/^HTTP_/n, '').downcase) do - @env[env] - end - end - - def query_string - qs = super - if !qs.blank? - qs - else - @env['QUERY_STRING'] - end - end - - def body_stream #:nodoc: - @env['rack.input'] - end - - def key?(key) - @env.key?(key) - end - - def cookies - Rack::Request.new(@env).cookies - end - - def server_port - @env['SERVER_PORT'].to_i - end - - def server_software - @env['SERVER_SOFTWARE'].split("/").first - end - - def session_options - @env['rack.session.options'] ||= {} - end - - def session_options=(options) - @env['rack.session.options'] = options - end - - def session - @env['rack.session'] ||= {} - end - - def reset_session - @env['rack.session'] = {} - end - end -end diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index cc079792bb..7c125df55a 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -3,6 +3,7 @@ require 'stringio' require 'strscan' require 'active_support/memoizable' +require 'action_controller/cgi_ext' module ActionController # CgiRequest and TestRequest provide concrete implementations. @@ -860,4 +861,75 @@ EOM class UploadedTempfile < Tempfile include UploadedFile end + + class RackRequest < AbstractRequest #:nodoc: + attr_accessor :session_options + + class SessionFixationAttempt < StandardError #:nodoc: + end + + def initialize(env) + @env = env + super() + end + + %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO + PATH_TRANSLATED REMOTE_HOST + REMOTE_IDENT REMOTE_USER SCRIPT_NAME + SERVER_NAME SERVER_PROTOCOL + + HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING + HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM + HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env| + define_method(env.sub(/^HTTP_/n, '').downcase) do + @env[env] + end + end + + def query_string + qs = super + if !qs.blank? + qs + else + @env['QUERY_STRING'] + end + end + + def body_stream #:nodoc: + @env['rack.input'] + end + + def key?(key) + @env.key?(key) + end + + def cookies + Rack::Request.new(@env).cookies + end + + def server_port + @env['SERVER_PORT'].to_i + end + + def server_software + @env['SERVER_SOFTWARE'].split("/").first + end + + def session_options + @env['rack.session.options'] ||= {} + end + + def session_options=(options) + @env['rack.session.options'] = options + end + + def session + @env['rack.session'] ||= {} + end + + def reset_session + @env['rack.session'] = {} + end + end + end -- cgit v1.2.3 From 7e1751111ecf3886eba313fef183d73ff1f07eb6 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 22 Dec 2008 22:36:38 +0000 Subject: Rename RackRequest to Request --- actionpack/lib/action_controller.rb | 4 ++-- actionpack/lib/action_controller/dispatcher.rb | 2 +- actionpack/lib/action_controller/integration.rb | 2 +- actionpack/lib/action_controller/request.rb | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index a69ef42954..8dc01ba792 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -38,7 +38,7 @@ module ActionController # TODO: Review explicit to see if they will automatically be handled by # the initilizer if they are really needed. def self.load_all! - [Base, CGIHandler, CgiRequest, RackRequest, RackRequest, Http::Headers, UrlRewriter, UrlWriter] + [Base, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter, UrlWriter] end autoload :AbstractRequest, 'action_controller/request' @@ -59,7 +59,7 @@ module ActionController autoload :MiddlewareStack, 'action_controller/middleware_stack' autoload :MimeResponds, 'action_controller/mime_responds' autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes' - autoload :RackRequest, 'action_controller/request' + autoload :Request, 'action_controller/request' autoload :RecordIdentifier, 'action_controller/record_identifier' autoload :Response, 'action_controller/response' autoload :RequestForgeryProtection, 'action_controller/request_forgery_protection' diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index ae9f117e3f..4dc76e1b49 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -83,7 +83,7 @@ module ActionController end def _call(env) - @request = RackRequest.new(env) + @request = Request.new(env) @response = Response.new dispatch end diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb index d952c3489b..701b464c99 100644 --- a/actionpack/lib/action_controller/integration.rb +++ b/actionpack/lib/action_controller/integration.rb @@ -371,7 +371,7 @@ module ActionController "SERVER_PORT" => https? ? "443" : "80", "HTTPS" => https? ? "on" : "off" } - UrlRewriter.new(RackRequest.new(env), {}) + UrlRewriter.new(Request.new(env), {}) end def name_with_prefix(prefix, name) diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index 7c125df55a..565a2d5d81 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -862,7 +862,7 @@ EOM include UploadedFile end - class RackRequest < AbstractRequest #:nodoc: + class Request < AbstractRequest #:nodoc: attr_accessor :session_options class SessionFixationAttempt < StandardError #:nodoc: -- cgit v1.2.3 From b5ecfe78f9fb3b06f4fec4815b5e79399e4993aa Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 22 Dec 2008 23:13:04 +0000 Subject: Use Rack::MockRequest for TestRequest --- .../lib/action_controller/assertions/routing_assertions.rb | 2 +- actionpack/lib/action_controller/request.rb | 13 +------------ actionpack/lib/action_controller/test_process.rb | 14 +++++++------- 3 files changed, 9 insertions(+), 20 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/assertions/routing_assertions.rb b/actionpack/lib/action_controller/assertions/routing_assertions.rb index 8a837c592c..5101751cea 100644 --- a/actionpack/lib/action_controller/assertions/routing_assertions.rb +++ b/actionpack/lib/action_controller/assertions/routing_assertions.rb @@ -134,7 +134,7 @@ module ActionController path = "/#{path}" unless path.first == '/' # Assume given controller - request = ActionController::TestRequest.new({}, {}, nil) + request = ActionController::TestRequest.new request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method request.path = path diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index 565a2d5d81..a3e96a0fc4 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -7,7 +7,7 @@ require 'action_controller/cgi_ext' module ActionController # CgiRequest and TestRequest provide concrete implementations. - class AbstractRequest + class AbstractRequest < Rack::Request extend ActiveSupport::Memoizable HTTP_METHODS = %w(get head put post delete options) @@ -424,7 +424,6 @@ EOM end alias referer referrer - def query_parameters @query_parameters ||= self.class.parse_query_parameters(query_string) end @@ -433,7 +432,6 @@ EOM @request_parameters ||= parse_formatted_request_parameters end - #-- # Must be implemented in the concrete request #++ @@ -868,11 +866,6 @@ EOM class SessionFixationAttempt < StandardError #:nodoc: end - def initialize(env) - @env = env - super() - end - %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO PATH_TRANSLATED REMOTE_HOST REMOTE_IDENT REMOTE_USER SCRIPT_NAME @@ -911,10 +904,6 @@ EOM @env['SERVER_PORT'].to_i end - def server_software - @env['SERVER_SOFTWARE'].split("/").first - end - def session_options @env['rack.session.options'] ||= {} end diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index 45dcf8b2c2..211e22ff58 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -27,20 +27,20 @@ module ActionController #:nodoc: alias_method_chain :process, :test end - class TestRequest < AbstractRequest #:nodoc: + class TestRequest < Request #:nodoc: attr_accessor :cookies, :session_options attr_accessor :query_parameters, :request_parameters, :path, :session attr_accessor :host, :user_agent - def initialize(query_parameters = nil, request_parameters = nil, session = nil) - @query_parameters = query_parameters || {} - @request_parameters = request_parameters || {} - @session = session || TestSession.new + def initialize + super(Rack::MockRequest.env_for('/')) + + @query_parameters = {} + @request_parameters = {} + @session = TestSession.new initialize_containers initialize_default_values - - super() end def reset_session -- cgit v1.2.3 From 293bb02f91390088890104335c76c51b8990cc49 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 23 Dec 2008 00:15:08 +0000 Subject: Unify ActionController::AbstractRequest and ActionController::Request --- actionpack/lib/action_controller/request.rb | 123 +++++++++++----------------- 1 file changed, 47 insertions(+), 76 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index a3e96a0fc4..71b5ebb1b3 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -7,9 +7,35 @@ require 'action_controller/cgi_ext' module ActionController # CgiRequest and TestRequest provide concrete implementations. - class AbstractRequest < Rack::Request + class Request extend ActiveSupport::Memoizable + class SessionFixationAttempt < StandardError #:nodoc: + end + + attr_reader :env + + def initialize(env) + @env = env + end + + %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO + PATH_TRANSLATED REMOTE_HOST + REMOTE_IDENT REMOTE_USER SCRIPT_NAME + SERVER_NAME SERVER_PROTOCOL + + HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING + HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM + HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env| + define_method(env.sub(/^HTTP_/n, '').downcase) do + @env[env] + end + end + + def key?(key) + @env.key?(key) + end + HTTP_METHODS = %w(get head put post delete options) HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h } @@ -242,7 +268,6 @@ EOM end memoize :server_software - # Returns the complete URL used for this request. def url protocol + host_with_port + request_uri @@ -326,11 +351,7 @@ EOM # Returns the query string, accounting for server idiosyncrasies. def query_string - if uri = @env['REQUEST_URI'] - uri.split('?', 2)[1] || '' - else - @env['QUERY_STRING'] || '' - end + @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].split('?', 2)[1] || '') end memoize :query_string @@ -432,24 +453,36 @@ EOM @request_parameters ||= parse_formatted_request_parameters end - #-- - # Must be implemented in the concrete request - #++ - def body_stream #:nodoc: + @env['rack.input'] end - def cookies #:nodoc: + def cookies + Rack::Request.new(@env).cookies end - def session #:nodoc: + def session + @env['rack.session'] ||= {} end def session=(session) #:nodoc: @session = session end - def reset_session #:nodoc: + def reset_session + @env['rack.session'] = {} + end + + def session_options + @env['rack.session.options'] ||= {} + end + + def session_options=(options) + @env['rack.session.options'] = options + end + + def server_port + @env['SERVER_PORT'].to_i end protected @@ -859,66 +892,4 @@ EOM class UploadedTempfile < Tempfile include UploadedFile end - - class Request < AbstractRequest #:nodoc: - attr_accessor :session_options - - class SessionFixationAttempt < StandardError #:nodoc: - end - - %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO - PATH_TRANSLATED REMOTE_HOST - REMOTE_IDENT REMOTE_USER SCRIPT_NAME - SERVER_NAME SERVER_PROTOCOL - - HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING - HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM - HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env| - define_method(env.sub(/^HTTP_/n, '').downcase) do - @env[env] - end - end - - def query_string - qs = super - if !qs.blank? - qs - else - @env['QUERY_STRING'] - end - end - - def body_stream #:nodoc: - @env['rack.input'] - end - - def key?(key) - @env.key?(key) - end - - def cookies - Rack::Request.new(@env).cookies - end - - def server_port - @env['SERVER_PORT'].to_i - end - - def session_options - @env['rack.session.options'] ||= {} - end - - def session_options=(options) - @env['rack.session.options'] = options - end - - def session - @env['rack.session'] ||= {} - end - - def reset_session - @env['rack.session'] = {} - end - end - end -- cgit v1.2.3 From 3562d54d18bf6c87384436c63383666617a2a1eb Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 23 Dec 2008 00:36:13 +0000 Subject: Remove duplicate attr_reader :env --- actionpack/lib/action_controller/request.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index 71b5ebb1b3..2cad7bc84c 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -13,6 +13,8 @@ module ActionController class SessionFixationAttempt < StandardError #:nodoc: end + # The hash of environment variables for this request, + # such as { 'RAILS_ENV' => 'production' }. attr_reader :env def initialize(env) @@ -39,10 +41,6 @@ module ActionController HTTP_METHODS = %w(get head put post delete options) HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h } - # The hash of environment variables for this request, - # such as { 'RAILS_ENV' => 'production' }. - attr_reader :env - # The true HTTP request \method as a lowercase symbol, such as :get. # UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS. def request_method -- cgit v1.2.3 From 9c1e48eaea921efa67fbeed1ff1876dc710f8fd2 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Tue, 23 Dec 2008 13:36:05 -0600 Subject: ActionController::VerbPiggybacking middleware --- actionpack/lib/action_controller.rb | 1 + actionpack/lib/action_controller/integration.rb | 11 ++++++++++ actionpack/lib/action_controller/middlewares.rb | 2 ++ actionpack/lib/action_controller/request.rb | 20 ++++++++---------- .../lib/action_controller/verb_piggybacking.rb | 24 ++++++++++++++++++++++ 5 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 actionpack/lib/action_controller/verb_piggybacking.rb (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 8dc01ba792..3bb755376f 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -74,6 +74,7 @@ module ActionController autoload :Translation, 'action_controller/translation' autoload :UrlRewriter, 'action_controller/url_rewriter' autoload :UrlWriter, 'action_controller/url_rewriter' + autoload :VerbPiggybacking, 'action_controller/verb_piggybacking' autoload :Verification, 'action_controller/verification' module Assertions diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb index 701b464c99..71e2524e81 100644 --- a/actionpack/lib/action_controller/integration.rb +++ b/actionpack/lib/action_controller/integration.rb @@ -2,6 +2,17 @@ require 'stringio' require 'uri' require 'active_support/test_case' +# Monkey patch Rack::Lint to support rewind +module Rack + class Lint + class InputWrapper + def rewind + @input.rewind + end + end + end +end + module ActionController module Integration #:nodoc: # An integration Session instance represents a set of requests and responses diff --git a/actionpack/lib/action_controller/middlewares.rb b/actionpack/lib/action_controller/middlewares.rb index e566c6fef9..793739723f 100644 --- a/actionpack/lib/action_controller/middlewares.rb +++ b/actionpack/lib/action_controller/middlewares.rb @@ -17,3 +17,5 @@ use "ActiveRecord::QueryCache", :if => lambda { defined?(ActiveRecord) } } ) end + +use ActionController::VerbPiggybacking diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index 2cad7bc84c..d9eb5af849 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -45,8 +45,6 @@ module ActionController # UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS. def request_method method = @env['REQUEST_METHOD'] - method = parameters[:_method] if method == 'POST' && !parameters[:_method].blank? - HTTP_METHOD_LOOKUP[method] || raise(UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}") end memoize :request_method @@ -143,15 +141,15 @@ module ActionController # supplied, both must match, or the request is not considered fresh. def fresh?(response) case - when if_modified_since && if_none_match - not_modified?(response.last_modified) && etag_matches?(response.etag) - when if_modified_since - not_modified?(response.last_modified) - when if_none_match - etag_matches?(response.etag) - else - false - end + when if_modified_since && if_none_match + not_modified?(response.last_modified) && etag_matches?(response.etag) + when if_modified_since + not_modified?(response.last_modified) + when if_none_match + etag_matches?(response.etag) + else + false + end end # Returns the Mime type for the \format used in the request. diff --git a/actionpack/lib/action_controller/verb_piggybacking.rb b/actionpack/lib/action_controller/verb_piggybacking.rb new file mode 100644 index 0000000000..86cde304a0 --- /dev/null +++ b/actionpack/lib/action_controller/verb_piggybacking.rb @@ -0,0 +1,24 @@ +module ActionController + # TODO: Use Rack::MethodOverride when it is released + class VerbPiggybacking + HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS) + + def initialize(app) + @app = app + end + + def call(env) + if env["REQUEST_METHOD"] == "POST" + req = Request.new(env) + if method = (req.parameters[:_method] || env["HTTP_X_HTTP_METHOD_OVERRIDE"]) + method = method.to_s.upcase + if HTTP_METHODS.include?(method) + env["REQUEST_METHOD"] = method + end + end + end + + @app.call(env) + end + end +end -- cgit v1.2.3 From e898f82a743063652aed802d99ea8b5deac2ec3c Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 25 Dec 2008 03:51:04 +0000 Subject: Move request parsing related code to ActionController::RequestParser --- actionpack/lib/action_controller.rb | 4 + actionpack/lib/action_controller/request.rb | 423 +-------------------- actionpack/lib/action_controller/request_parser.rb | 314 +++++++++++++++ actionpack/lib/action_controller/test_process.rb | 31 +- actionpack/lib/action_controller/uploaded_file.rb | 37 ++ .../action_controller/url_encoded_pair_parser.rb | 95 +++++ 6 files changed, 478 insertions(+), 426 deletions(-) create mode 100644 actionpack/lib/action_controller/request_parser.rb create mode 100644 actionpack/lib/action_controller/uploaded_file.rb create mode 100644 actionpack/lib/action_controller/url_encoded_pair_parser.rb (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 3bb755376f..98fb490d64 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -60,6 +60,10 @@ module ActionController autoload :MimeResponds, 'action_controller/mime_responds' autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes' autoload :Request, 'action_controller/request' + autoload :RequestParser, 'action_controller/request_parser' + autoload :UrlEncodedPairParser, 'action_controller/url_encoded_pair_parser' + autoload :UploadedStringIO, 'action_controller/uploaded_file' + autoload :UploadedTempfile, 'action_controller/uploaded_file' autoload :RecordIdentifier, 'action_controller/record_identifier' autoload :Response, 'action_controller/response' autoload :RequestForgeryProtection, 'action_controller/request_forgery_protection' diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index d9eb5af849..8a02130d88 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -101,7 +101,7 @@ module ActionController # For backward compatibility, the post \format is extracted from the # X-Post-Data-Format HTTP header if present. def content_type - Mime::Type.lookup(content_type_without_parameters) + Mime::Type.lookup(parser.content_type_without_parameters) end memoize :content_type @@ -389,11 +389,7 @@ EOM # Read the request \body. This is useful for web services that need to # work with raw requests directly. def raw_post - unless env.include? 'RAW_POST_DATA' - env['RAW_POST_DATA'] = body.read(content_length) - body.rewind if body.respond_to?(:rewind) - end - env['RAW_POST_DATA'] + parser.raw_post end # Returns both GET and POST \parameters in a single hash. @@ -421,15 +417,8 @@ EOM @path_parameters ||= {} end - # The request body is an IO input stream. If the RAW_POST_DATA environment - # variable is already set, wrap it in a StringIO. def body - if raw_post = env['RAW_POST_DATA'] - raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding) - StringIO.new(raw_post) - else - body_stream - end + parser.body end def remote_addr @@ -442,11 +431,11 @@ EOM alias referer referrer def query_parameters - @query_parameters ||= self.class.parse_query_parameters(query_string) + @query_parameters ||= parser.query_parameters end def request_parameters - @request_parameters ||= parse_formatted_request_parameters + @request_parameters ||= parser.request_parameters end def body_stream #:nodoc: @@ -481,411 +470,13 @@ EOM @env['SERVER_PORT'].to_i end - protected - # The raw content type string. Use when you need parameters such as - # charset or boundary which aren't included in the content_type MIME type. - # Overridden by the X-POST_DATA_FORMAT header for backward compatibility. - def content_type_with_parameters - content_type_from_legacy_post_data_format_header || - env['CONTENT_TYPE'].to_s - end - - # The raw content type string with its parameters stripped off. - def content_type_without_parameters - self.class.extract_content_type_without_parameters(content_type_with_parameters) - end - memoize :content_type_without_parameters - private - def content_type_from_legacy_post_data_format_header - if x_post_format = @env['HTTP_X_POST_DATA_FORMAT'] - case x_post_format.to_s.downcase - when 'yaml'; 'application/x-yaml' - when 'xml'; 'application/xml' - end - end - end - - def parse_formatted_request_parameters - return {} if content_length.zero? - - content_type, boundary = self.class.extract_multipart_boundary(content_type_with_parameters) - - # Don't parse params for unknown requests. - return {} if content_type.blank? - - mime_type = Mime::Type.lookup(content_type) - strategy = ActionController::Base.param_parsers[mime_type] - - # Only multipart form parsing expects a stream. - body = (strategy && strategy != :multipart_form) ? raw_post : self.body - - case strategy - when Proc - strategy.call(body) - when :url_encoded_form - self.class.clean_up_ajax_request_body! body - self.class.parse_query_parameters(body) - when :multipart_form - self.class.parse_multipart_form_parameters(body, boundary, content_length, env) - when :xml_simple, :xml_node - body.blank? ? {} : Hash.from_xml(body).with_indifferent_access - when :yaml - YAML.load(body) - when :json - if body.blank? - {} - else - data = ActiveSupport::JSON.decode(body) - data = {:_json => data} unless data.is_a?(Hash) - data.with_indifferent_access - end - else - {} - end - rescue Exception => e # YAML, XML or Ruby code block errors - raise - { "body" => body, - "content_type" => content_type_with_parameters, - "content_length" => content_length, - "exception" => "#{e.message} (#{e.class})", - "backtrace" => e.backtrace } - end - def named_host?(host) !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host)) end - class << self - def parse_query_parameters(query_string) - return {} if query_string.blank? - - pairs = query_string.split('&').collect do |chunk| - next if chunk.empty? - key, value = chunk.split('=', 2) - next if key.empty? - value = value.nil? ? nil : CGI.unescape(value) - [ CGI.unescape(key), value ] - end.compact - - UrlEncodedPairParser.new(pairs).result - end - - def parse_request_parameters(params) - parser = UrlEncodedPairParser.new - - params = params.dup - until params.empty? - for key, value in params - if key.blank? - params.delete key - elsif !key.include?('[') - # much faster to test for the most common case first (GET) - # and avoid the call to build_deep_hash - parser.result[key] = get_typed_value(value[0]) - params.delete key - elsif value.is_a?(Array) - parser.parse(key, get_typed_value(value.shift)) - params.delete key if value.empty? - else - raise TypeError, "Expected array, found #{value.inspect}" - end - end - end - - parser.result - end - - def parse_multipart_form_parameters(body, boundary, body_size, env) - parse_request_parameters(read_multipart(body, boundary, body_size, env)) - end - - def extract_multipart_boundary(content_type_with_parameters) - if content_type_with_parameters =~ MULTIPART_BOUNDARY - ['multipart/form-data', $1.dup] - else - extract_content_type_without_parameters(content_type_with_parameters) - end - end - - def extract_content_type_without_parameters(content_type_with_parameters) - $1.strip.downcase if content_type_with_parameters =~ /^([^,\;]*)/ - end - - def clean_up_ajax_request_body!(body) - body.chop! if body[-1] == 0 - body.gsub!(/&_=$/, '') - end - - - private - def get_typed_value(value) - case value - when String - value - when NilClass - '' - when Array - value.map { |v| get_typed_value(v) } - else - if value.respond_to? :original_filename - # Uploaded file - if value.original_filename - value - # Multipart param - else - result = value.read - value.rewind - result - end - # Unknown value, neither string nor multipart. - else - raise "Unknown form value: #{value.inspect}" - end - end - end - - MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n - - EOL = "\015\012" - - def read_multipart(body, boundary, body_size, env) - params = Hash.new([]) - boundary = "--" + boundary - quoted_boundary = Regexp.quote(boundary) - buf = "" - bufsize = 10 * 1024 - boundary_end="" - - # start multipart/form-data - body.binmode if defined? body.binmode - case body - when File - body.set_encoding(Encoding::BINARY) if body.respond_to?(:set_encoding) - when StringIO - body.string.force_encoding(Encoding::BINARY) if body.string.respond_to?(:force_encoding) - end - boundary_size = boundary.size + EOL.size - body_size -= boundary_size - status = body.read(boundary_size) - if nil == status - raise EOFError, "no content body" - elsif boundary + EOL != status - raise EOFError, "bad content body" - end - - loop do - head = nil - content = - if 10240 < body_size - UploadedTempfile.new("CGI") - else - UploadedStringIO.new - end - content.binmode if defined? content.binmode - - until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf) - - if (not head) and /#{EOL}#{EOL}/n.match(buf) - buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do - head = $1.dup - "" - end - next - end - - if head and ( (EOL + boundary + EOL).size < buf.size ) - content.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)] - buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = "" - end - - c = if bufsize < body_size - body.read(bufsize) - else - body.read(body_size) - end - if c.nil? || c.empty? - raise EOFError, "bad content body" - end - buf.concat(c) - body_size -= c.size - end - - buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do - content.print $1 - if "--" == $2 - body_size = -1 - end - boundary_end = $2.dup - "" - end - - content.rewind - - head =~ /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;]*))/ni - if filename = $1 || $2 - if /Mac/ni.match(env['HTTP_USER_AGENT']) and - /Mozilla/ni.match(env['HTTP_USER_AGENT']) and - (not /MSIE/ni.match(env['HTTP_USER_AGENT'])) - filename = CGI.unescape(filename) - end - content.original_path = filename.dup - end - - head =~ /Content-Type: ([^\r]*)/ni - content.content_type = $1.dup if $1 - - head =~ /Content-Disposition:.* name="?([^\";]*)"?/ni - name = $1.dup if $1 - - if params.has_key?(name) - params[name].push(content) - else - params[name] = [content] - end - break if body_size == -1 - end - raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/ - - begin - body.rewind if body.respond_to?(:rewind) - rescue Errno::ESPIPE - # Handles exceptions raised by input streams that cannot be rewound - # such as when using plain CGI under Apache - end - - params - end - end - end - - class UrlEncodedPairParser < StringScanner #:nodoc: - attr_reader :top, :parent, :result - - def initialize(pairs = []) - super('') - @result = {} - pairs.each { |key, value| parse(key, value) } - end - - KEY_REGEXP = %r{([^\[\]=&]+)} - BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]} - - # Parse the query string - def parse(key, value) - self.string = key - @top, @parent = result, nil - - # First scan the bare key - key = scan(KEY_REGEXP) or return - key = post_key_check(key) - - # Then scan as many nestings as present - until eos? - r = scan(BRACKETED_KEY_REGEXP) or return - key = self[1] - key = post_key_check(key) - end - - bind(key, value) - end - - private - # After we see a key, we must look ahead to determine our next action. Cases: - # - # [] follows the key. Then the value must be an array. - # = follows the key. (A value comes next) - # & or the end of string follows the key. Then the key is a flag. - # otherwise, a hash follows the key. - def post_key_check(key) - if scan(/\[\]/) # a[b][] indicates that b is an array - container(key, Array) - nil - elsif check(/\[[^\]]/) # a[b] indicates that a is a hash - container(key, Hash) - nil - else # End of key? We do nothing. - key - end - end - - # Add a container to the stack. - def container(key, klass) - type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass) - value = bind(key, klass.new) - type_conflict! klass, value unless value.is_a?(klass) - push(value) - end - - # Push a value onto the 'stack', which is actually only the top 2 items. - def push(value) - @parent, @top = @top, value + def parser + @parser ||= ActionController::RequestParser.new(@env) end - - # Bind a key (which may be nil for items in an array) to the provided value. - def bind(key, value) - if top.is_a? Array - if key - if top[-1].is_a?(Hash) && ! top[-1].key?(key) - top[-1][key] = value - else - top << {key => value}.with_indifferent_access - push top.last - value = top[key] - end - else - top << value - end - elsif top.is_a? Hash - key = CGI.unescape(key) - parent << (@top = {}) if top.key?(key) && parent.is_a?(Array) - top[key] ||= value - return top[key] - else - raise ArgumentError, "Don't know what to do: top is #{top.inspect}" - end - - return value - end - - def type_conflict!(klass, value) - raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)" - end - end - - module UploadedFile - def self.included(base) - base.class_eval do - attr_accessor :original_path, :content_type - alias_method :local_path, :path - end - end - - # Take the basename of the upload's original filename. - # This handles the full Windows paths given by Internet Explorer - # (and perhaps other broken user agents) without affecting - # those which give the lone filename. - # The Windows regexp is adapted from Perl's File::Basename. - def original_filename - unless defined? @original_filename - @original_filename = - unless original_path.blank? - if original_path =~ /^(?:.*[:\\\/])?(.*)/m - $1 - else - File.basename original_path - end - end - end - @original_filename - end - end - - class UploadedStringIO < StringIO - include UploadedFile - end - - class UploadedTempfile < Tempfile - include UploadedFile end end diff --git a/actionpack/lib/action_controller/request_parser.rb b/actionpack/lib/action_controller/request_parser.rb new file mode 100644 index 0000000000..82ee4c84c4 --- /dev/null +++ b/actionpack/lib/action_controller/request_parser.rb @@ -0,0 +1,314 @@ +module ActionController + class RequestParser + def initialize(env) + @env = env + end + + def request_parameters + @request_parameters ||= parse_formatted_request_parameters + end + + def query_parameters + @query_parameters ||= self.class.parse_query_parameters(query_string) + end + + # Returns the query string, accounting for server idiosyncrasies. + def query_string + @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].split('?', 2)[1] || '') + end + + # The request body is an IO input stream. If the RAW_POST_DATA environment + # variable is already set, wrap it in a StringIO. + def body + if raw_post = @env['RAW_POST_DATA'] + raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding) + StringIO.new(raw_post) + else + @env['rack.input'] + end + end + + # The raw content type string with its parameters stripped off. + def content_type_without_parameters + self.class.extract_content_type_without_parameters(content_type_with_parameters) + end + + def raw_post + unless @env.include? 'RAW_POST_DATA' + @env['RAW_POST_DATA'] = body.read(content_length) + body.rewind if body.respond_to?(:rewind) + end + @env['RAW_POST_DATA'] + end + + private + + def parse_formatted_request_parameters + return {} if content_length.zero? + + content_type, boundary = self.class.extract_multipart_boundary(content_type_with_parameters) + + # Don't parse params for unknown requests. + return {} if content_type.blank? + + mime_type = Mime::Type.lookup(content_type) + strategy = ActionController::Base.param_parsers[mime_type] + + # Only multipart form parsing expects a stream. + body = (strategy && strategy != :multipart_form) ? raw_post : self.body + + case strategy + when Proc + strategy.call(body) + when :url_encoded_form + self.class.clean_up_ajax_request_body! body + self.class.parse_query_parameters(body) + when :multipart_form + self.class.parse_multipart_form_parameters(body, boundary, content_length, @env) + when :xml_simple, :xml_node + body.blank? ? {} : Hash.from_xml(body).with_indifferent_access + when :yaml + YAML.load(body) + when :json + if body.blank? + {} + else + data = ActiveSupport::JSON.decode(body) + data = {:_json => data} unless data.is_a?(Hash) + data.with_indifferent_access + end + else + {} + end + rescue Exception => e # YAML, XML or Ruby code block errors + raise + { "body" => body, + "content_type" => content_type_with_parameters, + "content_length" => content_length, + "exception" => "#{e.message} (#{e.class})", + "backtrace" => e.backtrace } + end + + def content_length + @content_length ||= @env['CONTENT_LENGTH'].to_i + end + + # The raw content type string. Use when you need parameters such as + # charset or boundary which aren't included in the content_type MIME type. + # Overridden by the X-POST_DATA_FORMAT header for backward compatibility. + def content_type_with_parameters + content_type_from_legacy_post_data_format_header || @env['CONTENT_TYPE'].to_s + end + + def content_type_from_legacy_post_data_format_header + if x_post_format = @env['HTTP_X_POST_DATA_FORMAT'] + case x_post_format.to_s.downcase + when 'yaml'; 'application/x-yaml' + when 'xml'; 'application/xml' + end + end + end + + class << self + def parse_query_parameters(query_string) + return {} if query_string.blank? + + pairs = query_string.split('&').collect do |chunk| + next if chunk.empty? + key, value = chunk.split('=', 2) + next if key.empty? + value = value.nil? ? nil : CGI.unescape(value) + [ CGI.unescape(key), value ] + end.compact + + UrlEncodedPairParser.new(pairs).result + end + + def parse_request_parameters(params) + parser = UrlEncodedPairParser.new + + params = params.dup + until params.empty? + for key, value in params + if key.blank? + params.delete key + elsif !key.include?('[') + # much faster to test for the most common case first (GET) + # and avoid the call to build_deep_hash + parser.result[key] = get_typed_value(value[0]) + params.delete key + elsif value.is_a?(Array) + parser.parse(key, get_typed_value(value.shift)) + params.delete key if value.empty? + else + raise TypeError, "Expected array, found #{value.inspect}" + end + end + end + + parser.result + end + + def parse_multipart_form_parameters(body, boundary, body_size, env) + parse_request_parameters(read_multipart(body, boundary, body_size, env)) + end + + def extract_multipart_boundary(content_type_with_parameters) + if content_type_with_parameters =~ MULTIPART_BOUNDARY + ['multipart/form-data', $1.dup] + else + extract_content_type_without_parameters(content_type_with_parameters) + end + end + + def extract_content_type_without_parameters(content_type_with_parameters) + $1.strip.downcase if content_type_with_parameters =~ /^([^,\;]*)/ + end + + def clean_up_ajax_request_body!(body) + body.chop! if body[-1] == 0 + body.gsub!(/&_=$/, '') + end + + + private + def get_typed_value(value) + case value + when String + value + when NilClass + '' + when Array + value.map { |v| get_typed_value(v) } + else + if value.respond_to? :original_filename + # Uploaded file + if value.original_filename + value + # Multipart param + else + result = value.read + value.rewind + result + end + # Unknown value, neither string nor multipart. + else + raise "Unknown form value: #{value.inspect}" + end + end + end + + MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n + + EOL = "\015\012" + + def read_multipart(body, boundary, body_size, env) + params = Hash.new([]) + boundary = "--" + boundary + quoted_boundary = Regexp.quote(boundary) + buf = "" + bufsize = 10 * 1024 + boundary_end="" + + # start multipart/form-data + body.binmode if defined? body.binmode + case body + when File + body.set_encoding(Encoding::BINARY) if body.respond_to?(:set_encoding) + when StringIO + body.string.force_encoding(Encoding::BINARY) if body.string.respond_to?(:force_encoding) + end + boundary_size = boundary.size + EOL.size + body_size -= boundary_size + status = body.read(boundary_size) + if nil == status + raise EOFError, "no content body" + elsif boundary + EOL != status + raise EOFError, "bad content body" + end + + loop do + head = nil + content = + if 10240 < body_size + UploadedTempfile.new("CGI") + else + UploadedStringIO.new + end + content.binmode if defined? content.binmode + + until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf) + + if (not head) and /#{EOL}#{EOL}/n.match(buf) + buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do + head = $1.dup + "" + end + next + end + + if head and ( (EOL + boundary + EOL).size < buf.size ) + content.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)] + buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = "" + end + + c = if bufsize < body_size + body.read(bufsize) + else + body.read(body_size) + end + if c.nil? || c.empty? + raise EOFError, "bad content body" + end + buf.concat(c) + body_size -= c.size + end + + buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do + content.print $1 + if "--" == $2 + body_size = -1 + end + boundary_end = $2.dup + "" + end + + content.rewind + + head =~ /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;]*))/ni + if filename = $1 || $2 + if /Mac/ni.match(env['HTTP_USER_AGENT']) and + /Mozilla/ni.match(env['HTTP_USER_AGENT']) and + (not /MSIE/ni.match(env['HTTP_USER_AGENT'])) + filename = CGI.unescape(filename) + end + content.original_path = filename.dup + end + + head =~ /Content-Type: ([^\r]*)/ni + content.content_type = $1.dup if $1 + + head =~ /Content-Disposition:.* name="?([^\";]*)"?/ni + name = $1.dup if $1 + + if params.has_key?(name) + params[name].push(content) + else + params[name] = [content] + end + break if body_size == -1 + end + raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/ + + begin + body.rewind if body.respond_to?(:rewind) + rescue Errno::ESPIPE + # Handles exceptions raised by input streams that cannot be rewound + # such as when using plain CGI under Apache + end + + params + end + end # class << self + end +end diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index 211e22ff58..dddad1756a 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -29,18 +29,21 @@ module ActionController #:nodoc: class TestRequest < Request #:nodoc: attr_accessor :cookies, :session_options - attr_accessor :query_parameters, :request_parameters, :path, :session - attr_accessor :host, :user_agent + attr_accessor :query_parameters, :path, :session + attr_accessor :host def initialize - super(Rack::MockRequest.env_for('/')) + env = Rack::MockRequest.env_for("/") + + # TODO: Fix Request to assume env['SERVER_ADDR'] doesn't contain port number + env['SERVER_ADDR'] = env.delete("SERVER_NAME") + super(env) @query_parameters = {} - @request_parameters = {} @session = TestSession.new - initialize_containers initialize_default_values + initialize_containers end def reset_session @@ -55,7 +58,11 @@ module ActionController #:nodoc: # Either the RAW_POST_DATA environment variable or the URL-encoded request # parameters. def raw_post - env['RAW_POST_DATA'] ||= returning(url_encoded_request_parameters) { |b| b.force_encoding(Encoding::BINARY) if b.respond_to?(:force_encoding) } + @env['RAW_POST_DATA'] ||= begin + data = url_encoded_request_parameters + data.force_encoding(Encoding::BINARY) if data.respond_to?(:force_encoding) + data + end end def port=(number) @@ -125,26 +132,30 @@ module ActionController #:nodoc: path_parameters[key.to_s] = value end end + raw_post # populate env['RAW_POST_DATA'] @parameters = nil # reset TestRequest#parameters to use the new path_parameters end def recycle! - self.request_parameters = {} self.query_parameters = {} self.path_parameters = {} unmemoize_all end + def user_agent=(user_agent) + @env['HTTP_USER_AGENT'] = user_agent + end + private def initialize_containers - @env, @cookies = {}, {} + @cookies = {} end def initialize_default_values @host = "test.host" @request_uri = "/" - @user_agent = "Rails Testing" - self.remote_addr = "0.0.0.0" + @env['HTTP_USER_AGENT'] = "Rails Testing" + @env['REMOTE_ADDR'] = "0.0.0.0" @env["SERVER_PORT"] = 80 @env['REQUEST_METHOD'] = "GET" end diff --git a/actionpack/lib/action_controller/uploaded_file.rb b/actionpack/lib/action_controller/uploaded_file.rb new file mode 100644 index 0000000000..ea4845c68f --- /dev/null +++ b/actionpack/lib/action_controller/uploaded_file.rb @@ -0,0 +1,37 @@ +module ActionController + module UploadedFile + def self.included(base) + base.class_eval do + attr_accessor :original_path, :content_type + alias_method :local_path, :path + end + end + + # Take the basename of the upload's original filename. + # This handles the full Windows paths given by Internet Explorer + # (and perhaps other broken user agents) without affecting + # those which give the lone filename. + # The Windows regexp is adapted from Perl's File::Basename. + def original_filename + unless defined? @original_filename + @original_filename = + unless original_path.blank? + if original_path =~ /^(?:.*[:\\\/])?(.*)/m + $1 + else + File.basename original_path + end + end + end + @original_filename + end + end + + class UploadedStringIO < StringIO + include UploadedFile + end + + class UploadedTempfile < Tempfile + include UploadedFile + end +end diff --git a/actionpack/lib/action_controller/url_encoded_pair_parser.rb b/actionpack/lib/action_controller/url_encoded_pair_parser.rb new file mode 100644 index 0000000000..bea96c711d --- /dev/null +++ b/actionpack/lib/action_controller/url_encoded_pair_parser.rb @@ -0,0 +1,95 @@ +module ActionController + class UrlEncodedPairParser < StringScanner #:nodoc: + attr_reader :top, :parent, :result + + def initialize(pairs = []) + super('') + @result = {} + pairs.each { |key, value| parse(key, value) } + end + + KEY_REGEXP = %r{([^\[\]=&]+)} + BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]} + + # Parse the query string + def parse(key, value) + self.string = key + @top, @parent = result, nil + + # First scan the bare key + key = scan(KEY_REGEXP) or return + key = post_key_check(key) + + # Then scan as many nestings as present + until eos? + r = scan(BRACKETED_KEY_REGEXP) or return + key = self[1] + key = post_key_check(key) + end + + bind(key, value) + end + + private + # After we see a key, we must look ahead to determine our next action. Cases: + # + # [] follows the key. Then the value must be an array. + # = follows the key. (A value comes next) + # & or the end of string follows the key. Then the key is a flag. + # otherwise, a hash follows the key. + def post_key_check(key) + if scan(/\[\]/) # a[b][] indicates that b is an array + container(key, Array) + nil + elsif check(/\[[^\]]/) # a[b] indicates that a is a hash + container(key, Hash) + nil + else # End of key? We do nothing. + key + end + end + + # Add a container to the stack. + def container(key, klass) + type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass) + value = bind(key, klass.new) + type_conflict! klass, value unless value.is_a?(klass) + push(value) + end + + # Push a value onto the 'stack', which is actually only the top 2 items. + def push(value) + @parent, @top = @top, value + end + + # Bind a key (which may be nil for items in an array) to the provided value. + def bind(key, value) + if top.is_a? Array + if key + if top[-1].is_a?(Hash) && ! top[-1].key?(key) + top[-1][key] = value + else + top << {key => value}.with_indifferent_access + push top.last + value = top[key] + end + else + top << value + end + elsif top.is_a? Hash + key = CGI.unescape(key) + parent << (@top = {}) if top.key?(key) && parent.is_a?(Array) + top[key] ||= value + return top[key] + else + raise ArgumentError, "Don't know what to do: top is #{top.inspect}" + end + + return value + end + + def type_conflict!(klass, value) + raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)" + end + end +end \ No newline at end of file -- cgit v1.2.3 From 6e2a771661a47fb682108648244837f8616e350d Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 25 Dec 2008 17:54:44 +0000 Subject: Undry ActionController::TestCase# for better documentation --- actionpack/lib/action_controller/test_process.rb | 37 ++++++++++++++++-------- 1 file changed, 25 insertions(+), 12 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index dddad1756a..acfb10cdca 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -388,20 +388,33 @@ module ActionController #:nodoc: module TestProcess def self.included(base) - # execute the request simulating a specific HTTP method and set/volley the response - # TODO: this should be un-DRY'ed for the sake of API documentation. - %w( get post put delete head ).each do |method| - base.class_eval <<-EOV, __FILE__, __LINE__ - def #{method}(action, parameters = nil, session = nil, flash = nil) - @request.env['REQUEST_METHOD'] = "#{method.upcase}" if defined?(@request) - process(action, parameters, session, flash) - end - EOV + # Executes a request simulating GET HTTP method and set/volley the response + def get(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "GET") + end + + # Executes a request simulating POST HTTP method and set/volley the response + def post(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "POST") + end + + # Executes a request simulating PUT HTTP method and set/volley the response + def put(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "PUT") + end + + # Executes a request simulating DELETE HTTP method and set/volley the response + def delete(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "DELETE") + end + + # Executes a request simulating HEAD HTTP method and set/volley the response + def head(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "HEAD") end end - # execute the request and set/volley the response - def process(action, parameters = nil, session = nil, flash = nil) + def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET') # Sanity check for required instance variables so we can give an # understandable error message. %w(@controller @request @response).each do |iv_name| @@ -414,7 +427,7 @@ module ActionController #:nodoc: @response.recycle! @html_document = nil - @request.env['REQUEST_METHOD'] ||= "GET" + @request.env['REQUEST_METHOD'] = http_method @request.action = action.to_s -- cgit v1.2.3 From dd0753458f2a16c876c52734f84a242f56746607 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 25 Dec 2008 20:45:59 +0000 Subject: Move ActionController::Base#render arguments validation to a separate method --- actionpack/lib/action_controller/base.rb | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 4d4793c4e3..552075025f 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -859,16 +859,12 @@ module ActionController #:nodoc: def render(options = nil, extra_options = {}, &block) #:doc: raise DoubleRenderError, "Can only render or redirect once per action" if performed? + validate_render_arguments(options, extra_options) + if options.nil? return render(:file => default_template, :layout => true) - elsif !extra_options.is_a?(Hash) - raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}" - else - if options == :update - options = extra_options.merge({ :update => true }) - elsif !options.is_a?(Hash) - raise RenderError, "You called render with invalid options : #{options.inspect}" - end + elsif options == :update + options = extra_options.merge({ :update => true }) end layout = pick_layout(options) @@ -1186,6 +1182,16 @@ module ActionController #:nodoc: end end + def validate_render_arguments(options, extra_options) + if options && options != :update && !options.is_a?(Hash) + raise RenderError, "You called render with invalid options : #{options.inspect}" + end + + if !extra_options.is_a?(Hash) + raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}" + end + end + def initialize_template_class(response) response.template = ActionView::Base.new(self.class.view_paths, {}, self) response.template.helpers.send :include, self.class.master_helper_module -- cgit v1.2.3 From 061952392afd1dae1aa97a816e9a0c79df7c4514 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 25 Dec 2008 21:27:56 +0000 Subject: Make ActionController#render(string) work as a shortcut for render :file => string. [#1435] Examples: # Instead of render(:file => '/Users/lifo/home.html.erb') render('/Users/lifo/home.html.erb') Note : Filename must begin with a forward slash ('/') --- actionpack/lib/action_controller/base.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 552075025f..9bf044b6c0 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -865,6 +865,13 @@ module ActionController #:nodoc: return render(:file => default_template, :layout => true) elsif options == :update options = extra_options.merge({ :update => true }) + elsif options.is_a?(String) + case options.index('/') + when 0 + extra_options[:file] = options + end + + options = extra_options end layout = pick_layout(options) @@ -1183,7 +1190,7 @@ module ActionController #:nodoc: end def validate_render_arguments(options, extra_options) - if options && options != :update && !options.is_a?(Hash) + if options && options != :update && !options.is_a?(String) && !options.is_a?(Hash) raise RenderError, "You called render with invalid options : #{options.inspect}" end -- cgit v1.2.3 From d67e03871eabb912434dafac3eeb8e6ea7c5585f Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 25 Dec 2008 22:11:06 +0000 Subject: Make ActionController#render(string) work as a shortcut for render :template => string. [#1435] Examples: # Instead of render(:template => 'controller/action') render('controller/action') Note : Argument must not begin with a '/', but have at least one '/' --- actionpack/lib/action_controller/base.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 9bf044b6c0..29f1c84f03 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -866,9 +866,11 @@ module ActionController #:nodoc: elsif options == :update options = extra_options.merge({ :update => true }) elsif options.is_a?(String) - case options.index('/') + case position = options.index('/') when 0 extra_options[:file] = options + else + extra_options[:template] = options end options = extra_options -- cgit v1.2.3 From cd1d6e8768ae13b11bc343701037b20ad35e6f1e Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 25 Dec 2008 23:01:17 +0000 Subject: Make ActionController#render(string) work as a shortcut for render :action => string. [#1435] Examples: # Instead of render(:action => 'other_action') render('other_action') Note : Argument must not have any '/' --- actionpack/lib/action_controller/base.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 29f1c84f03..e9c96b0ba4 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -866,9 +866,11 @@ module ActionController #:nodoc: elsif options == :update options = extra_options.merge({ :update => true }) elsif options.is_a?(String) - case position = options.index('/') + case options.index('/') when 0 extra_options[:file] = options + when nil + extra_options[:action] = options else extra_options[:template] = options end -- cgit v1.2.3 From 80307c8b0a889acc7abb7f4e52fd4c02e1063ba8 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 26 Dec 2008 01:03:18 +0000 Subject: Make ActionController#render(symbol) behave same as ActionController#render(string) [#1435] --- actionpack/lib/action_controller/base.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index e9c96b0ba4..cb654534af 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -859,14 +859,14 @@ module ActionController #:nodoc: def render(options = nil, extra_options = {}, &block) #:doc: raise DoubleRenderError, "Can only render or redirect once per action" if performed? - validate_render_arguments(options, extra_options) + validate_render_arguments(options, extra_options, block_given?) if options.nil? return render(:file => default_template, :layout => true) elsif options == :update options = extra_options.merge({ :update => true }) - elsif options.is_a?(String) - case options.index('/') + elsif options.is_a?(String) || options.is_a?(Symbol) + case options.to_s.index('/') when 0 extra_options[:file] = options when nil @@ -1193,8 +1193,8 @@ module ActionController #:nodoc: end end - def validate_render_arguments(options, extra_options) - if options && options != :update && !options.is_a?(String) && !options.is_a?(Hash) + def validate_render_arguments(options, extra_options, has_block) + if options && (has_block && options != :update) && !options.is_a?(String) && !options.is_a?(Hash) && !options.is_a?(Symbol) raise RenderError, "You called render with invalid options : #{options.inspect}" end -- cgit v1.2.3 From 07298fd0929ae1c6dd6d1b41bf320112d6bfc6a0 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 26 Dec 2008 01:49:14 +0000 Subject: Don't recurse when ActionController#render is called without any arguments --- actionpack/lib/action_controller/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index cb654534af..5b83494eb4 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -862,7 +862,7 @@ module ActionController #:nodoc: validate_render_arguments(options, extra_options, block_given?) if options.nil? - return render(:file => default_template, :layout => true) + options = { :template => default_template.filename, :layout => true } elsif options == :update options = extra_options.merge({ :update => true }) elsif options.is_a?(String) || options.is_a?(Symbol) -- cgit v1.2.3