Skip to content
Browse files

Merge branch 'master' of git@github.com:lifo/docrails

  • Loading branch information...
2 parents 6623e4f + 24b688d commit 43d63298f7693a437b454b4b8ee84946af350572 Ahmed El-Daly committed Jan 29, 2009
Showing with 3,737 additions and 967 deletions.
  1. +36 −0 actionpack/CHANGELOG
  2. +0 −1 actionpack/lib/action_controller.rb
  3. +3 −8 actionpack/lib/action_controller/base.rb
  4. +7 −0 actionpack/lib/action_controller/caching/fragments.rb
  5. +2 −2 actionpack/lib/action_controller/caching/sweeping.rb
  6. +173 −23 actionpack/lib/action_controller/http_authentication.rb
  7. +1 −1 actionpack/lib/action_controller/layout.rb
  8. +10 −5 actionpack/lib/action_controller/middleware_stack.rb
  9. +3 −5 actionpack/lib/action_controller/middlewares.rb
  10. +15 −27 actionpack/lib/action_controller/request.rb
  11. +2 −2 actionpack/lib/action_controller/rewindable_input.rb
  12. +4 −2 actionpack/lib/action_controller/session/abstract_store.rb
  13. +5 −5 actionpack/lib/action_controller/session/cookie_store.rb
  14. +29 −19 actionpack/lib/action_controller/test_process.rb
  15. +1 −1 actionpack/lib/action_controller/url_encoded_pair_parser.rb
  16. +3 −6 actionpack/lib/action_controller/url_rewriter.rb
  17. +58 −1 actionpack/lib/action_view/helpers/form_options_helper.rb
  18. +28 −17 actionpack/lib/action_view/helpers/number_helper.rb
  19. +1 −1 actionpack/lib/action_view/helpers/text_helper.rb
  20. +12 −1 actionpack/lib/action_view/locale/en.yml
  21. +8 −1 actionpack/lib/action_view/paths.rb
  22. +42 −14 actionpack/lib/action_view/template.rb
  23. +4 −0 actionpack/test/abstract_unit.rb
  24. +0 −54 actionpack/test/controller/http_authentication_test.rb
  25. +88 −0 actionpack/test/controller/http_basic_authentication_test.rb
  26. +130 −0 actionpack/test/controller/http_digest_authentication_test.rb
  27. +6 −0 actionpack/test/controller/middleware_stack_test.rb
  28. +13 −13 actionpack/test/controller/rack_test.rb
  29. +8 −0 actionpack/test/controller/render_test.rb
  30. +15 −0 actionpack/test/controller/request/multipart_params_parsing_test.rb
  31. +29 −29 actionpack/test/controller/request_test.rb
  32. +34 −6 actionpack/test/controller/session/cookie_store_test.rb
  33. +16 −1 actionpack/test/controller/url_rewriter_test.rb
  34. +10 −0 actionpack/test/fixtures/multipart/empty
  35. +9 −0 actionpack/test/fixtures/multipart/none
  36. +1 −1 actionpack/test/fixtures/replies.yml
  37. +1 −0 actionpack/test/fixtures/test/hello_world.da.html.erb
  38. +1 −0 actionpack/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb
  39. +42 −0 actionpack/test/template/form_options_helper_test.rb
  40. +19 −5 actionpack/test/template/number_helper_i18n_test.rb
  41. +15 −0 actionpack/test/template/render_test.rb
  42. +23 −0 actionpack/test/template/text_helper_test.rb
  43. +6 −2 activerecord/Rakefile
  44. +7 −5 activerecord/lib/active_record/associations.rb
  45. +24 −5 activerecord/lib/active_record/base.rb
  46. +5 −2 activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
  47. +1 −1 activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
  48. +1 −1 activerecord/lib/active_record/validations.rb
  49. +7 −0 activerecord/test/cases/base_test.rb
  50. +26 −0 activerecord/test/cases/connection_test_mysql.rb
  51. +11 −0 activerecord/test/cases/copy_table_test_sqlite.rb
  52. +10 −0 activerecord/test/cases/method_scoping_test.rb
  53. +20 −0 activerecord/test/cases/named_scope_test.rb
  54. +18 −0 activerecord/test/connections/jdbc_jdbcderby/connection.rb
  55. +18 −0 activerecord/test/connections/jdbc_jdbch2/connection.rb
  56. +18 −0 activerecord/test/connections/jdbc_jdbchsqldb/connection.rb
  57. +26 −0 activerecord/test/connections/jdbc_jdbcmysql/connection.rb
  58. +26 −0 activerecord/test/connections/jdbc_jdbcpostgresql/connection.rb
  59. +25 −0 activerecord/test/connections/jdbc_jdbcsqlite3/connection.rb
  60. +6 −0 activerecord/test/models/post.rb
  61. +2 −0 activerecord/test/models/topic.rb
  62. +5 −0 activerecord/test/schema/schema.rb
  63. +3 −3 activeresource/lib/active_resource/base.rb
  64. +9 −0 activeresource/test/base/equality_test.rb
  65. +14 −1 activeresource/test/base_test.rb
  66. +1 −1 activesupport/lib/active_support/cache/strategy/local_cache.rb
  67. +9 −4 activesupport/lib/active_support/core_ext/try.rb
  68. +4 −0 activesupport/lib/active_support/ordered_hash.rb
  69. +5 −0 activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n.rb
  70. +7 −1 activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/backend/simple.rb
  71. +22 −0 activesupport/lib/active_support/vendor/i18n-0.1.1/test/simple_backend_test.rb
  72. +12 −0 activesupport/test/caching_test.rb
  73. +2 −2 ci/geminstaller.yml
  74. +2 −2 railties/CHANGELOG
  75. +892 −0 railties/doc/guides/html/2_3_release_notes.html
  76. +37 −0 railties/doc/guides/html/action_mailer_basics.html
  77. +48 −4 railties/doc/guides/html/actioncontroller_basics.html
  78. +215 −120 railties/doc/guides/html/active_record_basics.html
  79. +7 −25 railties/doc/guides/html/creating_plugins.html
  80. +132 −111 railties/doc/guides/html/form_helpers.html
  81. +242 −95 railties/doc/guides/html/i18n.html
  82. +5 −5 railties/doc/guides/html/index.html
  83. +399 −0 railties/doc/guides/source/2_3_release_notes.txt
  84. +5 −1 railties/doc/guides/source/action_mailer_basics.txt
  85. +36 −3 railties/doc/guides/source/actioncontroller_basics/http_auth.txt
  86. +93 −109 railties/doc/guides/source/active_record_basics.txt
  87. +4 −0 railties/doc/guides/source/creating_plugins/creating_plugins/lib/main.rb
  88. +1 −1 railties/doc/guides/source/creating_plugins/generator_commands.txt
  89. +2 −21 railties/doc/guides/source/creating_plugins/migrations.txt
  90. +1 −1 railties/doc/guides/source/creating_plugins/models.txt
  91. +2 −2 railties/doc/guides/source/creating_plugins/tasks.txt
  92. +0 −3 railties/doc/guides/source/creating_plugins/test_setup.txt
  93. +117 −105 railties/doc/guides/source/form_helpers.txt
  94. +206 −67 railties/doc/guides/source/i18n.txt
  95. +8 −8 railties/doc/guides/source/index.txt
  96. +17 −1 railties/lib/commands/dbconsole.rb
  97. +6 −2 railties/lib/initializer.rb
  98. +2 −2 railties/lib/tasks/gems.rake
  99. +1 −0 railties/lib/tasks/misc.rake
