aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
authorYehuda Katz <wycats@gmail.com>2009-01-30 10:53:14 -0800
committerYehuda Katz <wycats@gmail.com>2009-01-30 10:53:19 -0800
commitda10673e32718d6a0619bd0f4b4d3d796db86a1a (patch)
tree7bdb7868b0be65daec06ba729f68deccfe61a8bb /actionpack
parentb8fadd708b9850a77e1f64038763fffcff502499 (diff)
parented0e5640879fd42c00fc5900e0355a0ea1dcf2ad (diff)
downloadrails-da10673e32718d6a0619bd0f4b4d3d796db86a1a.tar.gz
rails-da10673e32718d6a0619bd0f4b4d3d796db86a1a.tar.bz2
rails-da10673e32718d6a0619bd0f4b4d3d796db86a1a.zip
Sync 'rails/rails/master'
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/CHANGELOG36
-rw-r--r--actionpack/MIT-LICENSE2
-rw-r--r--actionpack/lib/action_controller.rb6
-rw-r--r--actionpack/lib/action_controller/assertions/selector_assertions.rb23
-rw-r--r--actionpack/lib/action_controller/base.rb16
-rw-r--r--actionpack/lib/action_controller/caching/sweeping.rb4
-rw-r--r--actionpack/lib/action_controller/flash.rb12
-rw-r--r--actionpack/lib/action_controller/http_authentication.rb196
-rw-r--r--actionpack/lib/action_controller/layout.rb2
-rw-r--r--actionpack/lib/action_controller/lock.rb16
-rw-r--r--actionpack/lib/action_controller/middleware_stack.rb15
-rw-r--r--actionpack/lib/action_controller/middlewares.rb9
-rw-r--r--actionpack/lib/action_controller/params_parser.rb71
-rw-r--r--actionpack/lib/action_controller/rack_ext.rb25
-rw-r--r--actionpack/lib/action_controller/rack_ext/lock.rb21
-rw-r--r--actionpack/lib/action_controller/rack_ext/multipart.rb22
-rw-r--r--actionpack/lib/action_controller/rack_ext/parse_query.rb18
-rwxr-xr-xactionpack/lib/action_controller/request.rb80
-rw-r--r--actionpack/lib/action_controller/request_parser.rb315
-rw-r--r--actionpack/lib/action_controller/request_profiler.rb168
-rw-r--r--actionpack/lib/action_controller/rewindable_input.rb30
-rw-r--r--actionpack/lib/action_controller/routing.rb5
-rw-r--r--actionpack/lib/action_controller/session/abstract_store.rb6
-rw-r--r--actionpack/lib/action_controller/session/cookie_store.rb10
-rw-r--r--actionpack/lib/action_controller/session_management.rb2
-rw-r--r--actionpack/lib/action_controller/test_process.rb67
-rw-r--r--actionpack/lib/action_controller/uploaded_file.rb7
-rw-r--r--actionpack/lib/action_controller/url_encoded_pair_parser.rb61
-rw-r--r--actionpack/lib/action_controller/url_rewriter.rb9
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/lib/action_view.rb2
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb124
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb59
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb45
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb2
-rw-r--r--actionpack/lib/action_view/locale/en.yml13
-rw-r--r--actionpack/lib/action_view/paths.rb9
-rw-r--r--actionpack/lib/action_view/template.rb58
-rw-r--r--actionpack/lib/action_view/template_handlers.rb6
-rw-r--r--actionpack/test/abstract_unit.rb4
-rw-r--r--actionpack/test/controller/http_authentication_test.rb54
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb88
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb130
-rw-r--r--actionpack/test/controller/middleware_stack_test.rb6
-rw-r--r--actionpack/test/controller/rack_test.rb28
-rw-r--r--actionpack/test/controller/render_test.rb8
-rw-r--r--actionpack/test/controller/request/multipart_params_parsing_test.rb21
-rw-r--r--actionpack/test/controller/request/url_encoded_params_parsing_test.rb12
-rw-r--r--actionpack/test/controller/request_test.rb58
-rw-r--r--actionpack/test/controller/session/cookie_store_test.rb40
-rw-r--r--actionpack/test/controller/session/test_session_test.rb58
-rw-r--r--actionpack/test/controller/test_test.rb23
-rw-r--r--actionpack/test/controller/url_rewriter_test.rb17
-rw-r--r--actionpack/test/fixtures/multipart/empty10
-rw-r--r--actionpack/test/fixtures/multipart/none9
-rw-r--r--actionpack/test/fixtures/replies.yml2
-rw-r--r--actionpack/test/fixtures/test/hello_world.da.html.erb1
-rw-r--r--actionpack/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb1
-rw-r--r--actionpack/test/template/date_helper_test.rb95
-rw-r--r--actionpack/test/template/form_options_helper_test.rb42
-rw-r--r--actionpack/test/template/number_helper_i18n_test.rb24
-rw-r--r--actionpack/test/template/render_test.rb15
-rw-r--r--actionpack/test/template/text_helper_test.rb23
64 files changed, 1446 insertions, 907 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 26e40e76d5..e9e18a8f6b 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,41 @@
*2.3.0 [Edge]*
+* Added grouped_options_for_select helper method for wrapping option tags in optgroups. #977 [Jon Crawford]
+
+* Implement HTTP Digest authentication. #1230 [Gregg Kellogg, Pratik Naik] Example :
+
+ class DummyDigestController < ActionController::Base
+ USERS = { "lifo" => 'world' }
+
+ before_filter :authenticate
+
+ def index
+ render :text => "Hello Secret"
+ end
+
+ private
+
+ def authenticate
+ authenticate_or_request_with_http_digest("Super Secret") do |username|
+ # Return the user's password
+ USERS[username]
+ end
+ end
+ end
+
+* Improved i18n support for the number_to_human_size helper. Changes the storage_units translation data; update your translations accordingly. #1634 [Yaroslav Markin]
+ storage_units:
+ # %u is the storage unit, %n is the number (default: 2 MB)
+ format: "%n %u"
+ units:
+ byte:
+ one: "Byte"
+ other: "Bytes"
+ kb: "KB"
+ mb: "MB"
+ gb: "GB"
+ tb: "TB"
+
* Added :silence option to BenchmarkHelper#benchmark and turned log_level into a hash parameter and deprecated the old use [DHH]
* Fixed the AssetTagHelper cache to use the computed asset host as part of the cache key instead of just assuming the its a string #1299 [DHH]
diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE
index 13c90d46e9..e7accc5ea1 100644
--- a/actionpack/MIT-LICENSE
+++ b/actionpack/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2008 David Heinemeier Hansson
+Copyright (c) 2004-2009 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index f808bdd910..3e77970367 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2008 David Heinemeier Hansson
+# Copyright (c) 2004-2009 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -56,14 +56,13 @@ module ActionController
autoload :Integration, 'action_controller/integration'
autoload :IntegrationTest, 'action_controller/integration'
autoload :Layout, 'action_controller/layout'
- autoload :Lock, 'action_controller/lock'
autoload :MiddlewareStack, 'action_controller/middleware_stack'
autoload :MimeResponds, 'action_controller/mime_responds'
+ autoload :ParamsParser, 'action_controller/params_parser'
autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes'
autoload :RecordIdentifier, 'action_controller/record_identifier'
autoload :Request, 'action_controller/request'
autoload :RequestForgeryProtection, 'action_controller/request_forgery_protection'
- autoload :RequestParser, 'action_controller/request_parser'
autoload :Rescue, 'action_controller/rescue'
autoload :Resources, 'action_controller/resources'
autoload :Response, 'action_controller/response'
@@ -75,6 +74,7 @@ module ActionController
autoload :TestCase, 'action_controller/test_case'
autoload :TestProcess, 'action_controller/test_process'
autoload :Translation, 'action_controller/translation'
+ autoload :UploadedFile, 'action_controller/uploaded_file'
autoload :UploadedStringIO, 'action_controller/uploaded_file'
autoload :UploadedTempfile, 'action_controller/uploaded_file'
autoload :UrlEncodedPairParser, 'action_controller/url_encoded_pair_parser'
diff --git a/actionpack/lib/action_controller/assertions/selector_assertions.rb b/actionpack/lib/action_controller/assertions/selector_assertions.rb
index 7f8fe9ab19..0d56ea5ef7 100644
--- a/actionpack/lib/action_controller/assertions/selector_assertions.rb
+++ b/actionpack/lib/action_controller/assertions/selector_assertions.rb
@@ -109,20 +109,27 @@ module ActionController
# starting from (and including) that element and all its children in
# depth-first order.
#
- # If no element if specified, calling +assert_select+ will select from the
- # response HTML. Calling #assert_select inside an +assert_select+ block will
- # run the assertion for each element selected by the enclosing assertion.
+ # If no element if specified, calling +assert_select+ selects from the
+ # response HTML unless +assert_select+ is called from within an +assert_select+ block.
+ #
+ # When called with a block +assert_select+ passes an array of selected elements
+ # to the block. Calling +assert_select+ from the block, with no element specified,
+ # runs the assertion on the complete set of elements selected by the enclosing assertion.
+ # Alternatively the array may be iterated through so that +assert_select+ can be called
+ # separately for each element.
+ #
#
# ==== Example
- # assert_select "ol>li" do |elements|
+ # If the response contains two ordered lists, each with four list elements then:
+ # assert_select "ol" do |elements|
# elements.each do |element|
- # assert_select element, "li"
+ # assert_select element, "li", 4
# end
# end
#
- # Or for short:
- # assert_select "ol>li" do
- # assert_select "li"
+ # will pass, as will:
+ # assert_select "ol" do
+ # assert_select "li", 8
# end
#
# The selector may be a CSS selector expression (String), an expression
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index e22114195c..36b80d5780 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -301,10 +301,7 @@ module ActionController #:nodoc:
# A YAML parser is also available and can be turned on with:
#
# ActionController::Base.param_parsers[Mime::YAML] = :yaml
- @@param_parsers = { Mime::MULTIPART_FORM => :multipart_form,
- Mime::URL_ENCODED_FORM => :url_encoded_form,
- Mime::XML => :xml_simple,
- Mime::JSON => :json }
+ @@param_parsers = {}
cattr_accessor :param_parsers
# Controls the default charset for all renders.
@@ -647,7 +644,7 @@ module ActionController #:nodoc:
end
def session_enabled?
- request.session_options && request.session_options[:disabled] != false
+ ActiveSupport::Deprecation.warn("Sessions are now lazy loaded. So if you don't access them, consider them disabled.", caller)
end
self.view_paths = []
@@ -1318,10 +1315,6 @@ module ActionController #:nodoc:
"#{request.protocol}#{request.host}#{request.request_uri}"
end
- def close_session
- @_session.close if @_session && @_session.respond_to?(:close)
- end
-
def default_template(action_name = self.action_name)
self.view_paths.find_template(default_template_name(action_name), default_template_format)
end
@@ -1345,15 +1338,14 @@ module ActionController #:nodoc:
end
def process_cleanup
- close_session
end
end
Base.class_eval do
[ Filters, Layout, Benchmarking, Rescue, Flash, MimeResponds, Helpers,
Cookies, Caching, Verification, Streaming, SessionManagement,
- HttpAuthentication::Basic::ControllerMethods, RecordIdentifier,
- RequestForgeryProtection, Translation
+ HttpAuthentication::Basic::ControllerMethods, HttpAuthentication::Digest::ControllerMethods,
+ RecordIdentifier, RequestForgeryProtection, Translation
].each do |mod|
include mod
end
diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb
index c7992d7769..c1be264ffb 100644
--- a/actionpack/lib/action_controller/caching/sweeping.rb
+++ b/actionpack/lib/action_controller/caching/sweeping.rb
@@ -87,9 +87,9 @@ module ActionController #:nodoc:
__send__(action_callback_method_name) if respond_to?(action_callback_method_name, true)
end
- def method_missing(method, *arguments)
+ def method_missing(method, *arguments, &block)
return if @controller.nil?
- @controller.__send__(method, *arguments)
+ @controller.__send__(method, *arguments, &block)
end
end
end
diff --git a/actionpack/lib/action_controller/flash.rb b/actionpack/lib/action_controller/flash.rb
index 9856dbed2a..56ee9c67e2 100644
--- a/actionpack/lib/action_controller/flash.rb
+++ b/actionpack/lib/action_controller/flash.rb
@@ -4,20 +4,22 @@ module ActionController #:nodoc:
# action that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can
# then expose the flash to its template. Actually, that exposure is automatically done. Example:
#
- # class WeblogController < ActionController::Base
+ # class PostsController < ActionController::Base
# def create
# # save post
# flash[:notice] = "Successfully created post"
- # redirect_to :action => "display", :params => { :id => post.id }
+ # redirect_to posts_path(@post)
# end
#
- # def display
+ # def show
# # doesn't need to assign the flash notice to the template, that's done automatically
# end
# end
#
- # display.erb
- # <% if flash[:notice] %><div class="notice"><%= flash[:notice] %></div><% end %>
+ # show.html.erb
+ # <% if flash[:notice] %>
+ # <div class="notice"><%= flash[:notice] %></div>
+ # <% end %>
#
# This example just places a string in the flash, but you can put any object in there. And of course, you can put as
# many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
diff --git a/actionpack/lib/action_controller/http_authentication.rb b/actionpack/lib/action_controller/http_authentication.rb
index 2ed810db7d..5d915fda08 100644
--- a/actionpack/lib/action_controller/http_authentication.rb
+++ b/actionpack/lib/action_controller/http_authentication.rb
@@ -1,42 +1,42 @@
module ActionController
module HttpAuthentication
# Makes it dead easy to do HTTP Basic authentication.
- #
+ #
# Simple Basic example:
- #
+ #
# class PostsController < ApplicationController
# USER_NAME, PASSWORD = "dhh", "secret"
- #
+ #
# before_filter :authenticate, :except => [ :index ]
- #
+ #
# def index
# render :text => "Everyone can see me!"
# end
- #
+ #
# def edit
# render :text => "I'm only accessible if you know the password"
# end
- #
+ #
# private
# def authenticate
- # authenticate_or_request_with_http_basic do |user_name, password|
+ # authenticate_or_request_with_http_basic do |user_name, password|
# user_name == USER_NAME && password == PASSWORD
# end
# end
# end
- #
- #
- # Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication,
+ #
+ #
+ # Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication,
# the regular HTML interface is protected by a session approach:
- #
+ #
# class ApplicationController < ActionController::Base
# before_filter :set_account, :authenticate
- #
+ #
# protected
# def set_account
# @account = Account.find_by_url_name(request.subdomains.first)
# end
- #
+ #
# def authenticate
# case request.format
# when Mime::XML, Mime::ATOM
@@ -54,24 +54,48 @@ module ActionController
# end
# end
# end
- #
- #
+ #
# In your integration tests, you can do something like this:
- #
+ #
# def test_access_granted_from_xml
# get(
- # "/notes/1.xml", nil,
+ # "/notes/1.xml", nil,
# :authorization => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
# )
- #
+ #
# assert_equal 200, status
# end
- #
- #
+ #
+ # Simple Digest example:
+ #
+ # class PostsController < ApplicationController
+ # USERS = {"dhh" => "secret"}
+ #
+ # before_filter :authenticate, :except => [:index]
+ #
+ # def index
+ # render :text => "Everyone can see me!"
+ # end
+ #
+ # def edit
+ # render :text => "I'm only accessible if you know the password"
+ # end
+ #
+ # private
+ # def authenticate
+ # authenticate_or_request_with_http_digest(realm) do |username|
+ # USERS[username]
+ # end
+ # end
+ # end
+ #
+ # NOTE: The +authenticate_or_request_with_http_digest+ block must return the user's password so the framework can appropriately
+ # hash it to check the user's credentials. Returning +nil+ will cause authentication to fail.
+ #
# On shared hosts, Apache sometimes doesn't pass authentication headers to
# FCGI instances. If your environment matches this description and you cannot
# authenticate, try this rule in your Apache setup:
- #
+ #
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
module Basic
extend self
@@ -99,14 +123,14 @@ module ActionController
def user_name_and_password(request)
decode_credentials(request).split(/:/, 2)
end
-
+
def authorization(request)
request.env['HTTP_AUTHORIZATION'] ||
request.env['X-HTTP_AUTHORIZATION'] ||
request.env['X_HTTP_AUTHORIZATION'] ||
request.env['REDIRECT_X_HTTP_AUTHORIZATION']
end
-
+
def decode_credentials(request)
ActiveSupport::Base64.decode64(authorization(request).split.last || '')
end
@@ -120,5 +144,131 @@ module ActionController
controller.__send__ :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
end
end
+
+ module Digest
+ extend self
+
+ module ControllerMethods
+ def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure)
+ authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm)
+ end
+
+ # Authenticate with HTTP Digest, returns true or false
+ def authenticate_with_http_digest(realm = "Application", &password_procedure)
+ HttpAuthentication::Digest.authenticate(self, realm, &password_procedure)
+ end
+
+ # Render output including the HTTP Digest authentication header
+ def request_http_digest_authentication(realm = "Application", message = nil)
+ HttpAuthentication::Digest.authentication_request(self, realm, message)
+ end
+ end
+
+ # Returns false on a valid response, true otherwise
+ def authenticate(controller, realm, &password_procedure)
+ authorization(controller.request) && validate_digest_response(controller.request, realm, &password_procedure)
+ end
+
+ def authorization(request)
+ request.env['HTTP_AUTHORIZATION'] ||
+ request.env['X-HTTP_AUTHORIZATION'] ||
+ request.env['X_HTTP_AUTHORIZATION'] ||
+ request.env['REDIRECT_X_HTTP_AUTHORIZATION']
+ end
+
+ # Raises error unless the request credentials response value matches the expected value.
+ def validate_digest_response(request, realm, &password_procedure)
+ credentials = decode_credentials_header(request)
+ valid_nonce = validate_nonce(request, credentials[:nonce])
+
+ if valid_nonce && realm == credentials[:realm] && opaque(request.session.session_id) == credentials[:opaque]
+ password = password_procedure.call(credentials[:username])
+ expected = expected_response(request.env['REQUEST_METHOD'], request.url, credentials, password)
+ expected == credentials[:response]
+ end
+ end
+
+ # Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
+ def expected_response(http_method, uri, credentials, password)
+ ha1 = ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':'))
+ ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':'))
+ ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':'))
+ end
+
+ def encode_credentials(http_method, credentials, password)
+ credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password)
+ "Digest " + credentials.sort_by {|x| x[0].to_s }.inject([]) {|a, v| a << "#{v[0]}='#{v[1]}'" }.join(', ')
+ end
+
+ def decode_credentials_header(request)
+ decode_credentials(authorization(request))
+ end
+
+ def decode_credentials(header)
+ header.to_s.gsub(/^Digest\s+/,'').split(',').inject({}) do |hash, pair|
+ key, value = pair.split('=', 2)
+ hash[key.strip.to_sym] = value.to_s.gsub(/^"|"$/,'').gsub(/'/, '')
+ hash
+ end
+ end
+
+ def authentication_header(controller, realm)
+ session_id = controller.request.session.session_id
+ controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce(session_id)}", opaque="#{opaque(session_id)}")
+ end
+
+ def authentication_request(controller, realm, message = nil)
+ message ||= "HTTP Digest: Access denied.\n"
+ authentication_header(controller, realm)
+ controller.__send__ :render, :text => message, :status => :unauthorized
+ end
+
+ # Uses an MD5 digest based on time to generate a value to be used only once.
+ #
+ # A server-specified data string which should be uniquely generated each time a 401 response is made.
+ # It is recommended that this string be base64 or hexadecimal data.
+ # Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed.
+ #
+ # The contents of the nonce are implementation dependent.
+ # The quality of the implementation depends on a good choice.
+ # A nonce might, for example, be constructed as the base 64 encoding of
+ #
+ # => time-stamp H(time-stamp ":" ETag ":" private-key)
+ #
+ # where time-stamp is a server-generated time or other non-repeating value,
+ # ETag is the value of the HTTP ETag header associated with the requested entity,
+ # and private-key is data known only to the server.
+ # With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and
+ # reject the request if it did not match the nonce from that header or
+ # if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity.
+ # The inclusion of the ETag prevents a replay request for an updated version of the resource.
+ # (Note: including the IP address of the client in the nonce would appear to offer the server the ability
+ # to limit the reuse of the nonce to the same client that originally got it.
+ # However, that would break proxy farms, where requests from a single user often go through different proxies in the farm.
+ # Also, IP address spoofing is not that hard.)
+ #
+ # An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
+ # protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
+ # POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
+ # of this document.
+ #
+ # The nonce is opaque to the client.
+ def nonce(session_id, time = Time.now)
+ t = time.to_i
+ hashed = [t, session_id]
+ digest = ::Digest::MD5.hexdigest(hashed.join(":"))
+ Base64.encode64("#{t}:#{digest}").gsub("\n", '')
+ end
+
+ def validate_nonce(request, value)
+ t = Base64.decode64(value).split(":").first.to_i
+ nonce(request.session.session_id, t) == value && (t - Time.now.to_i).abs <= 10 * 60
+ end
+
+ # Opaque based on digest of session_id
+ def opaque(session_id)
+ Base64.encode64(::Digest::MD5::hexdigest(session_id)).gsub("\n", '')
+ end
+ end
end
end
diff --git a/actionpack/lib/action_controller/layout.rb b/actionpack/lib/action_controller/layout.rb
index 159c5c7326..183d56c2e8 100644
--- a/actionpack/lib/action_controller/layout.rb
+++ b/actionpack/lib/action_controller/layout.rb
@@ -179,7 +179,7 @@ module ActionController #:nodoc:
end
def layout_list #:nodoc:
- Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] }
+ Array(view_paths).sum([]) { |path| Dir["#{path.to_str}/layouts/**/*"] }
end
def find_layout(layout, *formats) #:nodoc:
diff --git a/actionpack/lib/action_controller/lock.rb b/actionpack/lib/action_controller/lock.rb
deleted file mode 100644
index c50762216e..0000000000
--- a/actionpack/lib/action_controller/lock.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-module ActionController
- class Lock
- FLAG = 'rack.multithread'.freeze
-
- def initialize(app, lock = Mutex.new)
- @app, @lock = app, lock
- end
-
- def call(env)
- old, env[FLAG] = env[FLAG], false
- @lock.synchronize { @app.call(env) }
- ensure
- env[FLAG] = old
- end
- end
-end
diff --git a/actionpack/lib/action_controller/middleware_stack.rb b/actionpack/lib/action_controller/middleware_stack.rb
index b94bf6ec4a..dbc2fda41e 100644
--- a/actionpack/lib/action_controller/middleware_stack.rb
+++ b/actionpack/lib/action_controller/middleware_stack.rb
@@ -75,17 +75,22 @@ module ActionController
block.call(self) if block_given?
end
- def insert(index, *objs)
+ def insert(index, *args, &block)
index = self.index(index) unless index.is_a?(Integer)
- objs = objs.map { |obj| Middleware.new(obj) }
- super(index, *objs)
+ middleware = Middleware.new(*args, &block)
+ super(index, middleware)
end
alias_method :insert_before, :insert
- def insert_after(index, *objs)
+ def insert_after(index, *args, &block)
index = self.index(index) unless index.is_a?(Integer)
- insert(index + 1, *objs)
+ insert(index + 1, *args, &block)
+ end
+
+ def swap(target, *args, &block)
+ insert_before(target, *args, &block)
+ delete(target)
end
def use(*args, &block)
diff --git a/actionpack/lib/action_controller/middlewares.rb b/actionpack/lib/action_controller/middlewares.rb
index 0f038b8856..f9cfc2b18e 100644
--- a/actionpack/lib/action_controller/middlewares.rb
+++ b/actionpack/lib/action_controller/middlewares.rb
@@ -1,11 +1,9 @@
-use "ActionController::Lock", :if => lambda {
+use "Rack::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|
@@ -18,5 +16,6 @@ use "ActiveRecord::QueryCache", :if => lambda { defined?(ActiveRecord) }
)
end
-use ActionController::RewindableInput
-use Rack::MethodOverride
+use "ActionController::RewindableInput"
+use "ActionController::ParamsParser"
+use "Rack::MethodOverride"
diff --git a/actionpack/lib/action_controller/params_parser.rb b/actionpack/lib/action_controller/params_parser.rb
new file mode 100644
index 0000000000..d269fe07fa
--- /dev/null
+++ b/actionpack/lib/action_controller/params_parser.rb
@@ -0,0 +1,71 @@
+module ActionController
+ class ParamsParser
+ ActionController::Base.param_parsers[Mime::XML] = :xml_simple
+ ActionController::Base.param_parsers[Mime::JSON] = :json
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ if params = parse_formatted_parameters(env)
+ env["action_controller.request.request_parameters"] = params
+ end
+
+ @app.call(env)
+ end
+
+ private
+ def parse_formatted_parameters(env)
+ request = Request.new(env)
+
+ return false if request.content_length.zero?
+
+ mime_type = content_type_from_legacy_post_data_format_header(env) || request.content_type
+ strategy = ActionController::Base.param_parsers[mime_type]
+
+ return false unless strategy
+
+ case strategy
+ when Proc
+ strategy.call(request.raw_post)
+ when :xml_simple, :xml_node
+ body = request.raw_post
+ body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
+ when :yaml
+ YAML.load(request.raw_post)
+ when :json
+ body = request.raw_post
+ if body.blank?
+ {}
+ else
+ data = ActiveSupport::JSON.decode(body)
+ data = {:_json => data} unless data.is_a?(Hash)
+ data.with_indifferent_access
+ end
+ else
+ false
+ end
+ rescue Exception => e # YAML, XML or Ruby code block errors
+ raise
+ { "body" => request.raw_post,
+ "content_type" => request.content_type,
+ "content_length" => request.content_length,
+ "exception" => "#{e.message} (#{e.class})",
+ "backtrace" => e.backtrace }
+ end
+
+ def content_type_from_legacy_post_data_format_header(env)
+ if x_post_format = env['HTTP_X_POST_DATA_FORMAT']
+ case x_post_format.to_s.downcase
+ when 'yaml'
+ return Mime::YAML
+ when 'xml'
+ return Mime::XML
+ end
+ end
+
+ nil
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/rack_ext.rb b/actionpack/lib/action_controller/rack_ext.rb
index 3b142307e9..2ba6654e3d 100644
--- a/actionpack/lib/action_controller/rack_ext.rb
+++ b/actionpack/lib/action_controller/rack_ext.rb
@@ -1,22 +1,3 @@
-module Rack
- module Utils
- module Multipart
- class << self
- def parse_multipart_with_rewind(env)
- result = parse_multipart_without_rewind(env)
-
- begin
- env['rack.input'].rewind if env['rack.input'].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
-
- result
- end
-
- alias_method_chain :parse_multipart, :rewind
- end
- end
- end
-end
+require 'action_controller/rack_ext/lock'
+require 'action_controller/rack_ext/multipart'
+require 'action_controller/rack_ext/parse_query'
diff --git a/actionpack/lib/action_controller/rack_ext/lock.rb b/actionpack/lib/action_controller/rack_ext/lock.rb
new file mode 100644
index 0000000000..9bf1889065
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_ext/lock.rb
@@ -0,0 +1,21 @@
+module Rack
+ # Rack::Lock was commited to Rack core
+ # http://github.com/rack/rack/commit/7409b0c
+ # Remove this when Rack 1.0 is released
+ unless defined? Lock
+ class Lock
+ FLAG = 'rack.multithread'.freeze
+
+ def initialize(app, lock = Mutex.new)
+ @app, @lock = app, lock
+ end
+
+ def call(env)
+ old, env[FLAG] = env[FLAG], false
+ @lock.synchronize { @app.call(env) }
+ ensure
+ env[FLAG] = old
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/rack_ext/multipart.rb b/actionpack/lib/action_controller/rack_ext/multipart.rb
new file mode 100644
index 0000000000..3b142307e9
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_ext/multipart.rb
@@ -0,0 +1,22 @@
+module Rack
+ module Utils
+ module Multipart
+ class << self
+ def parse_multipart_with_rewind(env)
+ result = parse_multipart_without_rewind(env)
+
+ begin
+ env['rack.input'].rewind if env['rack.input'].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
+
+ result
+ end
+
+ alias_method_chain :parse_multipart, :rewind
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/rack_ext/parse_query.rb b/actionpack/lib/action_controller/rack_ext/parse_query.rb
new file mode 100644
index 0000000000..2f21a57770
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_ext/parse_query.rb
@@ -0,0 +1,18 @@
+# Rack does not automatically cleanup Safari 2 AJAX POST body
+# This has not yet been commited to Rack, please +1 this ticket:
+# http://rack.lighthouseapp.com/projects/22435/tickets/19
+
+module Rack
+ module Utils
+ alias_method :parse_query_without_ajax_body_cleanup, :parse_query
+ module_function :parse_query_without_ajax_body_cleanup
+
+ def parse_query(qs, d = '&;')
+ qs = qs.dup
+ qs.chop! if qs[-1] == 0
+ qs.gsub!(/&_=$/, '')
+ parse_query_without_ajax_body_cleanup(qs, d)
+ end
+ module_function :parse_query
+ end
+end
diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb
index 791ddf3308..a8729bb6f5 100755
--- a/actionpack/lib/action_controller/request.rb
+++ b/actionpack/lib/action_controller/request.rb
@@ -7,12 +7,6 @@ require 'action_controller/cgi_ext'
module ActionController
class Request < Rack::Request
- extend ActiveSupport::Memoizable
-
- def initialize(env)
- super
- @parser = ActionController::RequestParser.new(env)
- end
%w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
@@ -34,16 +28,17 @@ 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 true HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
- # UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS.
+ # Returns the true HTTP request \method as a lowercase symbol, such as
+ # <tt>:get</tt>. If the request \method is not listed in the HTTP_METHODS
+ # constant above, an UnknownHttpMethod exception is raised.
def request_method
- HTTP_METHOD_LOOKUP[super] || raise(UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
+ @request_method ||= HTTP_METHOD_LOOKUP[super] || raise(UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
end
- memoize :request_method
- # The HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
- # Note, HEAD is returned as <tt>:get</tt> since the two are functionally
- # equivalent from the application's perspective.
+ # Returns the HTTP request \method used for action processing as a
+ # lowercase symbol, such as <tt>:post</tt>. (Unlike #request_method, this
+ # method returns <tt>:get</tt> for a HEAD request because the two are
+ # functionally equivalent from the application's perspective.)
def method
request_method == :head ? :get : request_method
end
@@ -78,9 +73,8 @@ module ActionController
#
# request.headers["Content-Type"] # => "text/plain"
def headers
- ActionController::Http::Headers.new(@env)
+ @headers ||= ActionController::Http::Headers.new(@env)
end
- memoize :headers
# Returns the content length of the request as an integer.
def content_length
@@ -92,28 +86,33 @@ 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(@parser.content_type_without_parameters)
+ @content_type ||= begin
+ if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
+ Mime::Type.lookup($1.strip.downcase)
+ else
+ nil
+ end
+ end
end
- memoize :content_type
# Returns the accepted MIME type for the request.
def accepts
- header = @env['HTTP_ACCEPT'].to_s.strip
+ @accepts ||= begin
+ header = @env['HTTP_ACCEPT'].to_s.strip
- if header.empty?
- [content_type, Mime::ALL].compact
- else
- Mime::Type.parse(header)
+ if header.empty?
+ [content_type, Mime::ALL].compact
+ else
+ Mime::Type.parse(header)
+ end
end
end
- memoize :accepts
def if_modified_since
if since = env['HTTP_IF_MODIFIED_SINCE']
Time.rfc2822(since) rescue nil
end
end
- memoize :if_modified_since
def if_none_match
env['HTTP_IF_NONE_MATCH']
@@ -256,25 +255,21 @@ EOM
@env['REMOTE_ADDR']
end
- memoize :remote_ip
# Returns the lowercase name of the HTTP server software.
def server_software
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
end
- memoize :server_software
# Returns the complete URL used for this request.
def url
protocol + host_with_port + request_uri
end
- memoize :url
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
def protocol
ssl? ? 'https://' : 'http://'
end
- memoize :protocol
# Is this an SSL request?
def ssl?
@@ -294,14 +289,12 @@ EOM
def host
raw_host_with_port.sub(/:\d+$/, '')
end
- memoize :host
# Returns a \host:\port string for this request, such as "example.com" or
# "example.com:8080".
def host_with_port
"#{host}#{port_string}"
end
- memoize :host_with_port
# Returns the port number of this request as an integer.
def port
@@ -311,7 +304,6 @@ EOM
standard_port
end
end
- memoize :port
# Returns the standard \port number for this request's protocol.
def standard_port
@@ -349,7 +341,6 @@ EOM
def query_string
@env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].split('?', 2)[1] || '')
end
- memoize :query_string
# Returns the request URI, accounting for server idiosyncrasies.
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
@@ -375,7 +366,6 @@ EOM
end
end
end
- memoize :request_uri
# Returns the interpreted \path to requested resource after all the installation
# directory of this application was taken into account.
@@ -384,12 +374,15 @@ EOM
path.sub!(/\A#{ActionController::Base.relative_url_root}/, '')
path
end
- memoize :path
# Read the request \body. This is useful for web services that need to
# work with raw requests directly.
def raw_post
- @parser.raw_post
+ unless @env.include? 'RAW_POST_DATA'
+ @env['RAW_POST_DATA'] = body.read(@env['CONTENT_LENGTH'].to_i)
+ body.rewind if body.respond_to?(:rewind)
+ end
+ @env['RAW_POST_DATA']
end
# Returns both GET and POST \parameters in a single hash.
@@ -418,19 +411,30 @@ EOM
@env["rack.routing_args"] ||= {}
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
- @parser.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
+
+ def form_data?
+ FORM_DATA_MEDIA_TYPES.include?(content_type.to_s)
end
# Override Rack's GET method to support nested query strings
def GET
- @parser.query_parameters
+ @env["action_controller.request.query_parameters"] ||= UrlEncodedPairParser.parse_query_parameters(query_string)
end
alias_method :query_parameters, :GET
# Override Rack's POST method to support nested query strings
def POST
- @parser.request_parameters
+ @env["action_controller.request.request_parameters"] ||= UrlEncodedPairParser.parse_hash_parameters(super)
end
alias_method :request_parameters, :POST
diff --git a/actionpack/lib/action_controller/request_parser.rb b/actionpack/lib/action_controller/request_parser.rb
deleted file mode 100644
index d1739ef4d0..0000000000
--- a/actionpack/lib/action_controller/request_parser.rb
+++ /dev/null
@@ -1,315 +0,0 @@
-module ActionController
- class RequestParser
- def initialize(env)
- @env = env
- freeze
- end
-
- def request_parameters
- @env["action_controller.request_parser.request_parameters"] ||= parse_formatted_request_parameters
- end
-
- def query_parameters
- @env["action_controller.request_parser.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
- @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/request_profiler.rb b/actionpack/lib/action_controller/request_profiler.rb
deleted file mode 100644
index 80cd55334f..0000000000
--- a/actionpack/lib/action_controller/request_profiler.rb
+++ /dev/null
@@ -1,168 +0,0 @@
-require 'optparse'
-
-module ActionController
- class RequestProfiler
- # Wrap up the integration session runner.
- class Sandbox
- include Integration::Runner
-
- def self.benchmark(n, script)
- new(script).benchmark(n)
- end
-
- def initialize(script_path)
- @quiet = false
- define_run_method(script_path)
- reset!
- end
-
- def benchmark(n, profiling = false)
- @quiet = true
- print ' '
-
- ms = Benchmark.ms do
- n.times do |i|
- run(profiling)
- print_progress(i)
- end
- end
-
- puts
- ms
- ensure
- @quiet = false
- end
-
- def say(message)
- puts " #{message}" unless @quiet
- end
-
- private
- def define_run_method(script_path)
- script = File.read(script_path)
-
- source = <<-end_source
- def run(profiling = false)
- if profiling
- RubyProf.resume do
- #{script}
- end
- else
- #{script}
- end
-
- old_request_count = request_count
- reset!
- self.request_count = old_request_count
- end
- end_source
-
- instance_eval source, script_path, 1
- end
-
- def print_progress(i)
- print "\n " if i % 60 == 0
- print ' ' if i % 10 == 0
- print '.'
- $stdout.flush
- end
- end
-
-
- attr_reader :options
-
- def initialize(options = {})
- @options = default_options.merge(options)
- end
-
-
- def self.run(args = nil, options = {})
- profiler = new(options)
- profiler.parse_options(args) if args
- profiler.run
- end
-
- def run
- sandbox = Sandbox.new(options[:script])
-
- puts 'Warming up once'
-
- elapsed = warmup(sandbox)
- puts '%.0f ms, %d requests, %d req/sec' % [elapsed, sandbox.request_count, 1000 * sandbox.request_count / elapsed]
- puts "\n#{options[:benchmark] ? 'Benchmarking' : 'Profiling'} #{options[:n]}x"
-
- options[:benchmark] ? benchmark(sandbox) : profile(sandbox)
- end
-
- def profile(sandbox)
- load_ruby_prof
-
- benchmark(sandbox, true)
- results = RubyProf.stop
-
- show_profile_results results
- results
- end
-
- def benchmark(sandbox, profiling = false)
- sandbox.request_count = 0
- elapsed = sandbox.benchmark(options[:n], profiling)
- count = sandbox.request_count.to_i
- puts '%.0f ms, %d requests, %d req/sec' % [elapsed, count, 1000 * count / elapsed]
- end
-
- def warmup(sandbox)
- Benchmark.ms { sandbox.run(false) }
- end
-
- def default_options
- { :n => 100, :open => 'open %s &' }
- end
-
- # Parse command-line options
- def parse_options(args)
- OptionParser.new do |opt|
- opt.banner = "USAGE: #{$0} [options] [session script path]"
-
- opt.on('-n', '--times [100]', 'How many requests to process. Defaults to 100.') { |v| options[:n] = v.to_i if v }
- opt.on('-b', '--benchmark', 'Benchmark instead of profiling') { |v| options[:benchmark] = v }
- opt.on('-m', '--measure [mode]', 'Which ruby-prof measure mode to use: process_time, wall_time, cpu_time, allocations, or memory. Defaults to process_time.') { |v| options[:measure] = v }
- opt.on('--open [CMD]', 'Command to open profile results. Defaults to "open %s &"') { |v| options[:open] = v }
- opt.on('-h', '--help', 'Show this help') { puts opt; exit }
-
- opt.parse args
-
- if args.empty?
- puts opt
- exit
- end
- options[:script] = args.pop
- end
- end
-
- protected
- def load_ruby_prof
- begin
- gem 'ruby-prof', '>= 0.6.1'
- require 'ruby-prof'
- if mode = options[:measure]
- RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
- end
- rescue LoadError
- abort '`gem install ruby-prof` to use the profiler'
- end
- end
-
- def show_profile_results(results)
- File.open "#{RAILS_ROOT}/tmp/profile-graph.html", 'w' do |file|
- RubyProf::GraphHtmlPrinter.new(results).print(file)
- `#{options[:open] % file.path}` if options[:open]
- end
-
- File.open "#{RAILS_ROOT}/tmp/profile-flat.txt", 'w' do |file|
- RubyProf::FlatPrinter.new(results).print(file)
- `#{options[:open] % file.path}` if options[:open]
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/rewindable_input.rb b/actionpack/lib/action_controller/rewindable_input.rb
index 058453ea68..cedfb7fd75 100644
--- a/actionpack/lib/action_controller/rewindable_input.rb
+++ b/actionpack/lib/action_controller/rewindable_input.rb
@@ -3,33 +3,17 @@ module ActionController
class RewindableIO < ActiveSupport::BasicObject
def initialize(io)
@io = io
- end
-
- def read(*args)
- read_original_io
- @io.read(*args)
- end
-
- def rewind
- read_original_io
- @io.rewind
- end
-
- def string
- @string
+ @rewindable = io.is_a?(::StringIO)
end
def method_missing(method, *args, &block)
- @io.send(method, *args, &block)
- end
-
- private
- def read_original_io
- unless @string
- @string = @io.read
- @io = StringIO.new(@string)
- end
+ unless @rewindable
+ @io = ::StringIO.new(@io.read)
+ @rewindable = true
end
+
+ @io.__send__(method, *args, &block)
+ end
end
def initialize(app)
diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb
index da9b56fdf9..a2141a77dc 100644
--- a/actionpack/lib/action_controller/routing.rb
+++ b/actionpack/lib/action_controller/routing.rb
@@ -193,9 +193,8 @@ module ActionController
#
# map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
#
- # will glob all remaining parts of the route that were not recognized earlier. This idiom
- # must appear at the end of the path. The globbed values are in <tt>params[:path]</tt> in
- # this case.
+ # will glob all remaining parts of the route that were not recognized earlier.
+ # The globbed values are in <tt>params[:path]</tt> as an array of path segments.
#
# == Route conditions
#
diff --git a/actionpack/lib/action_controller/session/abstract_store.rb b/actionpack/lib/action_controller/session/abstract_store.rb
index bf09fd33c5..9434c2e05e 100644
--- a/actionpack/lib/action_controller/session/abstract_store.rb
+++ b/actionpack/lib/action_controller/session/abstract_store.rb
@@ -102,8 +102,10 @@ module ActionController
response = @app.call(env)
session_data = env[ENV_SESSION_KEY]
- if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?)
- options = env[ENV_SESSION_OPTIONS_KEY]
+ options = env[ENV_SESSION_OPTIONS_KEY]
+
+ if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after]
+ session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?)
if session_data.is_a?(AbstractStore::SessionHash)
sid = session_data.id
diff --git a/actionpack/lib/action_controller/session/cookie_store.rb b/actionpack/lib/action_controller/session/cookie_store.rb
index e061c4d4a1..5a728d1877 100644
--- a/actionpack/lib/action_controller/session/cookie_store.rb
+++ b/actionpack/lib/action_controller/session/cookie_store.rb
@@ -45,7 +45,7 @@ module ActionController
:domain => nil,
:path => "/",
:expire_after => nil,
- :httponly => false
+ :httponly => true
}.freeze
ENV_SESSION_KEY = "rack.session".freeze
@@ -56,8 +56,6 @@ module ActionController
class CookieOverflow < StandardError; end
def initialize(app, options = {})
- options = options.dup
-
# Process legacy CGI options
options = options.symbolize_keys
if options.has_key?(:session_path)
@@ -95,12 +93,14 @@ module ActionController
status, headers, body = @app.call(env)
session_data = env[ENV_SESSION_KEY]
- if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?)
+ options = env[ENV_SESSION_OPTIONS_KEY]
+
+ if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after]
+ session_data.send(:load!) 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
- options = env[ENV_SESSION_OPTIONS_KEY]
cookie = Hash.new
cookie[:value] = session_data
unless options[:expire_after].nil?
diff --git a/actionpack/lib/action_controller/session_management.rb b/actionpack/lib/action_controller/session_management.rb
index f06a0da75c..b556f04649 100644
--- a/actionpack/lib/action_controller/session_management.rb
+++ b/actionpack/lib/action_controller/session_management.rb
@@ -37,7 +37,7 @@ module ActionController #:nodoc:
# Returns the hash used to configure the session. Example use:
#
- # ActionController::Base.session_options[:session_secure] = true # session only available over HTTPS
+ # ActionController::Base.session_options[:secure] = true # session only available over HTTPS
def session_options
@session_options ||= {}
end
diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb
index 22b97fc157..4b5fc3a3c1 100644
--- a/actionpack/lib/action_controller/test_process.rb
+++ b/actionpack/lib/action_controller/test_process.rb
@@ -15,7 +15,7 @@ module ActionController #:nodoc:
end
def reset_session
- @session = TestSession.new
+ @session.reset
end
# Wraps raw_post in a StringIO.
@@ -35,7 +35,6 @@ module ActionController #:nodoc:
def port=(number)
@env["SERVER_PORT"] = number.to_i
- port(true)
end
def action=(action_name)
@@ -49,8 +48,6 @@ module ActionController #:nodoc:
@env["REQUEST_URI"] = value
@request_uri = nil
@path = nil
- request_uri(true)
- path(true)
end
def request_uri=(uri)
@@ -58,9 +55,13 @@ module ActionController #:nodoc:
@path = uri.split("?").first
end
+ def request_method=(method)
+ @request_method = method
+ end
+
def accept=(mime_types)
@env["HTTP_ACCEPT"] = Array(mime_types).collect { |mime_types| mime_types.to_s }.join(",")
- accepts(true)
+ @accepts = nil
end
def if_modified_since=(last_modified)
@@ -76,11 +77,11 @@ module ActionController #:nodoc:
end
def request_uri(*args)
- @request_uri || super
+ @request_uri || super()
end
def path(*args)
- @path || super
+ @path || super()
end
def assign_parameters(controller_path, action, parameters)
@@ -107,7 +108,7 @@ module ActionController #:nodoc:
def recycle!
self.query_parameters = {}
self.path_parameters = {}
- unmemoize_all
+ @headers, @request_method, @accepts, @content_type = nil, nil, nil, nil
end
def user_agent=(user_agent)
@@ -279,38 +280,62 @@ module ActionController #:nodoc:
end
end
- class TestSession #:nodoc:
+ class TestSession < Hash #:nodoc:
attr_accessor :session_id
def initialize(attributes = nil)
- @session_id = ''
- @attributes = attributes.nil? ? nil : attributes.stringify_keys
- @saved_attributes = nil
+ reset_session_id
+ replace_attributes(attributes)
+ end
+
+ def reset
+ reset_session_id
+ replace_attributes({ })
end
def data
- @attributes ||= @saved_attributes || {}
+ to_hash
end
def [](key)
- data[key.to_s]
+ super(key.to_s)
end
def []=(key, value)
- data[key.to_s] = value
+ super(key.to_s, value)
end
- def update
- @saved_attributes = @attributes
+ def update(hash = nil)
+ if hash.nil?
+ ActiveSupport::Deprecation.warn('use replace instead', caller)
+ replace({})
+ else
+ super(hash)
+ end
end
- def delete
- @attributes = nil
+ def delete(key = nil)
+ if key.nil?
+ ActiveSupport::Deprecation.warn('use clear instead', caller)
+ clear
+ else
+ super(key.to_s)
+ end
end
def close
- update
- delete
+ ActiveSupport::Deprecation.warn('sessions should no longer be closed', caller)
+ end
+
+ private
+
+ def reset_session_id
+ @session_id = ''
+ end
+
+ def replace_attributes(attributes = nil)
+ attributes ||= {}
+ replace(attributes.stringify_keys)
end
end
diff --git a/actionpack/lib/action_controller/uploaded_file.rb b/actionpack/lib/action_controller/uploaded_file.rb
index ea4845c68f..376ba3621a 100644
--- a/actionpack/lib/action_controller/uploaded_file.rb
+++ b/actionpack/lib/action_controller/uploaded_file.rb
@@ -7,6 +7,13 @@ module ActionController
end
end
+ def self.extended(object)
+ object.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
diff --git a/actionpack/lib/action_controller/url_encoded_pair_parser.rb b/actionpack/lib/action_controller/url_encoded_pair_parser.rb
index 9883ad0d85..57594c4259 100644
--- a/actionpack/lib/action_controller/url_encoded_pair_parser.rb
+++ b/actionpack/lib/action_controller/url_encoded_pair_parser.rb
@@ -1,5 +1,66 @@
module ActionController
class UrlEncodedPairParser < StringScanner #:nodoc:
+ 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
+
+ new(pairs).result
+ end
+
+ def parse_hash_parameters(params)
+ parser = new
+
+ params = params.dup
+ until params.empty?
+ for key, value in params
+ if key.blank?
+ params.delete(key)
+ elsif value.is_a?(Array)
+ parser.parse(key, get_typed_value(value.shift))
+ params.delete(key) if value.empty?
+ else
+ parser.parse(key, get_typed_value(value))
+ params.delete(key)
+ end
+ end
+ end
+
+ parser.result
+ end
+
+ private
+ def get_typed_value(value)
+ case value
+ when String
+ value
+ when NilClass
+ ''
+ when Array
+ value.map { |v| get_typed_value(v) }
+ when Hash
+ if value.has_key?(:tempfile) && value[:filename].any?
+ upload = value[:tempfile]
+ upload.extend(UploadedFile)
+ upload.original_path = value[:filename]
+ upload.content_type = value[:type]
+ upload
+ else
+ nil
+ end
+ else
+ raise "Unknown form value: #{value.inspect}"
+ end
+ end
+ end
+
attr_reader :top, :parent, :result
def initialize(pairs = [])
diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb
index d86e2db67d..bb6cb437b7 100644
--- a/actionpack/lib/action_controller/url_rewriter.rb
+++ b/actionpack/lib/action_controller/url_rewriter.rb
@@ -92,15 +92,12 @@ module ActionController
# end
# end
module UrlWriter
- # The default options for urls written by this writer. Typically a <tt>:host</tt>
- # pair is provided.
- mattr_accessor :default_url_options
- self.default_url_options = {}
-
def self.included(base) #:nodoc:
ActionController::Routing::Routes.install_helpers(base)
base.mattr_accessor :default_url_options
- base.default_url_options ||= default_url_options
+
+ # The default options for urls written by this writer. Typically a <tt>:host</tt> pair is provided.
+ base.default_url_options ||= {}
end
# Generate a url based on the options provided, default_url_options and the
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index c7fd3092e7..b90f89be39 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2008 David Heinemeier Hansson
+# Copyright (c) 2004-2009 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 210a5f1a93..0b710bd8d9 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2008 David Heinemeier Hansson
+# Copyright (c) 2004-2009 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 58f8cca6be..f6abea38ed 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -6,54 +6,70 @@ module ActionView
module Helpers #:nodoc:
# This module provides methods for generating HTML that links views to assets such
# as images, javascripts, stylesheets, and feeds. These methods do not verify
- # the assets exist before linking to them.
+ # the assets exist before linking to them:
+ #
+ # image_tag("rails.png")
+ # # => <img alt="Rails src="/images/rails.png?1230601161" />
+ # stylesheet_link_tag("application")
+ # # => <link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
#
# === Using asset hosts
+ #
# By default, Rails links to these assets on the current host in the public
- # folder, but you can direct Rails to link to assets from a dedicated assets server by
- # setting ActionController::Base.asset_host in your <tt>config/environment.rb</tt>. For example,
- # let's say your asset host is <tt>assets.example.com</tt>.
+ # folder, but you can direct Rails to link to assets from a dedicated asset
+ # server by setting ActionController::Base.asset_host in the application
+ # configuration, typically in <tt>config/environments/production.rb</tt>.
+ # For example, you'd define <tt>assets.example.com</tt> to be your asset
+ # host this way:
#
# ActionController::Base.asset_host = "assets.example.com"
+ #
+ # Helpers take that into account:
+ #
# image_tag("rails.png")
- # => <img src="http://assets.example.com/images/rails.png" alt="Rails" />
+ # # => <img alt="Rails" src="http://assets.example.com/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
- # => <link href="http://assets.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
#
- # This is useful since browsers typically open at most two connections to a single host,
- # which means your assets often wait in single file for their turn to load. You can
- # alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com")
- # to automatically distribute asset requests among four hosts (e.g., "assets0.example.com" through "assets3.example.com")
- # so browsers will open eight connections rather than two.
+ # Browsers typically open at most two simultaneous connections to a single
+ # host, which means your assets often have to wait for other assets to finish
+ # downloading. You can alleviate this by using a <tt>%d</tt> wildcard in the
+ # +asset_host+. For example, "assets%d.example.com". If that wildcard is
+ # present Rails distributes asset requests among the corresponding four hosts
+ # "assets0.example.com", ..., "assets3.example.com". With this trick browsers
+ # will open eight simultaneous connections rather than two.
#
# image_tag("rails.png")
- # => <img src="http://assets0.example.com/images/rails.png" alt="Rails" />
+ # # => <img alt="Rails" src="http://assets0.example.com/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
- # => <link href="http://assets3.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
#
- # To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME
- # the wildcard to a single asset host. You can read more about setting up your DNS CNAME records from
- # your ISP.
+ # To do this, you can either setup four actual hosts, or you can use wildcard
+ # DNS to CNAME the wildcard to a single asset host. You can read more about
+ # setting up your DNS CNAME records from your ISP.
#
# Note: This is purely a browser performance optimization and is not meant
# for server load balancing. See http://www.die.net/musings/page_load_time/
# for background.
#
- # Alternatively, you can exert more control over the asset host by setting <tt>asset_host</tt> to a proc
- # that takes a single source argument. This is useful if you are unable to setup 4 actual hosts or have
- # fewer/more than 4 hosts. The example proc below generates http://assets1.example.com and
- # http://assets2.example.com randomly.
+ # Alternatively, you can exert more control over the asset host by setting
+ # +asset_host+ to a proc like this:
#
- # ActionController::Base.asset_host = Proc.new { |source| "http://assets#{rand(2) + 1}.example.com" }
+ # ActionController::Base.asset_host = Proc.new { |source|
+ # "http://assets#{rand(2) + 1}.example.com"
+ # }
# image_tag("rails.png")
- # => <img src="http://assets2.example.com/images/rails.png" alt="Rails" />
+ # # => <img alt="Rails" src="http://assets0.example.com/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
- # => <link href="http://assets1.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets1.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
#
- # The proc takes a <tt>source</tt> parameter (which is the path of the source asset) and an optional
- # <tt>request</tt> parameter (which is an entire instance of an <tt>ActionController::AbstractRequest</tt>
- # subclass). This can be used to generate a particular asset host depending on the asset path and the particular
- # request.
+ # The example above generates "http://assets1.example.com" and
+ # "http://assets2.example.com" randomly. This option is useful for example if
+ # you need fewer/more than four hosts, custom host names, etc.
+ #
+ # As you see the proc takes a +source+ parameter. That's a string with the
+ # absolute path of the asset with any extensions and timestamps in place,
+ # for example "/images/rails.png?1230601161".
#
# ActionController::Base.asset_host = Proc.new { |source|
# if source.starts_with?('/images')
@@ -63,14 +79,16 @@ module ActionView
# end
# }
# image_tag("rails.png")
- # => <img src="http://images.example.com/images/rails.png" alt="Rails" />
+ # # => <img alt="Rails" src="http://images.example.com/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
- # => <link href="http://assets.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
#
- # The optional <tt>request</tt> parameter to the proc is useful in particular for serving assets from an
- # SSL-protected page. The example proc below disables asset hosting for HTTPS connections, while still sending
- # assets for plain HTTP requests from asset hosts. This is useful for avoiding mixed media warnings when serving
- # non-HTTP assets from HTTPS web pages when you don't have an SSL certificate for each of the asset hosts.
+ # Alternatively you may ask for a second parameter +request+. That one is
+ # particularly useful for serving assets from an SSL-protected page. The
+ # example proc below disables asset hosting for HTTPS connections, while
+ # still sending assets for plain HTTP requests from asset hosts. If you don't
+ # have SSL certificates for each of the asset hosts this technique allows you
+ # to avoid warnings in the client about mixed media.
#
# ActionController::Base.asset_host = Proc.new { |source, request|
# if request.ssl?
@@ -80,7 +98,8 @@ module ActionView
# end
# }
#
- # You can also implement a custom asset host object that responds to the call method and tasks one or two parameters just like the proc.
+ # You can also implement a custom asset host object that responds to +call+
+ # and takes either one or two parameters just like the proc.
#
# config.action_controller.asset_host = AssetHostingWithMinimumSsl.new(
# "http://asset%d.example.com", "https://asset1.example.com"
@@ -88,24 +107,29 @@ module ActionView
#
# === Using asset timestamps
#
- # By default, Rails will append all asset paths with that asset's timestamp. This allows you to set a cache-expiration date for the
- # asset far into the future, but still be able to instantly invalidate it by simply updating the file (and hence updating the timestamp,
- # which then updates the URL as the timestamp is part of that, which in turn busts the cache).
+ # By default, Rails appends asset's timestamps to all asset paths. This allows
+ # you to set a cache-expiration date for the asset far into the future, but
+ # still be able to instantly invalidate it by simply updating the file (and
+ # hence updating the timestamp, which then updates the URL as the timestamp
+ # is part of that, which in turn busts the cache).
#
- # It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take
- # advantage of this feature. Here's an example for Apache:
+ # It's the responsibility of the web server you use to set the far-future
+ # expiration date on cache assets that you need to take advantage of this
+ # feature. Here's an example for Apache:
#
- # # Asset Expiration
- # ExpiresActive On
- # <FilesMatch "\.(ico|gif|jpe?g|png|js|css)$">
- # ExpiresDefault "access plus 1 year"
- # </FilesMatch>
+ # # Asset Expiration
+ # ExpiresActive On
+ # <FilesMatch "\.(ico|gif|jpe?g|png|js|css)$">
+ # ExpiresDefault "access plus 1 year"
+ # </FilesMatch>
#
- # Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must
- # have their clocks synchronized. If one of them drift out of sync, you'll see different timestamps at random and the cache won't
- # work. Which means that the browser will request the same assets over and over again even thought they didn't change. You can use
- # something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being
- # requested over and over).
+ # Also note that in order for this to work, all your application servers must
+ # return the same timestamps. This means that they must have their clocks
+ # synchronized. If one of them drifts out of sync, you'll see different
+ # timestamps at random and the cache won't work. In that case the browser
+ # will request the same assets over and over again even thought they didn't
+ # change. You can use something like Live HTTP Headers for Firefox to verify
+ # that the cache is indeed working.
module AssetTagHelper
ASSETS_DIR = defined?(Rails.public_path) ? Rails.public_path : "public"
JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts"
@@ -117,7 +141,7 @@ module ActionView
# <tt>:atom</tt>. Control the link options in url_for format using the
# +url_options+. You can modify the LINK tag itself in +tag_options+.
#
- # ==== Options:
+ # ==== Options
# * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate"
# * <tt>:type</tt> - Override the auto-generated mime type
# * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 4305617ac8..b4c1adbe76 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -860,7 +860,7 @@ module ActionView
# => post[written_on(1i)]
def input_name_from_type(type)
prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX
- prefix += "[#{@options[:index]}]" if @options[:index]
+ prefix += "[#{@options[:index]}]" if @options.has_key?(:index)
field_name = @options[:field_name] || type
if @options[:include_position]
@@ -923,7 +923,7 @@ module ActionView
options[:field_name] = @method_name
options[:include_position] = true
options[:prefix] ||= @object_name
- options[:index] ||= @auto_index
+ options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
options[:datetime_separator] ||= ' &mdash; '
options[:time_separator] ||= ' : '
@@ -961,15 +961,15 @@ module ActionView
class FormBuilder
def date_select(method, options = {}, html_options = {})
- @template.date_select(@object_name, method, options.merge(:object => @object), html_options)
+ @template.date_select(@object_name, method, objectify_options(options), html_options)
end
def time_select(method, options = {}, html_options = {})
- @template.time_select(@object_name, method, options.merge(:object => @object), html_options)
+ @template.time_select(@object_name, method, objectify_options(options), html_options)
end
def datetime_select(method, options = {}, html_options = {})
- @template.datetime_select(@object_name, method, options.merge(:object => @object), html_options)
+ @template.datetime_select(@object_name, method, objectify_options(options), html_options)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 9ed50a9653..54c82cbd1d 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -277,6 +277,62 @@ module ActionView
end
end
+ # Returns a string of <tt><option></tt> tags, like <tt>options_for_select</tt>, but
+ # wraps them with <tt><optgroup></tt> tags.
+ #
+ # Parameters:
+ # * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
+ # <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a
+ # nested array of text-value pairs. See <tt>options_for_select</tt> for more info.
+ # Ex. ["North America",[["United States","US"],["Canada","CA"]]]
+ # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
+ # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
+ # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
+ # * +prompt+ - set to true or a prompt string. When the select element doesn’t have a value yet, this
+ # prepends an option with a generic prompt — "Please select" — or the given prompt string.
+ #
+ # Sample usage (Array):
+ # grouped_options = [
+ # ['North America',
+ # [['United States','US'],'Canada']],
+ # ['Europe',
+ # ['Denmark','Germany','France']]
+ # ]
+ # grouped_options_for_select(grouped_options)
+ #
+ # Sample usage (Hash):
+ # grouped_options = {
+ # 'North America' => [['United States','US], 'Canada'],
+ # 'Europe' => ['Denmark','Germany','France']
+ # }
+ # grouped_options_for_select(grouped_options)
+ #
+ # Possible output:
+ # <optgroup label="Europe">
+ # <option value="Denmark">Denmark</option>
+ # <option value="Germany">Germany</option>
+ # <option value="France">France</option>
+ # </optgroup>
+ # <optgroup label="North America">
+ # <option value="US">United States</option>
+ # <option value="Canada">Canada</option>
+ # </optgroup>
+ #
+ # <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
+ # wrap the output in an appropriate <tt><select></tt> tag.
+ def grouped_options_for_select(grouped_options, selected_key = nil, prompt = nil)
+ body = ''
+ body << content_tag(:option, prompt, :value => "") if prompt
+
+ grouped_options = grouped_options.sort if grouped_options.is_a?(Hash)
+
+ grouped_options.each do |group|
+ body << content_tag(:optgroup, options_for_select(group[1], selected_key), :label => group[0])
+ end
+
+ body
+ end
+
# Returns a string of option tags for pretty much any time zone in the
# world. Supply a TimeZone name as +selected+ to have it marked as the
# selected option tag. You can also supply an array of TimeZone objects
@@ -349,8 +405,9 @@ module ActionView
html_options = html_options.stringify_keys
add_default_name_and_id(html_options)
value = value(object)
+ selected_value = options.has_key?(:selected) ? options[:selected] : value
content_tag(
- "select", add_options(options_from_collection_for_select(collection, value_method, text_method, value), options, value), html_options
+ "select", add_options(options_from_collection_for_select(collection, value_method, text_method, selected_value), options, value), html_options
)
end
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 3e734ccaab..e622f97b9e 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -220,6 +220,8 @@ module ActionView
end
end
+ STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
+
# Formats the bytes in +size+ into a more understandable representation
# (e.g., giving it 1500 yields 1.5 KB). This method is useful for
# reporting file sizes to users. This method returns nil if
@@ -247,7 +249,7 @@ module ActionView
# number_to_human_size(1234567, 2) # => 1.18 MB
# number_to_human_size(483989, 0) # => 473 KB
def number_to_human_size(number, *args)
- return number.nil? ? nil : pluralize(number.to_i, "Byte") if number.to_i < 1024
+ return nil if number.nil?
options = args.extract_options!
options.symbolize_keys!
@@ -255,7 +257,6 @@ module ActionView
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
human = I18n.translate(:'number.human.format', :locale => options[:locale], :raise => true) rescue {}
defaults = defaults.merge(human)
- storage_units = I18n.translate(:'number.human.storage_units', :locale => options[:locale], :raise => true)
unless args.empty?
ActiveSupport::Deprecation.warn('number_to_human_size takes an option hash ' +
@@ -267,22 +268,32 @@ module ActionView
separator ||= (options[:separator] || defaults[:separator])
delimiter ||= (options[:delimiter] || defaults[:delimiter])
- max_exp = storage_units.size - 1
- number = Float(number)
- exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024
- exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
- number /= 1024 ** exponent
- unit = storage_units[exponent]
+ storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
- begin
- escaped_separator = Regexp.escape(separator)
- number_with_precision(number,
- :precision => precision,
- :separator => separator,
- :delimiter => delimiter
- ).sub(/(\d)(#{escaped_separator}[1-9]*)?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') + " #{unit}"
- rescue
- number
+ if number.to_i < 1024
+ unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
+ storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
+ else
+ max_exp = STORAGE_UNITS.size - 1
+ number = Float(number)
+ exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024
+ exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
+ number /= 1024 ** exponent
+
+ unit_key = STORAGE_UNITS[exponent]
+ unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
+
+ begin
+ escaped_separator = Regexp.escape(separator)
+ formatted_number = number_with_precision(number,
+ :precision => precision,
+ :separator => separator,
+ :delimiter => delimiter
+ ).sub(/(\d)(#{escaped_separator}[1-9]*)?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
+ storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit)
+ rescue
+ number
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 1d9e4fe9b8..b1eb6891fa 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -107,7 +107,7 @@ module ActionView
text
else
match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
- text.gsub(/(#{match})/i, options[:highlighter])
+ text.gsub(/(#{match})(?!(?:[^<]*?)?(?:["'])[^<>]*>)/i, options[:highlighter])
end
end
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index a880fd83ef..afe35691bc 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -44,7 +44,18 @@
# separator:
delimiter: ""
precision: 1
- storage_units: [Bytes, KB, MB, GB, TB]
+ storage_units:
+ # Storage units output formatting.
+ # %u is the storage unit, %n is the number (default: 2 MB)
+ format: "%n %u"
+ units:
+ byte:
+ one: "Byte"
+ other: "Bytes"
+ kb: "KB"
+ mb: "MB"
+ gb: "GB"
+ tb: "TB"
# Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words()
datetime:
diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb
index 19207e7262..ee26542a07 100644
--- a/actionpack/lib/action_view/paths.rb
+++ b/actionpack/lib/action_view/paths.rb
@@ -37,10 +37,17 @@ module ActionView #:nodoc:
template_path = original_template_path.sub(/^\//, '')
each do |load_path|
- if format && (template = load_path["#{template_path}.#{format}"])
+ if format && (template = load_path["#{template_path}.#{I18n.locale}.#{format}"])
+ return template
+ elsif format && (template = load_path["#{template_path}.#{format}"])
+ return template
+ elsif template = load_path["#{template_path}.#{I18n.locale}"]
return template
elsif template = load_path[template_path]
return template
+ # Try to find html version if the format is javascript
+ elsif format == :js && template = load_path["#{template_path}.html"]
+ return template
end
end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 88ee07d95b..1361a969a9 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -93,13 +93,14 @@ module ActionView #:nodoc:
@@exempt_from_layout.merge(regexps)
end
- attr_accessor :filename, :load_path, :base_path, :name, :format, :extension
+ attr_accessor :filename, :load_path, :base_path
+ attr_accessor :locale, :name, :format, :extension
delegate :to_s, :to => :path
def initialize(template_path, load_paths = [])
template_path = template_path.dup
@load_path, @filename = find_full_path(template_path, load_paths)
- @base_path, @name, @format, @extension = split(template_path)
+ @base_path, @name, @locale, @format, @extension = split(template_path)
@base_path.to_s.gsub!(/\/$/, '') # Push to split method
# Extend with partial super powers
@@ -137,17 +138,17 @@ module ActionView #:nodoc:
memoize :mime_type
def path
- [base_path, [name, format, extension].compact.join('.')].compact.join('/')
+ [base_path, [name, locale, format, extension].compact.join('.')].compact.join('/')
end
memoize :path
def path_without_extension
- [base_path, [name, format].compact.join('.')].compact.join('/')
+ [base_path, [name, locale, format].compact.join('.')].compact.join('/')
end
memoize :path_without_extension
def path_without_format_and_extension
- [base_path, name].compact.join('/')
+ [base_path, [name, locale].compact.join('.')].compact.join('/')
end
memoize :path_without_format_and_extension
@@ -204,7 +205,11 @@ module ActionView #:nodoc:
private
def valid_extension?(extension)
- Template.template_handler_extensions.include?(extension)
+ !Template.registered_template_handler(extension).nil?
+ end
+
+ def valid_locale?(locale)
+ I18n.available_locales.include?(locale.to_sym)
end
def find_full_path(path, load_paths)
@@ -217,19 +222,42 @@ module ActionView #:nodoc:
end
# Returns file split into an array
- # [base_path, name, format, extension]
+ # [base_path, name, locale, format, extension]
def split(file)
- if m = file.match(/^(.*\/)?([^\.]+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/)
- if valid_extension?(m[5]) # Multipart formats
- [m[1], m[2], "#{m[3]}.#{m[4]}", m[5]]
- elsif valid_extension?(m[4]) # Single format
- [m[1], m[2], m[3], m[4]]
- elsif valid_extension?(m[3]) # No format
- [m[1], m[2], nil, m[3]]
+ if m = file.match(/^(.*\/)?([^\.]+)\.(.*)$/)
+ base_path = m[1]
+ name = m[2]
+ extensions = m[3]
+ else
+ return
+ end
+
+ locale = nil
+ format = nil
+ extension = nil
+
+ if m = extensions.match(/^(\w+)?\.?(\w+)?\.?(\w+)?\.?/)
+ if valid_locale?(m[1]) && m[2] && valid_extension?(m[3]) # All three
+ locale = m[1]
+ format = m[2]
+ extension = m[3]
+ elsif m[1] && m[2] && valid_extension?(m[3]) # Multipart formats
+ format = "#{m[1]}.#{m[2]}"
+ extension = m[3]
+ elsif valid_locale?(m[1]) && valid_extension?(m[2]) # locale and extension
+ locale = m[1]
+ extension = m[2]
+ elsif valid_extension?(m[2]) # format and extension
+ format = m[1]
+ extension = m[2]
+ elsif valid_extension?(m[1]) # Just extension
+ extension = m[1]
else # No extension
- [m[1], m[2], m[3], nil]
+ format = m[1]
end
end
+
+ [base_path, name, locale, format, extension]
end
end
end
diff --git a/actionpack/lib/action_view/template_handlers.rb b/actionpack/lib/action_view/template_handlers.rb
index d06ddd5fb5..205f8628f0 100644
--- a/actionpack/lib/action_view/template_handlers.rb
+++ b/actionpack/lib/action_view/template_handlers.rb
@@ -32,13 +32,17 @@ module ActionView #:nodoc:
@@template_handlers.keys.map(&:to_s).sort
end
+ def registered_template_handler(extension)
+ extension && @@template_handlers[extension.to_sym]
+ end
+
def register_default_template_handler(extension, klass)
register_template_handler(extension, klass)
@@default_template_handlers = klass
end
def handler_class_for_extension(extension)
- (extension && @@template_handlers[extension.to_sym]) || @@default_template_handlers
+ registered_template_handler(extension) || @@default_template_handlers
end
end
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 30e2d863d0..4baebcb4d1 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -32,6 +32,10 @@ ActionController::Routing::Routes.reload rescue nil
ActionController::Base.session_store = nil
+# Register danish language for testing
+I18n.backend.store_translations 'da', {}
+ORIGINAL_LOCALES = I18n.available_locales
+
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
ActionController::Base.view_paths = FIXTURE_LOAD_PATH
diff --git a/actionpack/test/controller/http_authentication_test.rb b/actionpack/test/controller/http_authentication_test.rb
deleted file mode 100644
index c0069e8032..0000000000
--- a/actionpack/test/controller/http_authentication_test.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'abstract_unit'
-
-class HttpBasicAuthenticationTest < Test::Unit::TestCase
- include ActionController::HttpAuthentication::Basic
-
- class DummyController
- attr_accessor :headers, :renders, :request
-
- def initialize
- @headers, @renders = {}, []
- @request = ActionController::TestRequest.new
- end
-
- def render(options)
- self.renders << options
- end
- end
-
- def setup
- @controller = DummyController.new
- @credentials = ActionController::HttpAuthentication::Basic.encode_credentials("dhh", "secret")
- end
-
- def test_successful_authentication
- login = Proc.new { |user_name, password| user_name == "dhh" && password == "secret" }
- set_headers
- assert authenticate(@controller, &login)
-
- set_headers ''
- assert_nothing_raised do
- assert !authenticate(@controller, &login)
- end
-
- set_headers nil
- set_headers @credentials, 'REDIRECT_X_HTTP_AUTHORIZATION'
- assert authenticate(@controller, &login)
- end
-
- def test_failing_authentication
- set_headers
- assert !authenticate(@controller) { |user_name, password| user_name == "dhh" && password == "incorrect" }
- end
-
- def test_authentication_request
- authentication_request(@controller, "Megaglobalapp")
- assert_equal 'Basic realm="Megaglobalapp"', @controller.headers["WWW-Authenticate"]
- assert_equal :unauthorized, @controller.renders.first[:status]
- end
-
- private
- def set_headers(value = @credentials, name = 'HTTP_AUTHORIZATION')
- @controller.request.env[name] = value
- end
-end
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
new file mode 100644
index 0000000000..fbc94a0df7
--- /dev/null
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -0,0 +1,88 @@
+require 'abstract_unit'
+
+class HttpBasicAuthenticationTest < ActionController::TestCase
+ class DummyController < ActionController::Base
+ before_filter :authenticate, :only => :index
+ before_filter :authenticate_with_request, :only => :display
+
+ def index
+ render :text => "Hello Secret"
+ end
+
+ def display
+ render :text => 'Definitely Maybe'
+ end
+
+ private
+
+ def authenticate
+ authenticate_or_request_with_http_basic do |username, password|
+ username == 'lifo' && password == 'world'
+ end
+ end
+
+ def authenticate_with_request
+ if authenticate_with_http_basic { |username, password| username == 'pretty' && password == 'please' }
+ @logged_in = true
+ else
+ request_http_basic_authentication("SuperSecret")
+ end
+ end
+ end
+
+ AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION']
+
+ tests DummyController
+
+ AUTH_HEADERS.each do |header|
+ test "successful authentication with #{header.downcase}" do
+ @request.env[header] = encode_credentials('lifo', 'world')
+ get :index
+
+ assert_response :success
+ assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}"
+ end
+ end
+
+ AUTH_HEADERS.each do |header|
+ test "unsuccessful authentication with #{header.downcase}" do
+ @request.env[header] = encode_credentials('h4x0r', 'world')
+ get :index
+
+ assert_response :unauthorized
+ assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}"
+ end
+ end
+
+ test "authentication request without credential" do
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "HTTP Basic: Access denied.\n", @response.body
+ assert_equal 'Basic realm="SuperSecret"', @response.headers['WWW-Authenticate']
+ end
+
+ test "authentication request with invalid credential" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials('pretty', 'foo')
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "HTTP Basic: Access denied.\n", @response.body
+ assert_equal 'Basic realm="SuperSecret"', @response.headers['WWW-Authenticate']
+ end
+
+ test "authentication request with valid credential" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials('pretty', 'please')
+ get :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ private
+
+ def encode_credentials(username, password)
+ "Basic #{ActiveSupport::Base64.encode64("#{username}:#{password}")}"
+ end
+end
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
new file mode 100644
index 0000000000..59f7a403b5
--- /dev/null
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -0,0 +1,130 @@
+require 'abstract_unit'
+
+class HttpDigestAuthenticationTest < ActionController::TestCase
+ class DummyDigestController < ActionController::Base
+ before_filter :authenticate, :only => :index
+ before_filter :authenticate_with_request, :only => :display
+
+ USERS = { 'lifo' => 'world', 'pretty' => 'please' }
+
+ def index
+ render :text => "Hello Secret"
+ end
+
+ def display
+ render :text => 'Definitely Maybe'
+ end
+
+ private
+
+ def authenticate
+ authenticate_or_request_with_http_digest("SuperSecret") do |username|
+ # Return the password
+ USERS[username]
+ end
+ end
+
+ def authenticate_with_request
+ if authenticate_with_http_digest("SuperSecret") { |username| USERS[username] }
+ @logged_in = true
+ else
+ request_http_digest_authentication("SuperSecret", "Authentication Failed")
+ end
+ end
+ end
+
+ AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION']
+
+ tests DummyDigestController
+
+ AUTH_HEADERS.each do |header|
+ test "successful authentication with #{header.downcase}" do
+ @request.env[header] = encode_credentials(:username => 'lifo', :password => 'world')
+ get :index
+
+ assert_response :success
+ assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}"
+ end
+ end
+
+ AUTH_HEADERS.each do |header|
+ test "unsuccessful authentication with #{header.downcase}" do
+ @request.env[header] = encode_credentials(:username => 'h4x0r', :password => 'world')
+ get :index
+
+ assert_response :unauthorized
+ assert_equal "HTTP Digest: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}"
+ end
+ end
+
+ test "authentication request without credential" do
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "Authentication Failed", @response.body
+ credentials = decode_credentials(@response.headers['WWW-Authenticate'])
+ assert_equal 'SuperSecret', credentials[:realm]
+ end
+
+ test "authentication request with invalid password" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo')
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "Authentication Failed", @response.body
+ end
+
+ test "authentication request with invalid nonce" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please', :nonce => "xxyyzz")
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "Authentication Failed", @response.body
+ end
+
+ test "authentication request with invalid opaque" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo', :opaque => "xxyyzz")
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "Authentication Failed", @response.body
+ end
+
+ test "authentication request with invalid realm" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo', :realm => "NotSecret")
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "Authentication Failed", @response.body
+ end
+
+ test "authentication request with valid credential" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
+ get :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ private
+
+ def encode_credentials(options)
+ options.reverse_merge!(:nc => "00000001", :cnonce => "0a4f113b")
+ password = options.delete(:password)
+
+ # Perform unautheticated get to retrieve digest parameters to use on subsequent request
+ get :index
+
+ assert_response :unauthorized
+
+ credentials = decode_credentials(@response.headers['WWW-Authenticate'])
+ credentials.merge!(options)
+ credentials.merge!(:uri => "http://#{@request.host}#{@request.env['REQUEST_URI']}")
+ ActionController::HttpAuthentication::Digest.encode_credentials("GET", credentials, password)
+ end
+
+ def decode_credentials(header)
+ ActionController::HttpAuthentication::Digest.decode_credentials(@response.headers['WWW-Authenticate'])
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/controller/middleware_stack_test.rb b/actionpack/test/controller/middleware_stack_test.rb
index 5029f5f609..2a141697da 100644
--- a/actionpack/test/controller/middleware_stack_test.rb
+++ b/actionpack/test/controller/middleware_stack_test.rb
@@ -60,6 +60,12 @@ class MiddlewareStackTest < ActiveSupport::TestCase
assert_equal BazMiddleware, @stack[2].klass
end
+ test "swaps one middleware out for another" do
+ assert_equal FooMiddleware, @stack[0].klass
+ @stack.swap(FooMiddleware, BazMiddleware)
+ assert_equal BazMiddleware, @stack[0].klass
+ end
+
test "active returns all only enabled middleware" do
assert_no_difference "@stack.active.size" do
assert_difference "@stack.size" do
diff --git a/actionpack/test/controller/rack_test.rb b/actionpack/test/controller/rack_test.rb
index 8fd004a9e9..e458ab6738 100644
--- a/actionpack/test/controller/rack_test.rb
+++ b/actionpack/test/controller/rack_test.rb
@@ -57,67 +57,67 @@ class BaseRackTest < Test::Unit::TestCase
@request.env['REQUEST_METHOD'] = 'POST'
@request.env['CONTENT_LENGTH'] = data.length
@request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
- @request.env['RAW_POST_DATA'] = data
+ @request.env['rack.input'] = StringIO.new(data)
end
end
class RackRequestTest < BaseRackTest
def test_proxy_request
- assert_equal 'glu.ttono.us', @request.host_with_port(true)
+ assert_equal 'glu.ttono.us', @request.host_with_port
end
def test_http_host
@env.delete "HTTP_X_FORWARDED_HOST"
@env['HTTP_HOST'] = "rubyonrails.org:8080"
- assert_equal "rubyonrails.org", @request.host(true)
- assert_equal "rubyonrails.org:8080", @request.host_with_port(true)
+ assert_equal "rubyonrails.org", @request.host
+ assert_equal "rubyonrails.org:8080", @request.host_with_port
@env['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org"
- assert_equal "www.secondhost.org", @request.host(true)
+ assert_equal "www.secondhost.org", @request.host
end
def test_http_host_with_default_port_overrides_server_port
@env.delete "HTTP_X_FORWARDED_HOST"
@env['HTTP_HOST'] = "rubyonrails.org"
- assert_equal "rubyonrails.org", @request.host_with_port(true)
+ assert_equal "rubyonrails.org", @request.host_with_port
end
def test_host_with_port_defaults_to_server_name_if_no_host_headers
@env.delete "HTTP_X_FORWARDED_HOST"
@env.delete "HTTP_HOST"
- assert_equal "glu.ttono.us:8007", @request.host_with_port(true)
+ assert_equal "glu.ttono.us:8007", @request.host_with_port
end
def test_host_with_port_falls_back_to_server_addr_if_necessary
@env.delete "HTTP_X_FORWARDED_HOST"
@env.delete "HTTP_HOST"
@env.delete "SERVER_NAME"
- assert_equal "207.7.108.53", @request.host(true)
- assert_equal 8007, @request.port(true)
- assert_equal "207.7.108.53:8007", @request.host_with_port(true)
+ assert_equal "207.7.108.53", @request.host
+ assert_equal 8007, @request.port
+ assert_equal "207.7.108.53:8007", @request.host_with_port
end
def test_host_with_port_if_http_standard_port_is_specified
@env['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:80"
- assert_equal "glu.ttono.us", @request.host_with_port(true)
+ assert_equal "glu.ttono.us", @request.host_with_port
end
def test_host_with_port_if_https_standard_port_is_specified
@env['HTTP_X_FORWARDED_PROTO'] = "https"
@env['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:443"
- assert_equal "glu.ttono.us", @request.host_with_port(true)
+ assert_equal "glu.ttono.us", @request.host_with_port
end
def test_host_if_ipv6_reference
@env.delete "HTTP_X_FORWARDED_HOST"
@env['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]"
- assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host(true)
+ assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
end
def test_host_if_ipv6_reference_with_port
@env.delete "HTTP_X_FORWARDED_HOST"
@env['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]:8008"
- assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host(true)
+ assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
end
def test_cgi_environment_variables
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 8809aa7c34..584b9277c4 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -274,6 +274,9 @@ class TestController < ActionController::Base
def render_explicit_html_template
end
+ def render_implicit_html_template_from_xhr_request
+ end
+
def formatted_html_erb
end
@@ -1010,6 +1013,11 @@ class RenderTest < ActionController::TestCase
end
end
+ def test_should_implicitly_render_html_template_from_xhr_request
+ get :render_implicit_html_template_from_xhr_request, :format => :js
+ assert_equal "Hello HTML!", @response.body
+ end
+
def test_should_render_formatted_template
get :formatted_html_erb
assert_equal 'formatted html erb', @response.body
diff --git a/actionpack/test/controller/request/multipart_params_parsing_test.rb b/actionpack/test/controller/request/multipart_params_parsing_test.rb
index 137fdbee54..054519d0d2 100644
--- a/actionpack/test/controller/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/controller/request/multipart_params_parsing_test.rb
@@ -36,7 +36,7 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest
assert_equal 'bar', params['foo']
file = params['file']
- assert_kind_of StringIO, file
+ assert_kind_of Tempfile, file
assert_equal 'file.txt', file.original_filename
assert_equal "text/plain", file.content_type
assert_equal 'contents', file.read
@@ -77,13 +77,13 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest
assert_equal 'bar', params['foo']
file = params['file']
- assert_kind_of StringIO, file
+ assert_kind_of Tempfile, file
assert_equal 'file.csv', file.original_filename
assert_nil file.content_type
assert_equal 'contents', file.read
file = params['flowers']
- assert_kind_of StringIO, file
+ assert_kind_of Tempfile, file
assert_equal 'flowers.jpg', file.original_filename
assert_equal "image/jpeg", file.content_type
assert_equal 19512, file.size
@@ -101,6 +101,21 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest
assert_equal 19756, files.size
end
+ test "does not create tempfile if no file has been selected" do
+ params = parse_multipart('none')
+ assert_equal %w(files submit-name), params.keys.sort
+ assert_equal 'Larry', params['submit-name']
+ assert_equal nil, params['files']
+ end
+
+ test "parses empty upload file" do
+ params = parse_multipart('empty')
+ assert_equal %w(files submit-name), params.keys.sort
+ assert_equal 'Larry', params['submit-name']
+ assert params['files']
+ assert_equal "", params['files'].read
+ end
+
test "uploads and reads binary file" do
with_test_routing do
fixture = FIXTURE_PATH + "/mona_lisa.jpg"
diff --git a/actionpack/test/controller/request/url_encoded_params_parsing_test.rb b/actionpack/test/controller/request/url_encoded_params_parsing_test.rb
index ee2a239d50..89239687de 100644
--- a/actionpack/test/controller/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/controller/request/url_encoded_params_parsing_test.rb
@@ -150,6 +150,18 @@ class UrlEncodedParamsParsingTest < ActionController::IntegrationTest
assert_parses expected, query
end
+ test "parses params with Safari 2 trailing null character" do
+ query = "selected[]=1&selected[]=2&selected[]=3\0"
+ expected = { "selected" => [ "1", "2", "3" ] }
+ assert_parses expected, query
+ end
+
+ test "parses params with Prototype's hack around Safari 2 trailing null character" do
+ query = "selected[]=1&selected[]=2&selected[]=3&_="
+ expected = { "selected" => [ "1", "2", "3" ] }
+ assert_parses expected, query
+ end
+
test "passes through rack middleware and parses params" do
with_muck_middleware do
assert_parses({ "a" => { "b" => "c" } }, "a[b]=c")
diff --git a/actionpack/test/controller/request_test.rb b/actionpack/test/controller/request_test.rb
index 7097d08076..efe4f136f5 100644
--- a/actionpack/test/controller/request_test.rb
+++ b/actionpack/test/controller/request_test.rb
@@ -14,53 +14,53 @@ class RequestTest < ActiveSupport::TestCase
assert_equal '0.0.0.0', @request.remote_ip
@request.remote_addr = '1.2.3.4'
- assert_equal '1.2.3.4', @request.remote_ip(true)
+ assert_equal '1.2.3.4', @request.remote_ip
@request.remote_addr = '1.2.3.4,3.4.5.6'
- assert_equal '1.2.3.4', @request.remote_ip(true)
+ assert_equal '1.2.3.4', @request.remote_ip
@request.env['HTTP_CLIENT_IP'] = '2.3.4.5'
- assert_equal '1.2.3.4', @request.remote_ip(true)
+ assert_equal '1.2.3.4', @request.remote_ip
@request.remote_addr = '192.168.0.1'
- assert_equal '2.3.4.5', @request.remote_ip(true)
+ assert_equal '2.3.4.5', @request.remote_ip
@request.env.delete 'HTTP_CLIENT_IP'
@request.remote_addr = '1.2.3.4'
@request.env['HTTP_X_FORWARDED_FOR'] = '3.4.5.6'
- assert_equal '1.2.3.4', @request.remote_ip(true)
+ assert_equal '1.2.3.4', @request.remote_ip
@request.remote_addr = '127.0.0.1'
@request.env['HTTP_X_FORWARDED_FOR'] = '3.4.5.6'
- assert_equal '3.4.5.6', @request.remote_ip(true)
+ assert_equal '3.4.5.6', @request.remote_ip
@request.env['HTTP_X_FORWARDED_FOR'] = 'unknown,3.4.5.6'
- assert_equal '3.4.5.6', @request.remote_ip(true)
+ assert_equal '3.4.5.6', @request.remote_ip
@request.env['HTTP_X_FORWARDED_FOR'] = '172.16.0.1,3.4.5.6'
- assert_equal '3.4.5.6', @request.remote_ip(true)
+ assert_equal '3.4.5.6', @request.remote_ip
@request.env['HTTP_X_FORWARDED_FOR'] = '192.168.0.1,3.4.5.6'
- assert_equal '3.4.5.6', @request.remote_ip(true)
+ assert_equal '3.4.5.6', @request.remote_ip
@request.env['HTTP_X_FORWARDED_FOR'] = '10.0.0.1,3.4.5.6'
- assert_equal '3.4.5.6', @request.remote_ip(true)
+ assert_equal '3.4.5.6', @request.remote_ip
@request.env['HTTP_X_FORWARDED_FOR'] = '10.0.0.1, 10.0.0.1, 3.4.5.6'
- assert_equal '3.4.5.6', @request.remote_ip(true)
+ assert_equal '3.4.5.6', @request.remote_ip
@request.env['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,3.4.5.6'
- assert_equal '3.4.5.6', @request.remote_ip(true)
+ assert_equal '3.4.5.6', @request.remote_ip
@request.env['HTTP_X_FORWARDED_FOR'] = 'unknown,192.168.0.1'
- assert_equal 'unknown', @request.remote_ip(true)
+ assert_equal 'unknown', @request.remote_ip
@request.env['HTTP_X_FORWARDED_FOR'] = '9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4'
- assert_equal '3.4.5.6', @request.remote_ip(true)
+ assert_equal '3.4.5.6', @request.remote_ip
@request.env['HTTP_CLIENT_IP'] = '8.8.8.8'
e = assert_raises(ActionController::ActionControllerError) {
- @request.remote_ip(true)
+ @request.remote_ip
}
assert_match /IP spoofing attack/, e.message
assert_match /HTTP_X_FORWARDED_FOR="9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4"/, e.message
@@ -72,11 +72,11 @@ class RequestTest < ActiveSupport::TestCase
# leap of faith to assume that their proxies are ever going to set the
# HTTP_CLIENT_IP/HTTP_X_FORWARDED_FOR headers properly.
ActionController::Base.ip_spoofing_check = false
- assert_equal('8.8.8.8', @request.remote_ip(true))
+ assert_equal('8.8.8.8', @request.remote_ip)
ActionController::Base.ip_spoofing_check = true
@request.env['HTTP_X_FORWARDED_FOR'] = '8.8.8.8, 9.9.9.9'
- assert_equal '8.8.8.8', @request.remote_ip(true)
+ assert_equal '8.8.8.8', @request.remote_ip
@request.env.delete 'HTTP_CLIENT_IP'
@request.env.delete 'HTTP_X_FORWARDED_FOR'
@@ -189,8 +189,8 @@ class RequestTest < ActiveSupport::TestCase
@request.env['PATH_INFO'] = "/path/of/some/uri?mapped=1"
@request.env['SCRIPT_NAME'] = "/path/dispatch.rb"
@request.set_REQUEST_URI nil
- assert_equal "/path/of/some/uri?mapped=1", @request.request_uri(true)
- assert_equal "/of/some/uri", @request.path(true)
+ assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
+ assert_equal "/of/some/uri", @request.path
ActionController::Base.relative_url_root = nil
@request.env['PATH_INFO'] = "/path/of/some/uri"
@@ -225,12 +225,12 @@ class RequestTest < ActiveSupport::TestCase
@request.set_REQUEST_URI '/hieraki/dispatch.cgi'
ActionController::Base.relative_url_root = '/hieraki'
- assert_equal "/dispatch.cgi", @request.path(true)
+ assert_equal "/dispatch.cgi", @request.path
ActionController::Base.relative_url_root = nil
@request.set_REQUEST_URI '/hieraki/dispatch.cgi'
ActionController::Base.relative_url_root = '/foo'
- assert_equal "/hieraki/dispatch.cgi", @request.path(true)
+ assert_equal "/hieraki/dispatch.cgi", @request.path
ActionController::Base.relative_url_root = nil
# This test ensures that Rails uses REQUEST_URI over PATH_INFO
@@ -238,8 +238,8 @@ class RequestTest < ActiveSupport::TestCase
@request.env['REQUEST_URI'] = "/some/path"
@request.env['PATH_INFO'] = "/another/path"
@request.env['SCRIPT_NAME'] = "/dispatch.cgi"
- assert_equal "/some/path", @request.request_uri(true)
- assert_equal "/some/path", @request.path(true)
+ assert_equal "/some/path", @request.request_uri
+ assert_equal "/some/path", @request.path
end
def test_host_with_default_port
@@ -255,13 +255,13 @@ class RequestTest < ActiveSupport::TestCase
end
def test_server_software
- assert_equal nil, @request.server_software(true)
+ assert_equal nil, @request.server_software
@request.env['SERVER_SOFTWARE'] = 'Apache3.422'
- assert_equal 'apache', @request.server_software(true)
+ assert_equal 'apache', @request.server_software
@request.env['SERVER_SOFTWARE'] = 'lighttpd(1.1.4)'
- assert_equal 'lighttpd', @request.server_software(true)
+ assert_equal 'lighttpd', @request.server_software
end
def test_xml_http_request
@@ -299,13 +299,13 @@ class RequestTest < ActiveSupport::TestCase
def test_invalid_http_method_raises_exception
assert_raises(ActionController::UnknownHttpMethod) do
self.request_method = :random_method
+ @request.request_method
end
end
def test_allow_method_hacking_on_post
[:get, :head, :options, :put, :post, :delete].each do |method|
self.request_method = method
- @request.request_method(true)
assert_equal(method == :head ? :get : method, @request.method)
end
end
@@ -313,7 +313,7 @@ class RequestTest < ActiveSupport::TestCase
def test_invalid_method_hacking_on_post_raises_exception
assert_raises(ActionController::UnknownHttpMethod) do
self.request_method = :_random_method
- @request.request_method(true)
+ @request.request_method
end
end
@@ -402,6 +402,6 @@ class RequestTest < ActiveSupport::TestCase
protected
def request_method=(method)
@request.env['REQUEST_METHOD'] = method.to_s.upcase
- @request.request_method(true)
+ @request.request_method = nil # Reset the ivar cache
end
end
diff --git a/actionpack/test/controller/session/cookie_store_test.rb b/actionpack/test/controller/session/cookie_store_test.rb
index d349c18d1d..95d2eb11c4 100644
--- a/actionpack/test/controller/session/cookie_store_test.rb
+++ b/actionpack/test/controller/session/cookie_store_test.rb
@@ -6,13 +6,11 @@ class CookieStoreTest < ActionController::IntegrationTest
SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
DispatcherApp = ActionController::Dispatcher.new
- CookieStoreApp = ActionController::Session::CookieStore.new(DispatcherApp,
- :key => SessionKey, :secret => SessionSecret)
+ CookieStoreApp = ActionController::Session::CookieStore.new(DispatcherApp, :key => SessionKey, :secret => SessionSecret)
Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, 'SHA1')
- SignedBar = "BAh7BjoIZm9vIghiYXI%3D--" +
- "fef868465920f415f2c0652d6910d3af288a0367"
+ SignedBar = "BAh7BjoIZm9vIghiYXI%3D--fef868465920f415f2c0652d6910d3af288a0367"
class TestController < ActionController::Base
def no_session_access
@@ -94,7 +92,7 @@ class CookieStoreTest < ActionController::IntegrationTest
with_test_route_set do
get '/set_session_value'
assert_response :success
- assert_equal ["_myapp_session=#{response.body}; path=/"],
+ assert_equal ["_myapp_session=#{response.body}; path=/; httponly"],
headers['Set-Cookie']
end
end
@@ -148,7 +146,7 @@ class CookieStoreTest < ActionController::IntegrationTest
get '/set_session_value'
assert_response :success
session_payload = response.body
- assert_equal ["_myapp_session=#{response.body}; path=/"],
+ assert_equal ["_myapp_session=#{response.body}; path=/; httponly"],
headers['Set-Cookie']
get '/call_reset_session'
@@ -177,6 +175,36 @@ class CookieStoreTest < ActionController::IntegrationTest
end
end
+ def test_session_store_with_expire_after
+ app = ActionController::Session::CookieStore.new(DispatcherApp, :key => SessionKey, :secret => SessionSecret, :expire_after => 5.hours)
+ @integration_session = open_session(app)
+
+ with_test_route_set do
+ # First request accesses the session
+ time = Time.local(2008, 4, 24)
+ Time.stubs(:now).returns(time)
+ expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT")
+
+ cookies[SessionKey] = SignedBar
+
+ get '/set_session_value'
+ assert_response :success
+
+ cookie_body = response.body
+ assert_equal ["_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; httponly"], headers['Set-Cookie']
+
+ # Second request does not access the session
+ time = Time.local(2008, 4, 25)
+ Time.stubs(:now).returns(time)
+ expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT")
+
+ get '/no_session_access'
+ assert_response :success
+
+ assert_equal ["_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; httponly"], headers['Set-Cookie']
+ end
+ end
+
private
def with_test_route_set
with_routing do |set|
diff --git a/actionpack/test/controller/session/test_session_test.rb b/actionpack/test/controller/session/test_session_test.rb
new file mode 100644
index 0000000000..83103be3ec
--- /dev/null
+++ b/actionpack/test/controller/session/test_session_test.rb
@@ -0,0 +1,58 @@
+require 'abstract_unit'
+require 'stringio'
+
+class ActionController::TestSessionTest < ActiveSupport::TestCase
+
+ def test_calling_delete_without_parameters_raises_deprecation_warning_and_calls_to_clear_test_session
+ assert_deprecated(/use clear instead/){ ActionController::TestSession.new.delete }
+ end
+
+ def test_calling_update_without_parameters_raises_deprecation_warning_and_calls_to_clear_test_session
+ assert_deprecated(/use replace instead/){ ActionController::TestSession.new.update }
+ end
+
+ def test_calling_close_raises_deprecation_warning
+ assert_deprecated(/sessions should no longer be closed/){ ActionController::TestSession.new.close }
+ end
+
+ def test_defaults
+ session = ActionController::TestSession.new
+ assert_equal({}, session.data)
+ assert_equal('', session.session_id)
+ end
+
+ def test_ctor_allows_setting
+ session = ActionController::TestSession.new({:one => 'one', :two => 'two'})
+ assert_equal('one', session[:one])
+ assert_equal('two', session[:two])
+ end
+
+ def test_setting_session_item_sets_item
+ session = ActionController::TestSession.new
+ session[:key] = 'value'
+ assert_equal('value', session[:key])
+ end
+
+ def test_calling_delete_removes item
+ session = ActionController::TestSession.new
+ session[:key] = 'value'
+ assert_equal('value', session[:key])
+ session.delete(:key)
+ assert_nil(session[:key])
+ end
+
+ def test_calling_update_with_params_passes_to_attributes
+ session = ActionController::TestSession.new()
+ session.update('key' => 'value')
+ assert_equal('value', session[:key])
+ end
+
+ def test_clear_emptys_session
+ params = {:one => 'one', :two => 'two'}
+ session = ActionController::TestSession.new({:one => 'one', :two => 'two'})
+ session.clear
+ assert_nil(session[:one])
+ assert_nil(session[:two])
+ end
+
+end \ No newline at end of file
diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb
index ee7b8ade8c..65c894c2e7 100644
--- a/actionpack/test/controller/test_test.rb
+++ b/actionpack/test/controller/test_test.rb
@@ -23,6 +23,11 @@ class TestTest < ActionController::TestCase
render :text => 'Success'
end
+ def reset_the_session
+ reset_session
+ render :text => 'ignore me'
+ end
+
def render_raw_post
raise ActiveSupport::TestCase::Assertion, "#raw_post is blank" if request.raw_post.blank?
render :text => request.raw_post
@@ -171,6 +176,24 @@ XML
assert_equal 'value2', session[:symbol]
end
+ def test_session_is_cleared_from_controller_after_reset_session
+ process :set_session
+ process :reset_the_session
+ assert_equal Hash.new, @controller.session.to_hash
+ end
+
+ def test_session_is_cleared_from_response_after_reset_session
+ process :set_session
+ process :reset_the_session
+ assert_equal Hash.new, @response.session.to_hash
+ end
+
+ def test_session_is_cleared_from_request_after_reset_session
+ process :set_session
+ process :reset_the_session
+ assert_equal Hash.new, @request.session.to_hash
+ end
+
def test_process_with_request_uri_with_no_params
process :test_uri
assert_equal "/test_test/test/test_uri", @response.body
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index e9d372544e..09a8356fec 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -303,7 +303,6 @@ class UrlWriterTests < ActionController::TestCase
def test_named_routes_with_nil_keys
ActionController::Routing::Routes.clear!
- add_host!
ActionController::Routing::Routes.draw do |map|
map.main '', :controller => 'posts'
map.resources :posts
@@ -311,6 +310,8 @@ class UrlWriterTests < ActionController::TestCase
end
# We need to create a new class in order to install the new named route.
kls = Class.new { include ActionController::UrlWriter }
+ kls.default_url_options[:host] = 'www.basecamphq.com'
+
controller = kls.new
params = {:action => :index, :controller => :posts, :format => :xml}
assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params))
@@ -337,6 +338,20 @@ class UrlWriterTests < ActionController::TestCase
ensure
ActionController::Routing::Routes.load!
end
+
+ def test_multiple_includes_maintain_distinct_options
+ first_class = Class.new { include ActionController::UrlWriter }
+ second_class = Class.new { include ActionController::UrlWriter }
+
+ first_host, second_host = 'firsthost.com', 'secondhost.com'
+
+ first_class.default_url_options[:host] = first_host
+ second_class.default_url_options[:host] = second_host
+
+ assert_equal first_class.default_url_options[:host], first_host
+ assert_equal second_class.default_url_options[:host], second_host
+ end
+
private
def extract_params(url)
url.split('?', 2).last.split('&')
diff --git a/actionpack/test/fixtures/multipart/empty b/actionpack/test/fixtures/multipart/empty
new file mode 100644
index 0000000000..f0f79835c9
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/empty
@@ -0,0 +1,10 @@
+--AaB03x
+Content-Disposition: form-data; name="submit-name"
+
+Larry
+--AaB03x
+Content-Disposition: form-data; name="files"; filename="file1.txt"
+Content-Type: text/plain
+
+
+--AaB03x--
diff --git a/actionpack/test/fixtures/multipart/none b/actionpack/test/fixtures/multipart/none
new file mode 100644
index 0000000000..d66f4730f1
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/none
@@ -0,0 +1,9 @@
+--AaB03x
+Content-Disposition: form-data; name="submit-name"
+
+Larry
+--AaB03x
+Content-Disposition: form-data; name="files"; filename=""
+
+
+--AaB03x--
diff --git a/actionpack/test/fixtures/replies.yml b/actionpack/test/fixtures/replies.yml
index a17d2fc42b..66020b706a 100644
--- a/actionpack/test/fixtures/replies.yml
+++ b/actionpack/test/fixtures/replies.yml
@@ -12,4 +12,4 @@ another:
developer_id: 1
content: Nuh uh!
created_at: <%= 1.hour.ago.to_s(:db) %>
- updated_at: nil \ No newline at end of file
+ updated_at: nil
diff --git a/actionpack/test/fixtures/test/hello_world.da.html.erb b/actionpack/test/fixtures/test/hello_world.da.html.erb
new file mode 100644
index 0000000000..10ec443291
--- /dev/null
+++ b/actionpack/test/fixtures/test/hello_world.da.html.erb
@@ -0,0 +1 @@
+Hey verden \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb b/actionpack/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb
new file mode 100644
index 0000000000..4a11845cfe
--- /dev/null
+++ b/actionpack/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb
@@ -0,0 +1 @@
+Hello HTML! \ No newline at end of file
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index 6ec01b7a8f..92cdce2e45 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -1228,6 +1228,38 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal(expected, output_buffer)
end
+ def test_date_select_within_fields_for_with_index
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+ id = 27
+
+ fields_for :post, @post, :index => id do |f|
+ concat f.date_select(:written_on)
+ end
+
+ expected = "<select id='post_#{id}_written_on_1i' name='post[#{id}][written_on(1i)]'>\n<option value='1999'>1999</option>\n<option value='2000'>2000</option>\n<option value='2001'>2001</option>\n<option value='2002'>2002</option>\n<option value='2003'>2003</option>\n<option selected='selected' value='2004'>2004</option>\n<option value='2005'>2005</option>\n<option value='2006'>2006</option>\n<option value='2007'>2007</option>\n<option value='2008'>2008</option>\n<option value='2009'>2009</option>\n</select>\n"
+ expected << "<select id='post_#{id}_written_on_2i' name='post[#{id}][written_on(2i)]'>\n<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option selected='selected' value='6'>June</option>\n<option value='7'>July</option>\n<option value='8'>August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n</select>\n"
+ expected << "<select id='post_#{id}_written_on_3i' name='post[#{id}][written_on(3i)]'>\n<option value='1'>1</option>\n<option value='2'>2</option>\n<option value='3'>3</option>\n<option value='4'>4</option>\n<option value='5'>5</option>\n<option value='6'>6</option>\n<option value='7'>7</option>\n<option value='8'>8</option>\n<option value='9'>9</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option selected='selected' value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n</select>\n"
+
+ assert_dom_equal(expected, output_buffer)
+ end
+
+ def test_date_select_within_fields_for_with_blank_index
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+ id = nil
+
+ fields_for :post, @post, :index => id do |f|
+ concat f.date_select(:written_on)
+ end
+
+ expected = "<select id='post_#{id}_written_on_1i' name='post[#{id}][written_on(1i)]'>\n<option value='1999'>1999</option>\n<option value='2000'>2000</option>\n<option value='2001'>2001</option>\n<option value='2002'>2002</option>\n<option value='2003'>2003</option>\n<option selected='selected' value='2004'>2004</option>\n<option value='2005'>2005</option>\n<option value='2006'>2006</option>\n<option value='2007'>2007</option>\n<option value='2008'>2008</option>\n<option value='2009'>2009</option>\n</select>\n"
+ expected << "<select id='post_#{id}_written_on_2i' name='post[#{id}][written_on(2i)]'>\n<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option selected='selected' value='6'>June</option>\n<option value='7'>July</option>\n<option value='8'>August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n</select>\n"
+ expected << "<select id='post_#{id}_written_on_3i' name='post[#{id}][written_on(3i)]'>\n<option value='1'>1</option>\n<option value='2'>2</option>\n<option value='3'>3</option>\n<option value='4'>4</option>\n<option value='5'>5</option>\n<option value='6'>6</option>\n<option value='7'>7</option>\n<option value='8'>8</option>\n<option value='9'>9</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option selected='selected' value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n</select>\n"
+
+ assert_dom_equal(expected, output_buffer)
+ end
+
def test_date_select_with_index
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
@@ -1243,7 +1275,6 @@ class DateHelperTest < ActionView::TestCase
expected << %{<select id="post_456_written_on_3i" name="post[#{id}][written_on(3i)]">\n}
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
-
expected << "</select>\n"
assert_dom_equal expected, date_select("post", "written_on", :index => id)
@@ -1330,13 +1361,13 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, date_select("post", "written_on", :include_blank => true)
end
-
+
def test_date_select_with_nil_and_blank_and_order
@post = Post.new
start_year = Time.now.year-5
end_year = Time.now.year+5
-
+
expected = '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i"/>' + "\n"
expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
expected << "<option value=\"\"></option>\n"
@@ -1966,6 +1997,40 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, datetime_select("post", "updated_at", :index => id)
end
+ def test_datetime_select_within_fields_for_with_options_index
+ @post = Post.new
+ @post.updated_at = Time.local(2004, 6, 15, 16, 35)
+ id = 456
+
+ fields_for :post, @post, :index => id do |f|
+ concat f.datetime_select(:updated_at)
+ end
+
+ expected = %{<select id="post_456_updated_at_1i" name="post[#{id}][updated_at(1i)]">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_456_updated_at_2i" name="post[#{id}][updated_at(2i)]">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_456_updated_at_3i" name="post[#{id}][updated_at(3i)]">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_456_updated_at_4i" name="post[#{id}][updated_at(4i)]">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n}
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_456_updated_at_5i" name="post[#{id}][updated_at(5i)]">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35" selected="selected">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n}
+ expected << "</select>\n"
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_datetime_select_with_auto_index
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 16, 35)
@@ -2253,7 +2318,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2008, 7, 16, 23, 30)
- options = {
+ options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2265,7 +2330,7 @@ class DateHelperTest < ActionView::TestCase
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
- assert_equal({
+ assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2279,7 +2344,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2008, 7, 16, 23, 30)
- options = {
+ options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2291,7 +2356,7 @@ class DateHelperTest < ActionView::TestCase
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
- assert_equal({
+ assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2305,7 +2370,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2008, 7, 16, 23, 30)
- options = {
+ options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2317,7 +2382,7 @@ class DateHelperTest < ActionView::TestCase
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
- assert_equal({
+ assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2328,7 +2393,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_should_not_change_passed_options_hash
- options = {
+ options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2340,7 +2405,7 @@ class DateHelperTest < ActionView::TestCase
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
- assert_equal({
+ assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2351,7 +2416,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_datetime_should_not_change_passed_options_hash
- options = {
+ options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2363,7 +2428,7 @@ class DateHelperTest < ActionView::TestCase
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
- assert_equal({
+ assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2374,7 +2439,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_time_should_not_change_passed_options_hash
- options = {
+ options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2386,7 +2451,7 @@ class DateHelperTest < ActionView::TestCase
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
- assert_equal({
+ assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 86a0bb6a79..86b321e6e5 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -143,6 +143,32 @@ uses_mocha "FormOptionsHelperTest" do
)
end
+ def test_grouped_options_for_select_with_array
+ assert_dom_equal(
+ "<optgroup label=\"North America\"><option value=\"US\">United States</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"Europe\"><option value=\"GB\">Great Britain</option>\n<option value=\"Germany\">Germany</option></optgroup>",
+ grouped_options_for_select([
+ ["North America",
+ [['United States','US'],"Canada"]],
+ ["Europe",
+ [["Great Britain","GB"], "Germany"]]
+ ])
+ )
+ end
+
+ def test_grouped_options_for_select_with_selected_and_prompt
+ assert_dom_equal(
+ "<option value=\"\">Choose a product...</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",
+ grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", "Choose a product...")
+ )
+ end
+
+ def test_optgroups_with_with_options_with_hash
+ assert_dom_equal(
+ "<optgroup label=\"Europe\"><option value=\"Denmark\">Denmark</option>\n<option value=\"Germany\">Germany</option></optgroup><optgroup label=\"North America\"><option value=\"United States\">United States</option>\n<option value=\"Canada\">Canada</option></optgroup>",
+ grouped_options_for_select({'North America' => ['United States','Canada'], 'Europe' => ['Denmark','Germany']})
+ )
+ end
+
def test_time_zone_options_no_parms
opts = time_zone_options_for_select
assert_dom_equal "<option value=\"A\">A</option>\n" +
@@ -473,6 +499,22 @@ uses_mocha "FormOptionsHelperTest" do
assert_dom_equal expected, collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true, :name => 'post[author_name][]' }, :multiple => true)
end
+ def test_collection_select_with_blank_and_selected
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
+
+ @post = Post.new
+ @post.author_name = "Babe"
+
+ assert_dom_equal(
+ %{<select id="post_author_name" name="post[author_name]"><option value=""></option>\n<option value="&lt;Abe&gt;" selected="selected">&lt;Abe&gt;</option>\n<option value="Babe">Babe</option>\n<option value="Cabe">Cabe</option></select>},
+ collection_select("post", "author_name", @posts, "author_name", "author_name", {:include_blank => true, :selected => "<Abe>"})
+ )
+ end
+
def test_time_zone_select
@firm = Firm.new("D")
html = time_zone_select( "firm", "time_zone" )
diff --git a/actionpack/test/template/number_helper_i18n_test.rb b/actionpack/test/template/number_helper_i18n_test.rb
index 2528bead36..3fdf991a44 100644
--- a/actionpack/test/template/number_helper_i18n_test.rb
+++ b/actionpack/test/template/number_helper_i18n_test.rb
@@ -10,7 +10,9 @@ class NumberHelperI18nTests < Test::Unit::TestCase
@number_defaults = { :precision => 3, :delimiter => ',', :separator => '.' }
@currency_defaults = { :unit => '$', :format => '%u%n', :precision => 2 }
@human_defaults = { :precision => 1 }
- @human_storage_units_defaults = %w(Bytes KB MB GB TB)
+ @human_storage_units_format_default = "%n %u"
+ @human_storage_units_units_byte_other = "Bytes"
+ @human_storage_units_units_kb_other = "KB"
@percentage_defaults = { :delimiter => '' }
@precision_defaults = { :delimiter => '' }
@@ -48,10 +50,22 @@ class NumberHelperI18nTests < Test::Unit::TestCase
I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults)
I18n.expects(:translate).with(:'number.human.format', :locale => 'en',
:raise => true).returns(@human_defaults)
- I18n.expects(:translate).with(:'number.human.storage_units', :locale => 'en',
- :raise => true).returns(@human_storage_units_defaults)
- # can't be called with 1 because this directly returns without calling I18n.translate
- number_to_human_size(1025, :locale => 'en')
+ I18n.expects(:translate).with(:'number.human.storage_units.format', :locale => 'en',
+ :raise => true).returns(@human_storage_units_format_default)
+ I18n.expects(:translate).with(:'number.human.storage_units.units.kb', :locale => 'en', :count => 2,
+ :raise => true).returns(@human_storage_units_units_kb_other)
+ # 2KB
+ number_to_human_size(2048, :locale => 'en')
+
+ I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults)
+ I18n.expects(:translate).with(:'number.human.format', :locale => 'en',
+ :raise => true).returns(@human_defaults)
+ I18n.expects(:translate).with(:'number.human.storage_units.format', :locale => 'en',
+ :raise => true).returns(@human_storage_units_format_default)
+ I18n.expects(:translate).with(:'number.human.storage_units.units.byte', :locale => 'en', :count => 42,
+ :raise => true).returns(@human_storage_units_units_byte_other)
+ # 42 Bytes
+ number_to_human_size(42, :locale => 'en')
end
end
end
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 4bd897efeb..c226e212b5 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -5,6 +5,13 @@ module RenderTestCases
def setup_view(paths)
@assigns = { :secret => 'in the sauce' }
@view = ActionView::Base.new(paths, @assigns)
+
+ # Reload and register danish language for testing
+ I18n.reload!
+ I18n.backend.store_translations 'da', {}
+
+ # Ensure original are still the same since we are reindexing view paths
+ assert_equal ORIGINAL_LOCALES, I18n.available_locales
end
def test_render_file
@@ -19,6 +26,14 @@ module RenderTestCases
assert_equal "Hello world!", @view.render(:file => "test/hello_world")
end
+ def test_render_file_with_localization
+ old_locale = I18n.locale
+ I18n.locale = :da
+ assert_equal "Hey verden", @view.render(:file => "test/hello_world")
+ ensure
+ I18n.locale = old_locale
+ end
+
def test_render_file_at_top_level
assert_equal 'Elastica', @view.render(:file => '/shared')
end
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index a6200fbdd7..564845779f 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -122,6 +122,29 @@ class TextHelperTest < ActionView::TestCase
)
end
+ def test_highlight_with_html
+ assert_equal(
+ "<p>This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
+ highlight("<p>This is a beautiful morning, but also a beautiful day</p>", "beautiful")
+ )
+ assert_equal(
+ "<p>This is a <em><strong class=\"highlight\">beautiful</strong></em> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
+ highlight("<p>This is a <em>beautiful</em> morning, but also a beautiful day</p>", "beautiful")
+ )
+ assert_equal(
+ "<p>This is a <em class=\"error\"><strong class=\"highlight\">beautiful</strong></em> morning, but also a <strong class=\"highlight\">beautiful</strong> <span class=\"last\">day</span></p>",
+ highlight("<p>This is a <em class=\"error\">beautiful</em> morning, but also a beautiful <span class=\"last\">day</span></p>", "beautiful")
+ )
+ assert_equal(
+ "<p class=\"beautiful\">This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
+ highlight("<p class=\"beautiful\">This is a beautiful morning, but also a beautiful day</p>", "beautiful")
+ )
+ assert_equal(
+ "<p>This is a <strong class=\"highlight\">beautiful</strong> <a href=\"http://example.com/beautiful\#top?what=beautiful%20morning&when=now+then\">morning</a>, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
+ highlight("<p>This is a beautiful <a href=\"http://example.com/beautiful\#top?what=beautiful%20morning&when=now+then\">morning</a>, but also a beautiful day</p>", "beautiful")
+ )
+ end
+
def test_excerpt
assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", 5))
assert_equal("This is a...", excerpt("This is a beautiful morning", "this", 5))