View
36 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]
View
1 actionpack/lib/action_controller.rb
@@ -63,7 +63,6 @@ def self.load_all!
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'
View
11 actionpack/lib/action_controller/base.rb
@@ -644,7 +644,7 @@ def controller_path
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 = []
@@ -1315,10 +1315,6 @@ def complete_request_uri
"#{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
@@ -1342,15 +1338,14 @@ def template_path_includes_controller?(path)
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
View
7 actionpack/lib/action_controller/caching/fragments.rb
@@ -25,6 +25,13 @@ module Caching
# The expiration call for this example is:
#
# expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")
+ #
+ # You can also pass a options hash to the call, which is passed on to the read and write methods of your fragment cache store. For example,
+ # if you are using the MemCacheStore, then you can pass the :expire_in option to make the fragment expire in a certain amount of time.
+ #
+ # <% cache "latest_photos", :expires_in => 5.minutes do %>
+ # <%= render :partial => "photo", :collection => Photo.latest%>
+ # <% end%>
module Fragments
# Given a key (as described in <tt>expire_fragment</tt>), returns a key suitable for use in reading,
# writing, or expiring a cached fragment. If the key is a hash, the generated key is the return
View
4 actionpack/lib/action_controller/caching/sweeping.rb
@@ -87,9 +87,9 @@ def callback(timing)
__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
View
196 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 HttpAuthentication
# 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 @@ def authenticate(controller, &login_procedure)
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 @@ def authentication_request(controller, realm)
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
View
2 actionpack/lib/action_controller/layout.rb
@@ -179,7 +179,7 @@ def default_layout(format) #: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:
View
15 actionpack/lib/action_controller/middleware_stack.rb
@@ -75,17 +75,22 @@ def initialize(*args, &block)
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)
View
8 actionpack/lib/action_controller/middlewares.rb
@@ -4,8 +4,6 @@
use "ActionController::Failsafe"
-use "ActiveRecord::QueryCache", :if => lambda { defined?(ActiveRecord) }
-
["ActionController::Session::CookieStore",
"ActionController::Session::MemCacheStore",
"ActiveRecord::SessionStore"].each do |store|
@@ -18,6 +16,6 @@
)
end
-use ActionController::RewindableInput
-use ActionController::ParamsParser
-use Rack::MethodOverride
+use "ActionController::RewindableInput"
+use "ActionController::ParamsParser"
+use "Rack::MethodOverride"
View
42 actionpack/lib/action_controller/request.rb
@@ -7,7 +7,6 @@
module ActionController
class Request < Rack::Request
- extend ActiveSupport::Memoizable
%w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
@@ -33,9 +32,8 @@ def key?(key)
# <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
# Returns the HTTP request \method used for action processing as a
# lowercase symbol, such as <tt>:post</tt>. (Unlike #request_method, this
@@ -75,9 +73,8 @@ def head?
#
# 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
@@ -89,32 +86,33 @@ def content_length
# For backward compatibility, the post \format is extracted from the
# X-Post-Data-Format HTTP header if present.
def content_type
- if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
- Mime::Type.lookup($1.strip.downcase)
- else
- nil
+ @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']
@@ -248,25 +246,21 @@ def remote_ip
@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?
@@ -286,14 +280,12 @@ def raw_host_with_port
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
@@ -303,7 +295,6 @@ def port
standard_port
end
end
- memoize :port
# Returns the standard \port number for this request's protocol.
def standard_port
@@ -341,7 +332,6 @@ def subdomains(tld_length = 1)
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.
@@ -367,7 +357,6 @@ def request_uri
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.
@@ -376,7 +365,6 @@ def path
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.
View
4 actionpack/lib/action_controller/rewindable_input.rb
@@ -3,12 +3,12 @@ class RewindableInput
class RewindableIO < ActiveSupport::BasicObject
def initialize(io)
@io = io
- @rewindable = io.is_a?(StringIO)
+ @rewindable = io.is_a?(::StringIO)
end
def method_missing(method, *args, &block)
unless @rewindable
- @io = StringIO.new(@io.read)
+ @io = ::StringIO.new(@io.read)
@rewindable = true
end
View
6 actionpack/lib/action_controller/session/abstract_store.rb
@@ -102,8 +102,10 @@ def call(env)
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
View
10 actionpack/lib/action_controller/session/cookie_store.rb
@@ -45,7 +45,7 @@ class CookieStore
:domain => nil,
:path => "/",
:expire_after => nil,
- :httponly => false
+ :httponly => true
}.freeze
ENV_SESSION_KEY = "rack.session".freeze
@@ -56,8 +56,6 @@ class CookieStore
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 @@ def call(env)
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?
View
48 actionpack/lib/action_controller/test_process.rb
@@ -35,7 +35,6 @@ def raw_post
def port=(number)
@env["SERVER_PORT"] = number.to_i
- port(true)
end
def action=(action_name)
@@ -49,18 +48,20 @@ def set_REQUEST_URI(value)
@env["REQUEST_URI"] = value
@request_uri = nil
@path = nil
- request_uri(true)
- path(true)
end
def request_uri=(uri)
@request_uri = uri
@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 @@ def remote_addr=(addr)
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 @@ def assign_parameters(controller_path, action, parameters)
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,47 @@ def recycle!
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
+ attributes ||= {}
+ replace(attributes.stringify_keys)
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
end
View
2 actionpack/lib/action_controller/url_encoded_pair_parser.rb
@@ -46,7 +46,7 @@ def get_typed_value(value)
when Array
value.map { |v| get_typed_value(v) }
when Hash
- if value.has_key?(:tempfile)
+ if value.has_key?(:tempfile) && value[:filename].any?
upload = value[:tempfile]
upload.extend(UploadedFile)
upload.original_path = value[:filename]
View
9 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
View
59 actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -277,6 +277,62 @@ def option_groups_from_collection_for_select(collection, group_method, group_lab
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 @@ def to_collection_select_tag(collection, value_method, text_method, options, htm
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
View
45 actionpack/lib/action_view/helpers/number_helper.rb
@@ -220,6 +220,8 @@ def number_with_precision(number, *args)
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,15 +249,14 @@ def number_with_precision(number, *args)
# 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!
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 @@ def number_to_human_size(number, *args)
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
View
2 actionpack/lib/action_view/helpers/text_helper.rb
@@ -107,7 +107,7 @@ def highlight(text, phrases, *args)
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
View
13 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:
View
9 actionpack/lib/action_view/paths.rb
@@ -37,10 +37,17 @@ def find_template(original_template_path, format = nil)
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
View
56 actionpack/lib/action_view/template.rb
@@ -93,13 +93,14 @@ def self.exempt_from_layout(*extensions)
@@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 @@ def mime_type
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
@@ -207,6 +208,10 @@ def valid_extension?(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)
load_paths = Array(load_paths) + [nil]
load_paths.each do |load_path|
@@ -217,19 +222,42 @@ def find_full_path(path, load_paths)
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
View
4 actionpack/test/abstract_unit.rb
@@ -32,6 +32,10 @@
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
View
54 actionpack/test/controller/http_authentication_test.rb
@@ -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
View
88 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
View
130 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
View
6 actionpack/test/controller/middleware_stack_test.rb
@@ -60,6 +60,12 @@ def setup
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
View
26 actionpack/test/controller/rack_test.rb
@@ -63,61 +63,61 @@ def set_content_data(data)
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
View
8 actionpack/test/controller/render_test.rb
@@ -274,6 +274,9 @@ def render_implicit_html_template
def render_explicit_html_template
end
+ def render_implicit_html_template_from_xhr_request
+ end
+
def formatted_html_erb
end
@@ -1010,6 +1013,11 @@ def test_explicitly_rendering_an_html_template_with_implicit_html_template_rende
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
View
15 actionpack/test/controller/request/multipart_params_parsing_test.rb
@@ -101,6 +101,21 @@ def teardown
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"
View
58 actionpack/test/controller/request_test.rb
@@ -14,53 +14,53 @@ def test_remote_ip
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 @@ def test_remote_ip
# 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 @@ def test_request_uri
@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,21 +225,21 @@ def test_request_uri
@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
ActionController::Base.relative_url_root = nil
@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 @@ def test_host_with_non_default_port
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,21 +299,21 @@ def test_symbolized_request_methods
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
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 @@ def test_parameters
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
View
40 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 @@ def test_setting_session_value
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 @@ def test_setting_session_value_after_session_reset
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 @@ def test_persistent_session_id
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