Merge branch 'master' of github.com:rails/rails

Conflicts:
	guides/source/security.md
This commit is contained in:
Vijay Dev 2015-08-24 06:05:07 +00:00
commit 4f252cddc1
456 changed files with 7475 additions and 4943 deletions

@ -21,16 +21,12 @@ env:
- "GEM=aj:integration"
- "GEM=guides"
rvm:
- 2.2.2
- 2.2.3
- ruby-head
- rbx-2
- jruby-head
matrix:
allow_failures:
- env: "GEM=ar:mysql"
- rvm: ruby-head
- rvm: rbx-2
- rvm: jruby-head
fast_finish: true
notifications:
email: false

12
CODE_OF_CONDUCT.md Normal file

@ -0,0 +1,12 @@
# Contributor Code of Conduct
The Rails team is committed to fostering a welcoming community.
**Our Code of Conduct can be found here**:
http://rubyonrails.org/conduct/
For a history of updates, see the page history here:
https://github.com/rails/rails.github.com/commits/master/conduct/index.html

@ -7,6 +7,7 @@ gem 'rake', '>= 10.3'
# Active Job depends on the URI::GID::MissingModelIDError, which isn't released yet.
gem 'globalid', github: 'rails/globalid'
gem 'rack', github: 'rack/rack'
# This needs to be with require false as it is
# loaded after loading the test library to
@ -20,8 +21,9 @@ gem 'turbolinks'
gem 'arel', github: 'rails/arel', branch: 'master'
gem 'mail', github: 'mikel/mail'
gem 'sprockets', '~> 3.0.0.rc.1'
gem 'sprockets', github: 'rails/sprockets', branch: 'master'
gem 'sprockets-rails', github: 'rails/sprockets-rails', branch: 'master'
gem 'sass-rails', github: 'rails/sass-rails', branch: 'master'
# require: false so bcrypt is loaded only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework)
@ -31,6 +33,7 @@ gem 'bcrypt', '~> 3.1.10', require: false
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
gem 'uglifier', '>= 1.3.0', require: false
gem 'sass', '>= 3.3', require: false
group :doc do
gem 'sdoc', '~> 0.4.0'
@ -49,7 +52,7 @@ group :job do
gem 'sidekiq', require: false
gem 'sucker_punch', require: false
gem 'delayed_job', require: false
gem 'queue_classic', require: false, platforms: :ruby
gem 'queue_classic', github: "QueueClassic/queue_classic", require: false, platforms: :ruby
gem 'sneakers', require: false
gem 'que', require: false
gem 'backburner', require: false

@ -1,3 +1,10 @@
GIT
remote: git://github.com/QueueClassic/queue_classic.git
revision: d144db29f1436e9e8b3c7a1a1ecd4442316a9ecd
specs:
queue_classic (3.2.0.alpha)
pg (>= 0.17, < 0.19)
GIT
remote: git://github.com/bkeepers/qu.git
revision: d098e2657c92e89a6413bebd9c033930759c061f
@ -13,44 +20,70 @@ GIT
GIT
remote: git://github.com/mikel/mail.git
revision: b159e0a542962fdd5e292a48cfffa560d7cf412e
revision: 64ef1a12efcdda53fd63e1456c2c564044bf82ce
specs:
mail (2.6.3.edge)
mime-types (>= 1.16, < 3)
GIT
remote: git://github.com/rack/rack.git
revision: c94e22401d4719b4d78378c7b63362cd692f9005
specs:
rack (2.0.0.alpha)
json
GIT
remote: git://github.com/rails/arel.git
revision: aac9da257f291ad8d2d4f914528881c240848bb2
revision: d5432b4616ff43fbb14540d351eed351e21bb20e
branch: master
specs:
arel (7.0.0.alpha)
GIT
remote: git://github.com/rails/globalid.git
revision: 4df66fb9e9f0c832d29119aa8bc30be55a614b71
revision: 8178ff2dc898a8f49dd71f6eb46f2a09712462de
specs:
globalid (0.3.5)
globalid (0.3.6)
activesupport (>= 4.1.0)
GIT
remote: git://github.com/rails/jquery-rails.git
revision: 272abdd319bb3381b23182b928b25320590096b0
revision: 38053f45402f1ccc4cce5dd8c7005ec707376db3
branch: master
specs:
jquery-rails (4.0.3)
jquery-rails (4.0.4)
rails-dom-testing (~> 1.0)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
GIT
remote: git://github.com/rails/sprockets-rails.git
revision: 85b89c44ad40af3056899808475e6e4bf65c1f5a
remote: git://github.com/rails/sass-rails.git
revision: 805cb17722b8a13ff00dffe20283a6ba2c9a45dc
branch: master
specs:
sprockets-rails (3.0.0.beta1)
sass-rails (6.0.0)
railties (>= 4.0.0, < 5.0)
sass (~> 3.3)
sprockets (>= 4.0)
sprockets-rails (< 4.0)
GIT
remote: git://github.com/rails/sprockets-rails.git
revision: e65e088575be4f6b994823b80a277affb0a57a5e
branch: master
specs:
sprockets-rails (3.0.0.beta2)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0, < 4.0)
sprockets (>= 3.0.0)
GIT
remote: git://github.com/rails/sprockets.git
revision: a61550db7184fc83964fb983547ff09da9efa9be
branch: master
specs:
sprockets (4.0.0)
rack (~> 2.x)
PATH
remote: .
@ -64,7 +97,7 @@ PATH
actionpack (5.0.0.alpha)
actionview (= 5.0.0.alpha)
activesupport (= 5.0.0.alpha)
rack (~> 1.6)
rack (~> 2.x)
rack-test (~> 0.6.3)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
@ -85,8 +118,10 @@ PATH
activesupport (= 5.0.0.alpha)
arel (= 7.0.0.alpha)
activesupport (5.0.0.alpha)
concurrent-ruby (~> 0.9.0)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
method_source
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
@ -111,83 +146,73 @@ PATH
GEM
remote: https://rubygems.org/
specs:
amq-protocol (1.9.2)
backburner (0.4.6)
beaneater (~> 0.3.1)
dante (~> 0.1.5)
amq-protocol (2.0.0)
backburner (1.0.0)
beaneater (~> 1.0)
dante (> 0.1.5)
bcrypt (3.1.10)
bcrypt (3.1.10-x64-mingw32)
bcrypt (3.1.10-x86-mingw32)
beaneater (0.3.3)
benchmark-ips (2.1.1)
beaneater (1.0.0)
benchmark-ips (2.3.0)
builder (3.2.2)
bunny (1.7.0)
bunny (2.0.0)
amq-protocol (>= 1.9.2)
byebug (4.0.5)
columnize (= 0.9.0)
byebug (6.0.2)
celluloid (0.16.0)
timers (~> 4.0.0)
coffee-rails (4.1.0)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.0)
coffee-script (2.3.0)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.9.0)
columnize (0.9.0)
connection_pool (2.1.1)
dalli (2.7.2)
dante (0.1.5)
coffee-script-source (1.9.1.1)
concurrent-ruby (0.9.1)
connection_pool (2.2.0)
dalli (2.7.4)
dante (0.2.0)
delayed_job (4.0.6)
activesupport (>= 3.0, < 5.0)
delayed_job_active_record (4.0.3)
activerecord (>= 3.0, < 5.0)
delayed_job (>= 3.0, < 4.1)
erubis (2.7.0)
execjs (2.3.0)
execjs (2.6.0)
hitimes (1.2.2)
hitimes (1.2.2-x86-mingw32)
i18n (0.7.0)
json (1.8.2)
json (1.8.3)
kindlerb (0.1.1)
mustache
nokogiri
loofah (2.0.1)
loofah (2.0.3)
nokogiri (>= 1.5.9)
metaclass (0.0.4)
method_source (0.8.2)
mime-types (2.4.3)
mime-types (2.6.1)
mini_portile (0.6.2)
minitest (5.3.3)
mocha (0.14.0)
metaclass (~> 0.0.1)
mono_logger (1.1.0)
multi_json (1.11.0)
mustache (1.0.0)
multi_json (1.11.2)
mustache (1.0.2)
mysql (2.9.1)
mysql2 (0.3.18)
mysql2 (0.3.19)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
nokogiri (1.6.6.2-x64-mingw32)
mini_portile (~> 0.6.0)
nokogiri (1.6.6.2-x86-mingw32)
mini_portile (~> 0.6.0)
pg (0.18.1)
psych (2.0.13)
que (0.9.2)
queue_classic (3.1.0)
pg (>= 0.17, < 0.19)
pg (0.18.2)
psych (2.0.15)
que (0.10.0)
racc (1.4.12)
rack (1.6.0)
rack-cache (1.2)
rack (>= 0.4)
rack-protection (1.5.3)
rack
rack-test (0.6.3)
rack (>= 1.0)
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
rails-dom-testing (1.0.6)
rails-dom-testing (1.0.7)
activesupport (>= 4.2.0.beta, < 5.0)
nokogiri (~> 1.6.0)
rails-deprecated_sanitizer (>= 1.0.1)
@ -197,7 +222,7 @@ GEM
rdoc (4.2.0)
redcarpet (3.2.3)
redis (3.2.1)
redis-namespace (1.5.1)
redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4)
resque (1.25.2)
mono_logger (~> 1.0)
@ -210,47 +235,42 @@ GEM
redis (~> 3.0)
resque (~> 1.25)
rufus-scheduler (~> 3.0)
rufus-scheduler (3.0.9)
tzinfo
rufus-scheduler (3.1.3)
sass (3.4.17)
sdoc (0.4.1)
json (~> 1.7, >= 1.7.7)
rdoc (~> 4.0)
sequel (4.19.0)
sequel (4.25.0)
serverengine (1.5.10)
sigdump (~> 0.2.2)
sidekiq (3.3.2)
celluloid (>= 0.16.0)
connection_pool (>= 2.1.1)
json
redis (>= 3.0.6)
redis-namespace (>= 1.3.1)
sigdump (0.2.2)
sinatra (1.4.5)
rack (~> 1.4)
rack-protection (~> 1.4)
tilt (~> 1.3, >= 1.3.4)
sneakers (1.0.4)
bunny (~> 1.7.0)
sidekiq (3.4.2)
celluloid (~> 0.16.0)
connection_pool (~> 2.2, >= 2.2.0)
json (~> 1.0)
redis (~> 3.2, >= 3.2.1)
redis-namespace (~> 1.5, >= 1.5.2)
sigdump (0.2.3)
sinatra (1.0)
rack (>= 1.0)
sneakers (1.1.1)
bunny (>= 1.7.0, <= 2.0.0)
serverengine (~> 1.5.5)
thor
thread (~> 0.1.7)
sprockets (3.0.2)
rack (~> 1.0)
sqlite3 (1.3.10)
stackprof (0.2.7)
sucker_punch (1.3.2)
celluloid (~> 0.16.0)
sucker_punch (1.5.1)
celluloid (= 0.16.0)
thor (0.19.1)
thread (0.1.7)
thread_safe (0.3.5)
tilt (1.4.1)
timers (4.0.1)
hitimes
turbolinks (2.5.3)
coffee-rails
tzinfo (1.2.2)
thread_safe (~> 0.1)
uglifier (2.7.0)
uglifier (2.7.1)
execjs (>= 0.3.0)
json (>= 1.8.0)
vegas (0.1.11)
@ -292,19 +312,22 @@ DEPENDENCIES
qu-rails!
qu-redis
que
queue_classic
queue_classic!
racc (>= 1.4.6)
rack!
rack-cache (~> 1.2)
rails!
rake (>= 10.3)
redcarpet (~> 3.2.3)
resque
resque-scheduler
sass (>= 3.3)
sass-rails!
sdoc (~> 0.4.0)
sequel
sidekiq
sneakers
sprockets (~> 3.0.0.rc.1)
sprockets!
sprockets-rails!
sqlite3 (~> 1.3.6)
stackprof
@ -314,4 +337,4 @@ DEPENDENCIES
w3c_validators
BUNDLED WITH
1.10.5
1.10.6

@ -76,6 +76,8 @@ and may also be used independently outside Rails.
We encourage you to contribute to Ruby on Rails! Please check out the
[Contributing to Ruby on Rails guide](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) for guidelines about how to proceed. [Join us!](http://contributors.rubyonrails.org)
Everyone interacting in Rails and its sub-projects codebases, issue trackers, chat rooms, and mailing lists is expected to follow the Rails [code of conduct](http://rubyonrails.org/conduct/).
## Code Status
[![Build Status](https://travis-ci.org/rails/rails.svg?branch=master)](https://travis-ci.org/rails/rails)

@ -1,43 +1,47 @@
= Releasing Rails
# Releasing Rails
In this document, we'll cover the steps necessary to release Rails. Each
section contains steps to take during that time before the release. The times
suggested in each header are just that: suggestions. However, they should
really be considered as minimums.
== 10 Days before release
## 10 Days before release
Today is mostly coordination tasks. Here are the things you must do today:
=== Is the CI green? If not, make it green. (See "Fixing the CI")
### Is the CI green? If not, make it green. (See "Fixing the CI")
Do not release with a Red CI. You can find the CI status here:
http://travis-ci.org/rails/rails
```
http://travis-ci.org/rails/rails
```
=== Is Sam Ruby happy? If not, make him happy.
### Is Sam Ruby happy? If not, make him happy.
Sam Ruby keeps a test suite that makes sure the code samples in his book (Agile
Web Development with Rails) all work. These are valuable integration tests
for Rails. You can check the status of his tests here:
http://intertwingly.net/projects/dashboard.html
```
http://intertwingly.net/projects/dashboard.html
```
Do not release with Red AWDwR tests.
=== Do we have any Git dependencies? If so, contact those authors.
### Do we have any Git dependencies? If so, contact those authors.
Having Git dependencies indicates that we depend on unreleased code.
Obviously Rails cannot be released when it depends on unreleased code.
Contact the authors of those particular gems and work out a release date that
suits them.
=== Contact the security team (either Koz or tenderlove)
### Contact the security team (either Koz or tenderlove)
Let them know of your plans to release. There may be security issues to be
addressed, and that can impact your release date.
=== Notify implementors.
### Notify implementors.
Ruby implementors have high stakes in making sure Rails works. Be kind and
give them a heads up that Rails will be released soonish.
@ -54,27 +58,29 @@ lists:
Implementors will love you and help you.
== 3 Days before release
### 3 Days before release
This is when you should release the release candidate. Here are your tasks
for today:
=== Is the CI green? If not, make it green.
### Is the CI green? If not, make it green.
=== Is Sam Ruby happy? If not, make him happy.
### Is Sam Ruby happy? If not, make him happy.
=== Contact the security team. CVE emails must be sent on this day.
### Contact the security team. CVE emails must be sent on this day.
=== Create a release branch.
### Create a release branch.
From the stable branch, create a release branch. For example, if you're
releasing Rails 3.0.10, do this:
[aaron@higgins rails (3-0-stable)]$ git checkout -b 3-0-10
Switched to a new branch '3-0-10'
[aaron@higgins rails (3-0-10)]$
```
[aaron@higgins rails (3-0-stable)]$ git checkout -b 3-0-10
Switched to a new branch '3-0-10'
[aaron@higgins rails (3-0-10)]$
```
=== Update each CHANGELOG.
### Update each CHANGELOG.
Many times commits are made without the CHANGELOG being updated. You should
review the commits since the last release, and fill in any missing information
@ -82,15 +88,17 @@ for each CHANGELOG.
You can review the commits for the 3.0.10 release like this:
[aaron@higgins rails (3-0-10)]$ git log v3.0.9..
```
[aaron@higgins rails (3-0-10)]$ git log v3.0.9..
```
If you're doing a stable branch release, you should also ensure that all of
the CHANGELOG entries in the stable branch are also synced to the master
branch.
=== Update the RAILS_VERSION file to include the RC.
### Update the RAILS_VERSION file to include the RC.
=== Build and test the gem.
### Build and test the gem.
Run `rake install` to generate the gems and install them locally. Then try
generating a new app and ensure that nothing explodes.
@ -98,7 +106,7 @@ generating a new app and ensure that nothing explodes.
This will stop you from looking silly when you push an RC to rubygems.org and
then realize it is broken.
=== Release the gem.
### Release the gem.
IMPORTANT: Due to YAML parse problems on the rubygems.org server, it is safest
to use Ruby 1.8 when releasing.
@ -108,14 +116,16 @@ RAILS_VERSION, commit the changes, tag it, and push the gems to rubygems.org.
Here are the commands that `rake release` should use, so you can understand
what to do in case anything goes wrong:
$ rake all:build
$ git commit -am'updating RAILS_VERSION'
$ git tag -m 'v3.0.10.rc1 release' v3.0.10.rc1
$ git push
$ git push --tags
$ for i in $(ls pkg); do gem push $i; done
```
$ rake all:build
$ git commit -am'updating RAILS_VERSION'
$ git tag -m 'v3.0.10.rc1 release' v3.0.10.rc1
$ git push
$ git push --tags
$ for i in $(ls pkg); do gem push $i; done
```
=== Send Rails release announcements
### Send Rails release announcements
Write a release announcement that includes the version, changes, and links to
GitHub where people can find the specific commit list. Here are the mailing
@ -132,16 +142,16 @@ IMPORTANT: If any users experience regressions when using the release
candidate, you *must* postpone the release. Bugfix releases *should not*
break existing applications.
=== Post the announcement to the Rails blog.
### Post the announcement to the Rails blog.
If you used Markdown format for your email, you can just paste it in to the
blog.
* http://weblog.rubyonrails.org
=== Post the announcement to the Rails Twitter account.
### Post the announcement to the Rails Twitter account.
== Time between release candidate and actual release
## Time between release candidate and actual release
Check the rails-core mailing list and the GitHub issue list for regressions in
the RC.
@ -155,7 +165,7 @@ When you fix the regressions, do not create a new branch. Fix them on the
stable branch, then cherry pick the commit to your release branch. No other
commits should be added to the release branch besides regression fixing commits.
== Day of release
## Day of release
Many of these steps are the same as for the release candidate, so if you need
more explanation on a particular step, see the RC steps.
@ -173,7 +183,7 @@ Today, do this stuff in this order:
* Email security lists
* Email general announcement lists
=== Emailing the Rails security announce list
### Emailing the Rails security announce list
Email the security announce list once for each vulnerability fixed.
@ -193,13 +203,13 @@ so we need to give them the security fixes in patch form.
* Merge the release branch to the stable branch.
* Drink beer (or other cocktail)
== Misc
## Misc
=== Fixing the CI
### Fixing the CI
There are two simple steps for fixing the CI:
1. Identify the problem
2. Fix it
Repeat these steps until the CI is green.
Repeat these steps until the CI is green.

@ -820,7 +820,7 @@ def mail(headers = {}, &block)
# Set configure delivery behavior
wrap_delivery_behavior!(headers.delete(:delivery_method), headers.delete(:delivery_method_options))
# Assign all headers except parts_order, content_type and body
# Assign all headers except parts_order, content_type, body, template_name, and template_path
assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path)
assignable.each { |k, v| m[k] = v }

@ -9,6 +9,7 @@
end
require 'active_support/testing/autorun'
require 'active_support/testing/method_call_assertions'
require 'action_mailer'
require 'action_mailer/test_case'
@ -40,4 +41,6 @@ def jruby_skip(message = '')
skip message if defined?(JRUBY_VERSION)
end
require 'mocha/setup' # FIXME: stop using mocha
class ActiveSupport::TestCase
include ActiveSupport::Testing::MethodCallAssertions
end

@ -505,9 +505,10 @@ def welcome
end
test "calling deliver on the action should deliver the mail object" do
BaseMailer.expects(:deliver_mail).once
mail = BaseMailer.welcome.deliver_now
assert_equal 'The first email on new API!', mail.subject
assert_called(BaseMailer, :deliver_mail) do
mail = BaseMailer.welcome.deliver_now
assert_equal 'The first email on new API!', mail.subject
end
end
test "calling deliver on the action should increment the deliveries collection if using the test mailer" do
@ -517,9 +518,11 @@ def welcome
test "calling deliver, ActionMailer should yield back to mail to let it call :do_delivery on itself" do
mail = Mail::Message.new
mail.expects(:do_delivery).once
BaseMailer.expects(:welcome).returns(mail)
BaseMailer.welcome.deliver
assert_called(mail, :do_delivery) do
assert_called(BaseMailer, :welcome, returns: mail) do
BaseMailer.welcome.deliver
end
end
end
# Rendering
@ -607,8 +610,9 @@ def self.delivered_email(mail)
mail_side_effects do
ActionMailer::Base.register_observer(MyObserver)
mail = BaseMailer.welcome
MyObserver.expects(:delivered_email).with(mail)
mail.deliver_now
assert_called_with(MyObserver, :delivered_email, [mail]) do
mail.deliver_now
end
end
end
@ -616,8 +620,9 @@ def self.delivered_email(mail)
mail_side_effects do
ActionMailer::Base.register_observer("BaseTest::MyObserver")
mail = BaseMailer.welcome
MyObserver.expects(:delivered_email).with(mail)
mail.deliver_now
assert_called_with(MyObserver, :delivered_email, [mail]) do
mail.deliver_now
end
end
end
@ -625,8 +630,9 @@ def self.delivered_email(mail)
mail_side_effects do
ActionMailer::Base.register_observer(:"base_test/my_observer")
mail = BaseMailer.welcome
MyObserver.expects(:delivered_email).with(mail)
mail.deliver_now
assert_called_with(MyObserver, :delivered_email, [mail]) do
mail.deliver_now
end
end
end
@ -634,9 +640,11 @@ def self.delivered_email(mail)
mail_side_effects do
ActionMailer::Base.register_observers("BaseTest::MyObserver", MySecondObserver)
mail = BaseMailer.welcome
MyObserver.expects(:delivered_email).with(mail)
MySecondObserver.expects(:delivered_email).with(mail)
mail.deliver_now
assert_called_with(MyObserver, :delivered_email, [mail]) do
assert_called_with(MySecondObserver, :delivered_email, [mail]) do
mail.deliver_now
end
end
end
end
@ -654,8 +662,9 @@ def self.previewing_email(mail); end
mail_side_effects do
ActionMailer::Base.register_interceptor(MyInterceptor)
mail = BaseMailer.welcome
MyInterceptor.expects(:delivering_email).with(mail)
mail.deliver_now
assert_called_with(MyInterceptor, :delivering_email, [mail]) do
mail.deliver_now
end
end
end
@ -663,8 +672,9 @@ def self.previewing_email(mail); end
mail_side_effects do
ActionMailer::Base.register_interceptor("BaseTest::MyInterceptor")
mail = BaseMailer.welcome
MyInterceptor.expects(:delivering_email).with(mail)
mail.deliver_now
assert_called_with(MyInterceptor, :delivering_email, [mail]) do
mail.deliver_now
end
end
end
@ -672,8 +682,9 @@ def self.previewing_email(mail); end
mail_side_effects do
ActionMailer::Base.register_interceptor(:"base_test/my_interceptor")
mail = BaseMailer.welcome
MyInterceptor.expects(:delivering_email).with(mail)
mail.deliver_now
assert_called_with(MyInterceptor, :delivering_email, [mail]) do
mail.deliver_now
end
end
end
@ -681,18 +692,21 @@ def self.previewing_email(mail); end
mail_side_effects do
ActionMailer::Base.register_interceptors("BaseTest::MyInterceptor", MySecondInterceptor)
mail = BaseMailer.welcome
MyInterceptor.expects(:delivering_email).with(mail)
MySecondInterceptor.expects(:delivering_email).with(mail)
mail.deliver_now
assert_called_with(MyInterceptor, :delivering_email, [mail]) do
assert_called_with(MySecondInterceptor, :delivering_email, [mail]) do
mail.deliver_now
end
end
end
end
test "being able to put proc's into the defaults hash and they get evaluated on mail sending" do
mail1 = ProcMailer.welcome['X-Proc-Method']
yesterday = 1.day.ago
Time.stubs(:now).returns(yesterday)
mail2 = ProcMailer.welcome['X-Proc-Method']
assert(mail1.to_s.to_i > mail2.to_s.to_i)
Time.stub(:now, yesterday) do
mail2 = ProcMailer.welcome['X-Proc-Method']
assert(mail1.to_s.to_i > mail2.to_s.to_i)
end
end
test 'default values which have to_proc (e.g. symbols) should not be considered procs' do
@ -877,33 +891,50 @@ def self.previewing_email(mail); end
test "you can register a preview interceptor to the mail object that gets passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptor(MyInterceptor)
mail = BaseMailer.welcome
BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
MyInterceptor.expects(:previewing_email).with(mail)
BaseMailerPreview.call(:welcome)
stub_any_instance(BaseMailerPreview) do |instance|
instance.stub(:welcome, mail) do
assert_called_with(MyInterceptor, :previewing_email, [mail]) do
BaseMailerPreview.call(:welcome)
end
end
end
end
test "you can register a preview interceptor using its stringified name to the mail object that gets passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptor("BasePreviewInterceptorsTest::MyInterceptor")
mail = BaseMailer.welcome
BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
MyInterceptor.expects(:previewing_email).with(mail)
BaseMailerPreview.call(:welcome)
stub_any_instance(BaseMailerPreview) do |instance|
instance.stub(:welcome, mail) do
assert_called_with(MyInterceptor, :previewing_email, [mail]) do
BaseMailerPreview.call(:welcome)
end
end
end
end
test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptor(:"base_preview_interceptors_test/my_interceptor")
mail = BaseMailer.welcome
BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
MyInterceptor.expects(:previewing_email).with(mail)
BaseMailerPreview.call(:welcome)
stub_any_instance(BaseMailerPreview) do |instance|
instance.stub(:welcome, mail) do
assert_called_with(MyInterceptor, :previewing_email, [mail]) do
BaseMailerPreview.call(:welcome)
end
end
end
end
test "you can register multiple preview interceptors to the mail object that both get passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptors("BasePreviewInterceptorsTest::MyInterceptor", MySecondInterceptor)
mail = BaseMailer.welcome
BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
MyInterceptor.expects(:previewing_email).with(mail)
MySecondInterceptor.expects(:previewing_email).with(mail)
BaseMailerPreview.call(:welcome)
stub_any_instance(BaseMailerPreview) do |instance|
instance.stub(:welcome, mail) do
assert_called_with(MyInterceptor, :previewing_email, [mail]) do
assert_called_with(MySecondInterceptor, :previewing_email, [mail]) do
BaseMailerPreview.call(:welcome)
end
end
end
end
end
end

@ -102,16 +102,21 @@ def welcome(hash={})
end
test "ActionMailer should be told when Mail gets delivered" do
DeliveryMailer.expects(:deliver_mail).once
DeliveryMailer.welcome.deliver_now
DeliveryMailer.delivery_method = :test
assert_called(DeliveryMailer, :deliver_mail) do
DeliveryMailer.welcome.deliver_now
end
end
test "delivery method can be customized per instance" do
Mail::SMTP.any_instance.expects(:deliver!)
email = DeliveryMailer.welcome.deliver_now
assert_instance_of Mail::SMTP, email.delivery_method
email = DeliveryMailer.welcome(delivery_method: :test).deliver_now
assert_instance_of Mail::TestMailer, email.delivery_method
stub_any_instance(Mail::SMTP, instance: Mail::SMTP.new({})) do |instance|
assert_called(instance, :deliver!) do
email = DeliveryMailer.welcome.deliver_now
assert_instance_of Mail::SMTP, email.delivery_method
email = DeliveryMailer.welcome(delivery_method: :test).deliver_now
assert_instance_of Mail::TestMailer, email.delivery_method
end
end
end
test "delivery method can be customized in subclasses not changing the parent" do
@ -176,8 +181,11 @@ def welcome(hash={})
old_perform_deliveries = DeliveryMailer.perform_deliveries
begin
DeliveryMailer.perform_deliveries = false
Mail::Message.any_instance.expects(:deliver!).never
DeliveryMailer.welcome.deliver_now
stub_any_instance(Mail::Message) do |instance|
assert_not_called(instance, :deliver!) do
DeliveryMailer.welcome.deliver_now
end
end
ensure
DeliveryMailer.perform_deliveries = old_perform_deliveries
end

@ -1,6 +1,7 @@
require 'abstract_unit'
require 'action_view'
require 'action_controller'
require 'active_support/deprecation'
class I18nTestMailer < ActionMailer::Base
configure do |c|
@ -52,10 +53,15 @@ def app
end
def test_send_mail
Mail::SMTP.any_instance.expects(:deliver!)
with_translation 'de', email_subject: '[Anmeldung] Willkommen' do
get '/test/send_mail'
assert_equal "Mail sent - Subject: [Anmeldung] Willkommen", @response.body
stub_any_instance(Mail::SMTP, instance: Mail::SMTP.new({})) do |instance|
assert_called(instance, :deliver!) do
with_translation 'de', email_subject: '[Anmeldung] Willkommen' do
ActiveSupport::Deprecation.silence do
get '/test/send_mail'
end
assert_equal "Mail sent - Subject: [Anmeldung] Willkommen", @response.body
end
end
end
end

@ -1,3 +1,45 @@
* Using strings or symbols for middleware class names is deprecated. Convert
things like this:
middleware.use "Foo::Bar"
to this:
middleware.use Foo::Bar
* ActionController::TestSession now accepts a default value as well as
a block for generating a default value based off the key provided.
This fixes calls to session#fetch in ApplicationController instances that
take more two arguments or a block from raising `ArgumentError: wrong
number of arguments (2 for 1)` when performing controller tests.
*Matthew Gerrior*
* Fix `ActionController::Parameters#fetch` overwriting `KeyError` returned by
default block.
*Jonas Schuber Erlandsson*, *Roque Pinel*
* `ActionController::Parameters` no longer inherits from
`HashWithIndifferentAccess`
Inheriting from `HashWithIndifferentAccess` allowed users to call any
enumerable methods on `Parameters` object, resulting in a risk of losing the
`permitted?` status or even getting back a pure `Hash` object instead of
a `Parameters` object with proper sanitization.
By not inheriting from `HashWithIndifferentAccess`, we are able to make
sure that all methods that are defined in `Parameters` object will return
a proper `Parameters` object with a correct `permitted?` flag.
*Prem Sichanugrist*
* Replaced `ActiveSupport::Concurrency::Latch` with `Concurrent::CountDownLatch`
from the concurrent-ruby gem.
*Jerry D'Antonio*
* Add ability to filter parameters based on parent keys.
# matches {credit_card: {code: "xxxx"}}
@ -164,7 +206,8 @@
*arthurnn*
* `ActionController#translate` supports symbols as shortcuts.
When shortcut is given it also lookups without action name.
When a shortcut is given it also performs the lookup without the action
name.
*Max Melentiev*

@ -21,7 +21,7 @@
s.add_dependency 'activesupport', version
s.add_dependency 'rack', '~> 1.6'
s.add_dependency 'rack', '~> 2.x'
s.add_dependency 'rack-test', '~> 0.6.3'
s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.2'
s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5'

@ -96,7 +96,7 @@ def clear_action_methods!
# ==== Returns
# * <tt>String</tt>
def controller_path
@controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous?
@controller_path ||= name.sub(/Controller$/, ''.freeze).underscore unless anonymous?
end
# Refresh the cached action_methods when a new action_method is added.

@ -181,7 +181,7 @@ def add_template_helper(mod)
end
def default_helper_module!
module_name = name.sub(/Controller$/, '')
module_name = name.sub(/Controller$/, ''.freeze)
module_path = module_name.underscore
helper module_path
rescue LoadError => e

@ -54,11 +54,11 @@ def rendered_format
Mime::TEXT
end
DEFAULT_PROTECTED_INSTANCE_VARIABLES = Set.new %w(
DEFAULT_PROTECTED_INSTANCE_VARIABLES = Set.new %i(
@_action_name @_response_body @_formats @_prefixes @_config
@_view_context_class @_view_renderer @_lookup_context
@_routes @_db_runtime
).map(&:to_sym)
)
# This method should return a hash with assigns.
# You can overwrite this configuration per controller.

@ -90,7 +90,7 @@ class API < Metal
# Shortcut helper that returns all the ActionController::API modules except
# the ones passed as arguments:
#
# class MetalController
# class MyAPIBaseController < ActionController::Metal
# ActionController::API.without_modules(:ForceSSL, :UrlFor).each do |left|
# include left
# end

@ -183,7 +183,7 @@ class Base < Metal
# Shortcut helper that returns all the modules included in
# ActionController::Base except the ones passed as arguments:
#
# class MetalController
# class MyBaseController < ActionController::Metal
# ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
# include left
# end
@ -252,7 +252,7 @@ def self.without_modules(*modules)
# Define some internal variables that should not be propagated to the view.
PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [
:@_status, :@_headers, :@_params, :@_env, :@_response, :@_request,
:@_status, :@_headers, :@_params, :@_response, :@_request,
:@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout ]
def _protected_ivars # :nodoc:

@ -8,7 +8,7 @@ module ActionController
#
# You can read more about each approach by clicking the modules below.
#
# Note: To turn off all caching, set
# Note: To turn off all caching provided by Action Controller, set
# config.action_controller.perform_caching = false
#
# == \Caching stores

@ -25,7 +25,7 @@ def process_action(event)
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
message << " (#{additions.join(" | ")})" unless additions.blank?
message << " (#{additions.join(" | ".freeze)})" unless additions.blank?
message
end
end

@ -1,5 +1,6 @@
require 'active_support/core_ext/array/extract_options'
require 'action_dispatch/middleware/stack'
require 'active_support/deprecation'
module ActionController
# Extend ActionDispatch middleware stack to make it aware of options
@ -11,22 +12,14 @@ module ActionController
#
class MiddlewareStack < ActionDispatch::MiddlewareStack #:nodoc:
class Middleware < ActionDispatch::MiddlewareStack::Middleware #:nodoc:
def initialize(klass, *args, &block)
options = args.extract_options!
@only = Array(options.delete(:only)).map(&:to_s)
@except = Array(options.delete(:except)).map(&:to_s)
args << options unless options.empty?
super
def initialize(klass, args, actions, strategy, block)
@actions = actions
@strategy = strategy
super(klass, args, block)
end
def valid?(action)
if @only.present?
@only.include?(action)
elsif @except.present?
!@except.include?(action)
else
true
end
@strategy.call @actions, action
end
end
@ -37,6 +30,32 @@ def build(action, app = Proc.new)
middleware.valid?(action) ? middleware.build(a) : a
end
end
private
INCLUDE = ->(list, action) { list.include? action }
EXCLUDE = ->(list, action) { !list.include? action }
NULL = ->(list, action) { true }
def build_middleware(klass, args, block)
options = args.extract_options!
only = Array(options.delete(:only)).map(&:to_s)
except = Array(options.delete(:except)).map(&:to_s)
args << options unless options.empty?
strategy = NULL
list = nil
if only.any?
strategy = INCLUDE
list = only
elsif except.any?
strategy = EXCLUDE
list = except
end
Middleware.new(get_class(klass), args, list, strategy, block)
end
end
# <tt>ActionController::Metal</tt> is the simplest possible controller, providing a
@ -98,11 +117,10 @@ def build(action, app = Proc.new)
class Metal < AbstractController::Base
abstract!
attr_internal_writer :env
def env
@_env ||= {}
@_request.env
end
deprecate :env
# Returns the last part of the controller's name, underscored, without the ending
# <tt>Controller</tt>. For instance, PostsController returns <tt>posts</tt>.
@ -197,8 +215,7 @@ def dispatch(name, request) #:nodoc:
def set_request!(request) #:nodoc:
@_request = request
@_env = request.env
@_env['action_controller.instance'] = self
@_request.controller_instance = self
end
def to_a #:nodoc:
@ -232,13 +249,13 @@ def self.call(env)
end
# Returns a Rack endpoint for the given action name.
def self.action(name, klass = ActionDispatch::Request)
def self.action(name)
if middleware_stack.any?
middleware_stack.build(name) do |env|
new.dispatch(name, klass.new(env))
new.dispatch(name, ActionDispatch::Request.new(env))
end
else
lambda { |env| new.dispatch(name, klass.new(env)) }
lambda { |env| new.dispatch(name, ActionDispatch::Request.new(env)) }
end
end
end

@ -85,6 +85,10 @@ def initialize(path)
@to_path = path
end
def body
File.binread(to_path)
end
# Stream the file's contents if Rack::Sendfile isn't present.
def each
File.open(to_path, 'rb') do |file|
@ -126,7 +130,7 @@ def each
# See +send_file+ for more information on HTTP Content-* headers and caching.
def send_data(data, options = {}) #:doc:
send_file_headers! options
render options.slice(:status, :content_type).merge(:text => data)
render options.slice(:status, :content_type).merge(body: data)
end
private

@ -55,10 +55,10 @@ module ClassMethods
# You can pass any of the following options to affect the before_action callback
# * <tt>only</tt> - The callback should be run only for this action
# * <tt>except</tt> - The callback should be run for all actions except this action
# * <tt>if</tt> - A symbol naming an instance method or a proc; the callback
# will be called only when it returns a true value.
# * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback
# will be called only when it returns a false value.
# * <tt>if</tt> - A symbol naming an instance method or a proc; the
# callback will be called only when it returns a true value.
# * <tt>unless</tt> - A symbol naming an instance method or a proc; the
# callback will be called only when it returns a false value.
def force_ssl(options = {})
action_options = options.slice(*ACTION_OPTIONS)
redirect_options = options.except(*ACTION_OPTIONS)
@ -71,8 +71,8 @@ def force_ssl(options = {})
# Redirect the existing request to use the HTTPS protocol.
#
# ==== Parameters
# * <tt>host_or_options</tt> - Either a host name or any of the url & redirect options
# available to the <tt>force_ssl</tt> method.
# * <tt>host_or_options</tt> - Either a host name or any of the url &
# redirect options available to the <tt>force_ssl</tt> method.
def force_ssl_redirect(host_or_options = nil)
unless request.ssl?
options = {

@ -73,7 +73,7 @@ def helper_attr(*attrs)
# Provides a proxy to access helpers methods from outside the view.
def helpers
@helper_proxy ||= begin
@helper_proxy ||= begin
proxy = ActionView::Base.new
proxy.config = config.inheritable_copy
proxy.extend(_helpers)
@ -100,7 +100,7 @@ def modules_for_helpers(args)
def all_helpers_from_path(path)
helpers = Array(path).flat_map do |_path|
extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1'.freeze) }
names.sort!
end
helpers.uniq!

@ -94,7 +94,7 @@ def authenticate(request, &login_procedure)
end
def has_basic_credentials?(request)
request.authorization.present? && (auth_scheme(request) == 'Basic')
request.authorization.present? && (auth_scheme(request).downcase == 'basic')
end
def user_name_and_password(request)
@ -502,7 +502,7 @@ def encode_credentials(token, options = {})
def authentication_request(controller, realm, message = nil)
message ||= "HTTP Token: Access denied.\n"
controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"'.freeze, "".freeze)}")
controller.__send__ :render, :text => message, :status => :unauthorized
controller.__send__ :render, plain: message, status: :unauthorized
end
end
end

@ -156,16 +156,16 @@ module MimeResponds
# It works for both inline:
#
# respond_to do |format|
# format.html.any { render text: "any" }
# format.html.phone { render text: "phone" }
# format.html.any { render html: "any" }
# format.html.phone { render html: "phone" }
# end
#
# and block syntax:
#
# respond_to do |format|
# format.html do |variant|
# variant.any(:tablet, :phablet){ render text: "any" }
# variant.phone { render text: "phone" }
# variant.any(:tablet, :phablet){ render html: "any" }
# variant.phone { render html: "phone" }
# end
# end
#

@ -4,8 +4,8 @@
require 'action_dispatch/http/mime_type'
module ActionController
# Wraps the parameters hash into a nested hash. This will allow clients to submit
# POST requests without having to specify any root elements.
# Wraps the parameters hash into a nested hash. This will allow clients to
# submit requests without having to specify any root elements.
#
# This functionality is enabled in +config/initializers/wrap_parameters.rb+
# and can be customized.
@ -14,7 +14,7 @@ module ActionController
# a non-empty array:
#
# class UsersController < ApplicationController
# wrap_parameters format: [:json, :xml]
# wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]
# end
#
# If you enable +ParamsWrapper+ for +:json+ format, instead of having to

@ -94,7 +94,7 @@ def self.add(key, &block)
# This method is the opposite of add method.
#
# Usage:
# To remove a csv renderer:
#
# ActionController::Renderers.remove(:csv)
def self.remove(key)

@ -1,3 +1,6 @@
require 'active_support/deprecation'
require 'active_support/core_ext/string/filters'
module ActionController
module Rendering
extend ActiveSupport::Concern
@ -74,6 +77,17 @@ def _normalize_args(action=nil, options={}, &blk) #:nodoc:
def _normalize_options(options) #:nodoc:
_normalize_text(options)
if options[:text]
ActiveSupport::Deprecation.warn <<-WARNING.squish
`render :text` is deprecated because it does not actually render a
`text/plain` response. Switch to `render plain: 'plain text'` to
render as `text/plain`, `render html: '<strong>HTML</strong>'` to
render as `text/html`, or `render body: 'raw'` to match the deprecated
behavior and render with the default Content-Type, which is
`text/plain`.
WARNING
end
if options[:html]
options[:html] = ERB::Util.html_escape(options[:html])
end

@ -20,7 +20,7 @@ class InvalidCrossOriginRequest < ActionControllerError #:nodoc:
# Since HTML and JavaScript requests are typically made from the browser, we
# need to ensure to verify request authenticity for the web browser. We can
# use session-oriented authentication for these types of requests, by using
# the `protect_form_forgery` method in our controllers.
# the `protect_from_forgery` method in our controllers.
#
# GET requests are not protected since they don't have side effects like writing
# to the database and don't leak sensitive information. JavaScript requests are
@ -136,17 +136,17 @@ def initialize(controller)
# This is the method that defines the application behavior when a request is found to be unverified.
def handle_unverified_request
request = @controller.request
request.session = NullSessionHash.new(request.env)
request.session = NullSessionHash.new(request)
request.env['action_dispatch.request.flash_hash'] = nil
request.env['rack.session.options'] = { skip: true }
request.env['action_dispatch.cookies'] = NullCookieJar.build(request)
request.cookie_jar = NullCookieJar.build(request, {})
end
protected
class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
def initialize(env)
super(nil, env)
def initialize(req)
super(nil, req)
@data = {}
@loaded = true
end
@ -160,14 +160,6 @@ def exists?
end
class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
def self.build(request)
key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY]
host = request.host
secure = request.ssl?
new(key_generator, host, secure, options_for_env({}))
end
def write(*)
# nothing
end

@ -199,7 +199,7 @@ module Streaming
def _process_options(options) #:nodoc:
super
if options[:stream]
if env["HTTP_VERSION"] == "HTTP/1.0"
if request.version == "HTTP/1.0"
options.delete(:stream)
else
headers["Cache-Control"] ||= "no-cache"

@ -104,10 +104,12 @@ def initialize(params) # :nodoc:
# params = ActionController::Parameters.new(key: 'value')
# params[:key] # => "value"
# params["key"] # => "value"
class Parameters < ActiveSupport::HashWithIndifferentAccess
class Parameters
cattr_accessor :permit_all_parameters, instance_accessor: false
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
delegate :keys, :key?, :has_key?, :empty?, :inspect, to: :@parameters
# By default, never raise an UnpermittedParameters exception if these
# params are present. The default includes both 'controller' and 'action'
# because they are added by Rails and should be of no concern. One way
@ -144,11 +146,22 @@ def self.const_missing(const_name)
# params = ActionController::Parameters.new(name: 'Francesco')
# params.permitted? # => true
# Person.new(params) # => #<Person id: nil, name: "Francesco">
def initialize(attributes = nil)
super(attributes)
def initialize(parameters = {})
@parameters = parameters.with_indifferent_access
@permitted = self.class.permit_all_parameters
end
# Returns true if another +Parameters+ object contains the same content and
# permitted flag, or other Hash-like object contains the same content. This
# override is in place so you can perform a comparison with `Hash`.
def ==(other_hash)
if other_hash.respond_to?(:permitted?)
super
else
@parameters == other_hash
end
end
# Returns a safe +Hash+ representation of this parameter with all
# unpermitted keys removed.
#
@ -162,7 +175,7 @@ def initialize(attributes = nil)
# safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
def to_h
if permitted?
to_hash
@parameters.to_h
else
slice(*self.class.always_permitted_parameters).permit!.to_h
end
@ -170,20 +183,17 @@ def to_h
# Returns an unsafe, unfiltered +Hash+ representation of this parameter.
def to_unsafe_h
to_hash
@parameters.to_h
end
alias_method :to_unsafe_hash, :to_unsafe_h
# Convert all hashes in values into parameters, then yield each pair like
# the same way as <tt>Hash#each_pair</tt>
def each_pair(&block)
super do |key, value|
convert_hashes_to_parameters(key, value)
@parameters.each_pair do |key, value|
yield key, convert_hashes_to_parameters(key, value)
end
super
end
alias_method :each, :each_pair
# Attribute that keeps track of converted arrays, if any, to avoid double
@ -347,7 +357,13 @@ def permit(*filters)
# params[:person] # => {"name"=>"Francesco"}
# params[:none] # => nil
def [](key)
convert_hashes_to_parameters(key, super)
convert_hashes_to_parameters(key, @parameters[key])
end
# Assigns a value to a given +key+. The given key may still get filtered out
# when +permit+ is called.
def []=(key, value)
@parameters[key] = value
end
# Returns a parameter for the given +key+. If the +key+
@ -361,10 +377,16 @@ def [](key)
# params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
# params.fetch(:none, 'Francesco') # => "Francesco"
# params.fetch(:none) { 'Francesco' } # => "Francesco"
def fetch(key, *args)
convert_hashes_to_parameters(key, super, false)
rescue KeyError
raise ActionController::ParameterMissing.new(key)
def fetch(key, *args, &block)
convert_value_to_parameters(
@parameters.fetch(key) {
if block_given?
yield
else
args.fetch(0) { raise ActionController::ParameterMissing.new(key) }
end
}
)
end
# Returns a new <tt>ActionController::Parameters</tt> instance that
@ -375,7 +397,24 @@ def fetch(key, *args)
# params.slice(:a, :b) # => {"a"=>1, "b"=>2}
# params.slice(:d) # => {}
def slice(*keys)
new_instance_with_inherited_permitted_status(super)
new_instance_with_inherited_permitted_status(@parameters.slice(*keys))
end
# Returns current <tt>ActionController::Parameters</tt> instance which
# contains only the given +keys+.
def slice!(*keys)
@parameters.slice!(*keys)
self
end
# Returns a new <tt>ActionController::Parameters</tt> instance that
# filters out the given +keys+.
#
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
# params.except(:a, :b) # => {"c"=>3}
# params.except(:d) # => {"a"=>1,"b"=>2,"c"=>3}
def except(*keys)
new_instance_with_inherited_permitted_status(@parameters.except(*keys))
end
# Removes and returns the key/value pairs matching the given keys.
@ -384,7 +423,7 @@ def slice(*keys)
# params.extract!(:a, :b) # => {"a"=>1, "b"=>2}
# params # => {"c"=>3}
def extract!(*keys)
new_instance_with_inherited_permitted_status(super)
new_instance_with_inherited_permitted_status(@parameters.extract!(*keys))
end
# Returns a new <tt>ActionController::Parameters</tt> with the results of
@ -393,36 +432,80 @@ def extract!(*keys)
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
# params.transform_values { |x| x * 2 }
# # => {"a"=>2, "b"=>4, "c"=>6}
def transform_values
if block_given?
new_instance_with_inherited_permitted_status(super)
def transform_values(&block)
if block
new_instance_with_inherited_permitted_status(
@parameters.transform_values(&block)
)
else
super
@parameters.transform_values
end
end
# This method is here only to make sure that the returned object has the
# correct +permitted+ status. It should not matter since the parent of
# this object is +HashWithIndifferentAccess+
def transform_keys # :nodoc:
if block_given?
new_instance_with_inherited_permitted_status(super)
# Performs values transformation and returns the altered
# <tt>ActionController::Parameters</tt> instance.
def transform_values!(&block)
@parameters.transform_values!(&block)
self
end
# Returns a new <tt>ActionController::Parameters</tt> instance with the
# results of running +block+ once for every key. The values are unchanged.
def transform_keys(&block)
if block
new_instance_with_inherited_permitted_status(
@parameters.transform_keys(&block)
)
else
super
@parameters.transform_keys
end
end
# Performs keys transformation and returns the altered
# <tt>ActionController::Parameters</tt> instance.
def transform_keys!(&block)
@parameters.transform_keys!(&block)
self
end
# Deletes and returns a key-value pair from +Parameters+ whose key is equal
# to key. If the key is not found, returns the default value. If the
# optional code block is given and the key is not found, pass in the key
# and return the result of block.
def delete(key, &block)
convert_hashes_to_parameters(key, super, false)
convert_value_to_parameters(@parameters.delete(key))
end
# Returns a new instance of <tt>ActionController::Parameters</tt> with only
# items that the block evaluates to true.
def select(&block)
new_instance_with_inherited_permitted_status(@parameters.select(&block))
end
# Equivalent to Hash#keep_if, but returns nil if no changes were made.
def select!(&block)
convert_value_to_parameters(super)
@parameters.select!(&block)
self
end
alias_method :keep_if, :select!
# Returns a new instance of <tt>ActionController::Parameters</tt> with items
# that the block evaluates to true removed.
def reject(&block)
new_instance_with_inherited_permitted_status(@parameters.reject(&block))
end
# Removes items that the block evaluates to true and returns self.
def reject!(&block)
@parameters.reject!(&block)
self
end
alias_method :delete_if, :reject!
# Return values that were assigned to the given +keys+. Note that all the
# +Hash+ objects will be converted to <tt>ActionController::Parameters</tt>.
def values_at(*keys)
convert_value_to_parameters(@parameters.values_at(*keys))
end
# Returns an exact copy of the <tt>ActionController::Parameters</tt>
@ -439,11 +522,30 @@ def dup
end
end
# Returns a new <tt>ActionController::Parameters</tt> with all keys from
# +other_hash+ merges into current hash.
def merge(other_hash)
new_instance_with_inherited_permitted_status(
@parameters.merge(other_hash)
)
end
# This is required by ActiveModel attribute assignment, so that user can
# pass +Parameters+ to a mass assignment methods in a model. It should not
# matter as we are using +HashWithIndifferentAccess+ internally.
def stringify_keys # :nodoc:
dup
end
protected
def permitted=(new_permitted)
@permitted = new_permitted
end
def fields_for_style?
@parameters.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
end
private
def new_instance_with_inherited_permitted_status(hash)
self.class.new(hash).tap do |new_instance|
@ -451,40 +553,41 @@ def new_instance_with_inherited_permitted_status(hash)
end
end
def convert_hashes_to_parameters(key, value, assign_if_converted=true)
def convert_hashes_to_parameters(key, value)
converted = convert_value_to_parameters(value)
self[key] = converted if assign_if_converted && !converted.equal?(value)
@parameters[key] = converted unless converted.equal?(value)
converted
end
def convert_value_to_parameters(value)
if value.is_a?(Array) && !converted_arrays.member?(value)
case value
when Array
return value if converted_arrays.member?(value)
converted = value.map { |_| convert_value_to_parameters(_) }
converted_arrays << converted
converted
elsif value.is_a?(Parameters) || !value.is_a?(Hash)
value
else
when Hash
self.class.new(value)
else
value
end
end
def each_element(object)
if object.is_a?(Array)
object.map { |el| yield el }.compact
elsif fields_for_style?(object)
hash = object.class.new
object.each { |k,v| hash[k] = yield v }
hash
else
yield object
case object
when Array
object.grep(Parameters).map { |el| yield el }.compact
when Parameters
if object.fields_for_style?
hash = object.class.new
object.each { |k,v| hash[k] = yield v }
hash
else
yield object
end
end
end
def fields_for_style?(object)
object.is_a?(Hash) && object.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
end
def unpermitted_parameters!(params)
unpermitted_keys = unpermitted_keys(params)
if unpermitted_keys.any?
@ -546,14 +649,8 @@ def permitted_scalar_filter(params, key)
end
def array_of_permitted_scalars?(value)
if value.is_a?(Array)
value.all? {|element| permitted_scalar?(element)}
end
end
def array_of_permitted_scalars_filter(params, key)
if has_key?(key) && array_of_permitted_scalars?(self[key])
params[key] = self[key]
if value.is_a?(Array) && value.all? {|element| permitted_scalar?(element)}
yield value
end
end
@ -564,17 +661,17 @@ def hash_filter(params, filter)
# Slicing filters out non-declared keys.
slice(*filter.keys).each do |key, value|
next unless value
next unless has_key? key
if filter[key] == EMPTY_ARRAY
# Declaration { comment_ids: [] }.
array_of_permitted_scalars_filter(params, key)
array_of_permitted_scalars?(self[key]) do |val|
params[key] = val
end
else
# Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
params[key] = each_element(value) do |element|
if element.is_a?(Hash)
element = self.class.new(element) unless element.respond_to?(:permit)
element.permit(*Array.wrap(filter[key]))
end
element.permit(*Array.wrap(filter[key]))
end
end
end

@ -41,7 +41,11 @@ def url_options
if original_script_name
options[:original_script_name] = original_script_name
else
options[:script_name] = same_origin ? request.script_name.dup : script_name
if same_origin
options[:script_name] = request.script_name.empty? ? "".freeze : request.script_name.dup
else
options[:script_name] = script_name
end
end
options.freeze
else

@ -1,39 +0,0 @@
module ActionController
class Middleware < Metal
class ActionMiddleware
def initialize(controller, app)
@controller, @app = controller, app
end
def call(env)
request = ActionDispatch::Request.new(env)
@controller.build(@app).dispatch(:index, request)
end
end
class << self
alias build new
def new(app)
ActionMiddleware.new(self, app)
end
end
attr_internal :app
def process(action)
response = super
self.status, self.headers, self.response_body = response if response.is_a?(Array)
response
end
def initialize(app)
super()
@_app = app
end
def index
call(env)
end
end
end

@ -1,4 +1,5 @@
require 'rack/session/abstract/id'
require 'active_support/core_ext/hash/conversions'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/hash/keys'
@ -35,21 +36,23 @@ def initialize(env, session)
end
def query_string=(string)
@env[Rack::QUERY_STRING] = string
set_header Rack::QUERY_STRING, string
end
def request_parameters=(params)
@env["action_dispatch.request.request_parameters"] = params
set_header "action_dispatch.request.request_parameters", params
end
def assign_parameters(routes, controller_path, action, parameters = {})
parameters = parameters.symbolize_keys
generated_path, extra_keys = routes.generate_extras(parameters.merge(:controller => controller_path, :action => action))
def content_type=(type)
set_header 'CONTENT_TYPE', type
end
def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys)
non_path_parameters = {}
path_parameters = {}
parameters.each do |key, value|
if extra_keys.include?(key) || key == :action || key == :controller
if query_string_keys.include?(key)
non_path_parameters[key] = value
else
if value.is_a?(Array)
@ -68,10 +71,12 @@ def assign_parameters(routes, controller_path, action, parameters = {})
end
else
if ENCODER.should_multipart?(non_path_parameters)
@env['CONTENT_TYPE'] = ENCODER.content_type
self.content_type = ENCODER.content_type
data = ENCODER.build_multipart non_path_parameters
else
@env['CONTENT_TYPE'] ||= 'application/x-www-form-urlencoded'
get_header('CONTENT_TYPE') do |k|
set_header k, 'application/x-www-form-urlencoded'
end
# FIXME: setting `request_parametes` is normally handled by the
# params parser middleware, and we should remove this roundtripping
@ -93,8 +98,8 @@ def assign_parameters(routes, controller_path, action, parameters = {})
end
end
@env['CONTENT_LENGTH'] = data.length.to_s
@env['rack.input'] = StringIO.new(data)
set_header 'CONTENT_LENGTH', data.length.to_s
set_header 'rack.input', StringIO.new(data)
end
@env["PATH_INFO"] ||= generated_path
@ -132,23 +137,13 @@ def content_type
end.new
end
class TestResponse < ActionDispatch::TestResponse
end
class LiveTestResponse < Live::Response
def body
@body ||= super
end
# Was the response successful?
alias_method :success?, :successful?
# Was the URL not found?
alias_method :missing?, :not_found?
# Were we redirected?
alias_method :redirect?, :redirection?
# Was there a server-side error?
alias_method :error?, :server_error?
end
@ -181,6 +176,10 @@ def destroy
clear
end
def fetch(*args, &block)
@data.fetch(*args, &block)
end
private
def load!
@ -237,7 +236,7 @@ def load!
# request. You can modify this object before sending the HTTP request. For example,
# you might want to set some session properties before sending a GET request.
# <b>@response</b>::
# An ActionController::TestResponse object, representing the response
# An ActionDispatch::TestResponse object, representing the response
# of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
# after calling +post+. If the various assert methods are not sufficient, then you
# may use this object to inspect the HTTP response in detail.
@ -457,7 +456,7 @@ def process(action, *args)
end
if body.present?
@request.env['RAW_POST_DATA'] = body
@request.set_header 'RAW_POST_DATA', body
end
if http_method.present?
@ -479,44 +478,50 @@ def process(action, *args)
end
self.cookies.update @request.cookies
@request.env['HTTP_COOKIE'] = cookies.to_header
@request.env['action_dispatch.cookies'] = nil
@request.set_header 'HTTP_COOKIE', cookies.to_header
@request.delete_header 'action_dispatch.cookies'
@request = TestRequest.new scrub_env!(@request.env), @request.session
@response = build_response @response_klass
@response.request = @request
@controller.recycle!
@request.env['REQUEST_METHOD'] = http_method
@request.set_header 'REQUEST_METHOD', http_method
controller_class_name = @controller.class.anonymous? ?
"anonymous" :
@controller.class.controller_path
parameters = parameters.symbolize_keys
@request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action.to_s))
generated_path = generated_path(generated_extras)
query_string_keys = query_parameter_names(generated_extras)
@request.assign_parameters(@routes, controller_class_name, action.to_s, parameters, generated_path, query_string_keys)
@request.session.update(session) if session
@request.flash.update(flash || {})
if xhr
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
@request.set_header 'HTTP_X_REQUESTED_WITH', 'XMLHttpRequest'
@request.get_header('HTTP_ACCEPT') do |k|
@request.set_header k, [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
end
end
@controller.request = @request
@controller.response = @response
@request.env["SCRIPT_NAME"] ||= @controller.config.relative_url_root
@request.get_header("SCRIPT_NAME") do |k|
@request.set_header k, @controller.config.relative_url_root
end
@controller.recycle!
@controller.process(action)
@request.env.delete 'HTTP_COOKIE'
@request.delete_header 'HTTP_COOKIE'
if cookies = @request.env['action_dispatch.cookies']
if @request.have_cookie_jar?
unless @response.committed?
cookies.write(@response)
self.cookies.update(cookies.instance_variable_get(:@cookies))
@request.cookie_jar.write(@response)
self.cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
end
end
@response.prepare!
@ -528,18 +533,30 @@ def process(action, *args)
end
if xhr
@request.env.delete 'HTTP_X_REQUESTED_WITH'
@request.env.delete 'HTTP_ACCEPT'
@request.delete_header 'HTTP_X_REQUESTED_WITH'
@request.delete_header 'HTTP_ACCEPT'
end
@request.query_string = ''
@response
end
def controller_class_name
@controller.class.anonymous? ? "anonymous" : @controller.class.controller_path
end
def generated_path(generated_extras)
generated_extras[0]
end
def query_parameter_names(generated_extras)
generated_extras[1] + [:controller, :action]
end
def setup_controller_request_and_response
@controller = nil unless defined? @controller
@response_klass = TestResponse
@response_klass = ActionDispatch::TestResponse
if klass = self.class.controller_class
if klass < ActionController::Live

@ -50,13 +50,13 @@ def filtered_path
protected
def parameter_filter
parameter_filter_for @env.fetch("action_dispatch.parameter_filter") {
parameter_filter_for get_header("action_dispatch.parameter_filter") {
return NULL_PARAM_FILTER
}
end
def env_filter
user_key = @env.fetch("action_dispatch.parameter_filter") {
user_key = get_header("action_dispatch.parameter_filter") {
return NULL_ENV_FILTER
}
parameter_filter_for(Array(user_key) + ENV_MATCH)

@ -5,8 +5,7 @@ module FilterRedirect
FILTERED = '[FILTERED]'.freeze # :nodoc:
def filtered_location # :nodoc:
filters = location_filter
if !filters.empty? && location_filter_match?(filters)
if location_filter_match?
FILTERED
else
location
@ -15,7 +14,7 @@ def filtered_location # :nodoc:
private
def location_filter
def location_filters
if request
request.env['action_dispatch.redirect_filter'] || []
else
@ -23,12 +22,12 @@ def location_filter
end
end
def location_filter_match?(filters)
filters.any? do |filter|
def location_filter_match?
location_filters.any? do |filter|
if String === filter
location.include?(filter)
elsif Regexp === filter
location.match(filter)
location =~ filter
end
end
end

@ -30,27 +30,32 @@ class Headers
HTTP_HEADER = /\A[A-Za-z0-9-]+\z/
include Enumerable
attr_reader :env
def initialize(env = {}) # :nodoc:
@env = env
def self.from_hash(hash)
new ActionDispatch::Request.new hash
end
def initialize(request) # :nodoc:
@req = request
end
# Returns the value for the given key mapped to @env.
def [](key)
@env[env_name(key)]
@req.get_header env_name(key)
end
# Sets the given value for the key mapped to @env.
def []=(key, value)
@env[env_name(key)] = value
@req.set_header env_name(key), value
end
def key?(key)
@env.key? env_name(key)
@req.has_header? env_name(key)
end
alias :include? :key?
DEFAULT = Object.new # :nodoc:
# Returns the value for the given key mapped to @env.
#
# If the key is not found and an optional code block is not provided,
@ -58,18 +63,22 @@ def key?(key)
#
# If the code block is provided, then it will be run and
# its result returned.
def fetch(key, *args, &block)
@env.fetch env_name(key), *args, &block
def fetch(key, default = DEFAULT)
@req.get_header(env_name(key)) do
return default unless default == DEFAULT
return yield if block_given?
raise NameError, key
end
end
def each(&block)
@env.each(&block)
@req.each_header(&block)
end
# Returns a new Http::Headers instance containing the contents of
# <tt>headers_or_env</tt> and the original instance.
def merge(headers_or_env)
headers = Http::Headers.new(env.dup)
headers = @req.dup.headers
headers.merge!(headers_or_env)
headers
end
@ -79,11 +88,14 @@ def merge(headers_or_env)
# <tt>headers_or_env</tt>.
def merge!(headers_or_env)
headers_or_env.each do |key, value|
self[env_name(key)] = value
@req.set_header env_name(key), value
end
end
def env; @req.env.dup; end
private
# Converts a HTTP header name to an environment variable name if it is
# not contained within the headers hash.
def env_name(key)

@ -15,12 +15,13 @@ module MimeNegotiation
# For backward compatibility, the post \format is extracted from the
# X-Post-Data-Format HTTP header if present.
def content_mime_type
@env["action_dispatch.request.content_type"] ||= begin
if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
get_header("action_dispatch.request.content_type") do |k|
v = if get_header('CONTENT_TYPE') =~ /^([^,\;]*)/
Mime::Type.lookup($1.strip.downcase)
else
nil
end
set_header k, v
end
end
@ -30,14 +31,15 @@ def content_type
# Returns the accepted MIME type for the request.
def accepts
@env["action_dispatch.request.accepts"] ||= begin
header = @env['HTTP_ACCEPT'].to_s.strip
get_header("action_dispatch.request.accepts") do |k|
header = get_header('HTTP_ACCEPT').to_s.strip
if header.empty?
v = if header.empty?
[content_mime_type]
else
Mime::Type.parse(header)
end
set_header k, v
end
end
@ -52,14 +54,14 @@ def format(view_path = [])
end
def formats
@env["action_dispatch.request.formats"] ||= begin
get_header("action_dispatch.request.formats") do |k|
params_readable = begin
parameters[:format]
rescue ActionController::BadRequest
false
end
if params_readable
v = if params_readable
Array(Mime[parameters[:format]])
elsif use_accept_header && valid_accept_header
accepts
@ -68,6 +70,7 @@ def formats
else
[Mime::HTML]
end
set_header k, v
end
end
@ -102,7 +105,7 @@ def variant
# end
def format=(extension)
parameters[:format] = extension.to_s
@env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])]
set_header "action_dispatch.request.formats", [Mime::Type.lookup_by_extension(parameters[:format])]
end
# Sets the \formats by string extensions. This differs from #format= by allowing you
@ -121,9 +124,9 @@ def format=(extension)
# end
def formats=(extensions)
parameters[:format] = extensions.first.to_s
@env["action_dispatch.request.formats"] = extensions.collect do |extension|
set_header "action_dispatch.request.formats", extensions.collect { |extension|
Mime::Type.lookup_by_extension(extension)
end
}
end
# Receives an array of mimes and return the first user sent mime that

@ -45,7 +45,7 @@ def fetch(type)
#
# respond_to do |format|
# format.html
# format.ics { render text: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") }
# format.ics { render body: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") }
# format.xml { render xml: @post }
# end
# end
@ -211,7 +211,7 @@ def parse_data_with_trailing_star(input)
# This method is opposite of register method.
#
# Usage:
# To unregister a MIME type:
#
# Mime::Type.unregister(:mobile)
def unregister(symbol)

@ -34,11 +34,11 @@ def self.compile(filters)
end
end
deep_regexps, regexps = regexps.partition { |r| r.to_s.include?("\\.") }
deep_strings, strings = strings.partition { |s| s.include?("\\.") }
deep_regexps, regexps = regexps.partition { |r| r.to_s.include?("\\.".freeze) }
deep_strings, strings = strings.partition { |s| s.include?("\\.".freeze) }
regexps << Regexp.new(strings.join('|'), true) unless strings.empty?
deep_regexps << Regexp.new(deep_strings.join('|'), true) unless deep_strings.empty?
regexps << Regexp.new(strings.join('|'.freeze), true) unless strings.empty?
deep_regexps << Regexp.new(deep_strings.join('|'.freeze), true) unless deep_strings.empty?
new regexps, deep_regexps, blocks
end

@ -8,20 +8,23 @@ module Parameters
# Returns both GET and POST \parameters in a single hash.
def parameters
@env["action_dispatch.request.parameters"] ||= begin
params = begin
request_parameters.merge(query_parameters)
rescue EOFError
query_parameters.dup
end
params.merge!(path_parameters)
end
params = get_header("action_dispatch.request.parameters")
return params if params
params = begin
request_parameters.merge(query_parameters)
rescue EOFError
query_parameters.dup
end
params.merge!(path_parameters)
set_header("action_dispatch.request.parameters", params)
params
end
alias :params :parameters
def path_parameters=(parameters) #:nodoc:
@env.delete('action_dispatch.request.parameters')
@env[PARAMETERS_KEY] = parameters
delete_header('action_dispatch.request.parameters')
set_header PARAMETERS_KEY, parameters
end
# Returns a hash with the \parameters used to form the \path of the request.
@ -29,7 +32,7 @@ def path_parameters=(parameters) #:nodoc:
#
# {'action' => 'my_action', 'controller' => 'my_controller'}
def path_parameters
@env[PARAMETERS_KEY] ||= {}
get_header(PARAMETERS_KEY) || {}
end
private
@ -37,22 +40,7 @@ def path_parameters
# Convert nested Hash to HashWithIndifferentAccess.
#
def normalize_encode_params(params)
case params
when Hash
if params.has_key?(:tempfile)
UploadedFile.new(params)
else
params.each_with_object({}) do |(key, val), new_hash|
new_hash[key] = if val.is_a?(Array)
val.map! { |el| normalize_encode_params(el) }
else
normalize_encode_params(val)
end
end.with_indifferent_access
end
else
params
end
ActionDispatch::Request::Utils.normalize_encode_params params
end
end
end

@ -20,8 +20,6 @@ class Request < Rack::Request
include ActionDispatch::Http::FilterParameters
include ActionDispatch::Http::URL
HTTP_X_REQUEST_ID = "HTTP_X_REQUEST_ID".freeze # :nodoc:
autoload :Session, 'action_dispatch/request/session'
autoload :Utils, 'action_dispatch/request/utils'
@ -31,15 +29,19 @@ class Request < Rack::Request
PATH_TRANSLATED REMOTE_HOST
REMOTE_IDENT REMOTE_USER REMOTE_ADDR
SERVER_NAME SERVER_PROTOCOL
ORIGINAL_SCRIPT_NAME
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
HTTP_NEGOTIATE HTTP_PRAGMA ].freeze
HTTP_NEGOTIATE HTTP_PRAGMA HTTP_CLIENT_IP
HTTP_X_FORWARDED_FOR HTTP_VERSION
HTTP_X_REQUEST_ID
].freeze
ENV_METHODS.each do |env|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{env.sub(/^HTTP_/n, '').downcase} # def accept_charset
@env["#{env}"] # @env["HTTP_ACCEPT_CHARSET"]
get_header "#{env}".freeze # get_header "HTTP_ACCEPT_CHARSET".freeze
end # end
METHOD
end
@ -65,8 +67,20 @@ def check_path_parameters!
end
end
def controller_class
check_path_parameters!
params = path_parameters
controller_param = params[:controller].underscore if params.key?(:controller)
params[:action] ||= 'index'
yield unless controller_param
const_name = "#{controller_param.camelize}Controller"
ActiveSupport::Dependencies.constantize(const_name)
end
def key?(key)
@env.key?(key)
has_header? key
end
# List of HTTP request methods from the following RFCs:
@ -103,27 +117,46 @@ def key?(key)
# the application should use), this \method returns the overridden
# value, not the original.
def request_method
@request_method ||= check_method(env["REQUEST_METHOD"])
@request_method ||= check_method(super)
end
def routes # :nodoc:
env["action_dispatch.routes".freeze]
get_header("action_dispatch.routes".freeze)
end
def original_script_name # :nodoc:
env['ORIGINAL_SCRIPT_NAME'.freeze]
def routes=(routes) # :nodoc:
set_header("action_dispatch.routes".freeze, routes)
end
def engine_script_name(_routes) # :nodoc:
env[_routes.env_key]
get_header(_routes.env_key)
end
def engine_script_name=(name) # :nodoc:
set_header(routes.env_key, name.dup)
end
def request_method=(request_method) #:nodoc:
if check_method(request_method)
@request_method = env["REQUEST_METHOD"] = request_method
@request_method = set_header("REQUEST_METHOD", request_method)
end
end
def controller_instance # :nodoc:
get_header('action_controller.instance'.freeze)
end
def controller_instance=(controller) # :nodoc:
set_header('action_controller.instance'.freeze, controller)
end
def show_exceptions? # :nodoc:
# We're treating `nil` as "unset", and we want the default setting to be
# `true`. This logic should be extracted to `env_config` and calculated
# once.
!(get_header('action_dispatch.show_exceptions'.freeze) == false)
end
# Returns a symbol form of the #request_method
def request_method_symbol
HTTP_METHOD_LOOKUP[request_method]
@ -133,7 +166,7 @@ def request_method_symbol
# even if it was overridden by middleware. See #request_method for
# more information.
def method
@method ||= check_method(env["rack.methodoverride.original_method"] || env['REQUEST_METHOD'])
@method ||= check_method(get_header("rack.methodoverride.original_method") || get_header('REQUEST_METHOD'))
end
# Returns a symbol form of the #method
@ -145,7 +178,7 @@ def method_symbol
#
# request.headers["Content-Type"] # => "text/plain"
def headers
@headers ||= Http::Headers.new(@env)
@headers ||= Http::Headers.new(self)
end
# Returns a +String+ with the last requested path including their params.
@ -156,7 +189,7 @@ def headers
# # get '/foo?bar'
# request.original_fullpath # => '/foo?bar'
def original_fullpath
@original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath)
@original_fullpath ||= (get_header("ORIGINAL_FULLPATH") || fullpath)
end
# Returns the +String+ full path including params of the last URL requested.
@ -195,7 +228,7 @@ def content_length
# (case-insensitive), which may need to be manually added depending on the
# choice of JavaScript libraries and frameworks.
def xml_http_request?
@env['HTTP_X_REQUESTED_WITH'] =~ /XMLHttpRequest/i
get_header('HTTP_X_REQUESTED_WITH') =~ /XMLHttpRequest/i
end
alias :xhr? :xml_http_request?
@ -207,7 +240,11 @@ def ip
# Returns the IP address of client as a +String+,
# usually set by the RemoteIp middleware.
def remote_ip
@remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
@remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s
end
def remote_ip=(remote_ip)
set_header "action_dispatch.remote_ip".freeze, remote_ip
end
ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id".freeze # :nodoc:
@ -219,43 +256,39 @@ def remote_ip
# This unique ID is useful for tracing a request from end-to-end as part of logging or debugging.
# This relies on the rack variable set by the ActionDispatch::RequestId middleware.
def request_id
env[ACTION_DISPATCH_REQUEST_ID]
get_header ACTION_DISPATCH_REQUEST_ID
end
def request_id=(id) # :nodoc:
env[ACTION_DISPATCH_REQUEST_ID] = id
set_header ACTION_DISPATCH_REQUEST_ID, id
end
alias_method :uuid, :request_id
def x_request_id # :nodoc:
@env[HTTP_X_REQUEST_ID]
end
# Returns the lowercase name of the HTTP server software.
def server_software
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
(get_header('SERVER_SOFTWARE') && /^([a-zA-Z]+)/ =~ get_header('SERVER_SOFTWARE')) ? $1.downcase : nil
end
# Read the request \body. This is useful for web services that need to
# work with raw requests directly.
def raw_post
unless @env.include? 'RAW_POST_DATA'
unless has_header? 'RAW_POST_DATA'
raw_post_body = body
@env['RAW_POST_DATA'] = raw_post_body.read(content_length)
set_header('RAW_POST_DATA', raw_post_body.read(content_length))
raw_post_body.rewind if raw_post_body.respond_to?(:rewind)
end
@env['RAW_POST_DATA']
get_header 'RAW_POST_DATA'
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']
if raw_post = get_header('RAW_POST_DATA')
raw_post.force_encoding(Encoding::BINARY)
StringIO.new(raw_post)
else
@env['rack.input']
body_stream
end
end
@ -266,7 +299,7 @@ def form_data?
end
def body_stream #:nodoc:
@env['rack.input']
get_header('rack.input')
end
# TODO This should be broken apart into AD::Request::Session and probably
@ -277,20 +310,20 @@ def reset_session
else
self.session = {}
end
@env['action_dispatch.request.flash_hash'] = nil
set_header('action_dispatch.request.flash_hash', nil)
end
def session=(session) #:nodoc:
Session.set @env, session
Session.set self, session
end
def session_options=(options)
Session::Options.set @env, options
Session::Options.set self, options
end
# Override Rack's GET method to support indifferent access
def GET
@env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
@env["action_dispatch.request.query_parameters"] ||= normalize_encode_params(super || {})
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new(:query, e)
end
@ -298,7 +331,7 @@ def GET
# Override Rack's POST method to support indifferent access
def POST
@env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
@env["action_dispatch.request.request_parameters"] ||= normalize_encode_params(super || {})
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new(:request, e)
end
@ -307,10 +340,10 @@ def POST
# Returns the authorization header regardless of whether it was specified directly or through one of the
# proxy alternatives.
def authorization
@env['HTTP_AUTHORIZATION'] ||
@env['X-HTTP_AUTHORIZATION'] ||
@env['X_HTTP_AUTHORIZATION'] ||
@env['REDIRECT_X_HTTP_AUTHORIZATION']
get_header('HTTP_AUTHORIZATION') ||
get_header('X-HTTP_AUTHORIZATION') ||
get_header('X_HTTP_AUTHORIZATION') ||
get_header('REDIRECT_X_HTTP_AUTHORIZATION')
end
# True if the request came from localhost, 127.0.0.1.
@ -318,10 +351,13 @@ def local?
LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
end
protected
def parse_query(*)
Utils.deep_munge(super)
end
def request_parameters=(params)
set_header("action_dispatch.request.request_parameters".freeze, params)
end
def logger
get_header("action_dispatch.logger".freeze)
end
private
def check_method(name)

@ -80,11 +80,21 @@ def initialize(response, buf)
@response = response
@buf = buf
@closed = false
@str_body = nil
end
def body
@str_body ||= begin
buf = ''
each { |chunk| buf << chunk }
buf
end
end
def write(string)
raise IOError, "closed stream" if closed?
@str_body = nil
@response.commit!
@buf.push string
end
@ -187,13 +197,13 @@ def content_type=(content_type)
@content_type = content_type.to_s
end
# Sets the HTTP character set.
# Sets the HTTP character set. In case of nil parameter
# it sets the charset to utf-8.
#
# response.charset = 'utf-16' # => 'utf-16'
# response.charset = nil # => 'utf-8'
def charset=(charset)
if nil == charset
@charset = self.class.default_charset
else
@charset = charset
end
@charset = charset.nil? ? self.class.default_charset : charset
end
# The response code of the request.
@ -222,9 +232,7 @@ def message
# Returns the content of the response as a string. This contains the contents
# of any calls to <tt>render</tt>.
def body
strings = []
each { |part| strings << part.to_s }
strings.join
@stream.body
end
EMPTY = " "

@ -28,7 +28,13 @@ def initialize(hash) # :nodoc:
raise(ArgumentError, ':tempfile is required') unless @tempfile
@original_filename = hash[:filename]
@original_filename &&= @original_filename.encode "UTF-8"
if @original_filename
begin
@original_filename.encode!(Encoding::UTF_8)
rescue EncodingError
@original_filename.force_encoding(Encoding::UTF_8)
end
end
@content_type = hash[:type]
@headers = hash[:head]
end

@ -245,7 +245,7 @@ def raw_host_with_port
# req = Request.new 'HTTP_HOST' => 'example.com:8080'
# req.host # => "example.com"
def host
raw_host_with_port.sub(/:\d+$/, '')
raw_host_with_port.sub(/:\d+$/, ''.freeze)
end
# Returns a \host:\port string for this request, such as "example.com" or

@ -14,7 +14,7 @@ def initialize(routes)
def generate(name, options, path_parameters, parameterize = nil)
constraints = path_parameters.merge(options)
missing_keys = []
missing_keys = nil # need for variable scope
match_route(name, constraints) do |route|
parameterized_parts = extract_parameterized_parts(route, options, path_parameters, parameterize)
@ -25,22 +25,22 @@ def generate(name, options, path_parameters, parameterize = nil)
next unless name || route.dispatcher?
missing_keys = missing_keys(route, parameterized_parts)
next unless missing_keys.empty?
next if missing_keys && !missing_keys.empty?
params = options.dup.delete_if do |key, _|
parameterized_parts.key?(key) || route.defaults.key?(key)
end
defaults = route.defaults
required_parts = route.required_parts
parameterized_parts.delete_if do |key, value|
value.to_s == defaults[key].to_s && !required_parts.include?(key)
parameterized_parts.keep_if do |key, value|
defaults[key].nil? || value.to_s != defaults[key].to_s || required_parts.include?(key)
end
return [route.format(parameterized_parts), params]
end
message = "No route matches #{Hash[constraints.sort_by{|k,v| k.to_s}].inspect}"
message << " missing required keys: #{missing_keys.sort.inspect}" unless missing_keys.empty?
message << " missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty?
raise ActionController::UrlGenerationError, message
end
@ -54,12 +54,12 @@ def clear
def extract_parameterized_parts(route, options, recall, parameterize = nil)
parameterized_parts = recall.merge(options)
keys_to_keep = route.parts.reverse.drop_while { |part|
keys_to_keep = route.parts.reverse_each.drop_while { |part|
!options.key?(part) || (options[part] || recall[part]).nil?
} | route.required_parts
(parameterized_parts.keys - keys_to_keep).each do |bad_key|
parameterized_parts.delete(bad_key)
parameterized_parts.delete_if do |bad_key, _|
!keys_to_keep.include?(bad_key)
end
if parameterize
@ -110,15 +110,36 @@ def non_recursive(cache, options)
routes
end
module RegexCaseComparator
DEFAULT_INPUT = /[-_.a-zA-Z0-9]+\/[-_.a-zA-Z0-9]+/
DEFAULT_REGEX = /\A#{DEFAULT_INPUT}\Z/
def self.===(regex)
DEFAULT_INPUT == regex
end
end
# Returns an array populated with missing keys if any are present.
def missing_keys(route, parts)
missing_keys = []
missing_keys = nil
tests = route.path.requirements
route.required_parts.each { |key|
if tests.key?(key)
missing_keys << key unless /\A#{tests[key]}\Z/ === parts[key]
case tests[key]
when nil
unless parts[key]
missing_keys ||= []
missing_keys << key
end
when RegexCaseComparator
unless RegexCaseComparator::DEFAULT_REGEX === parts[key]
missing_keys ||= []
missing_keys << key
end
else
missing_keys << key unless parts[key]
unless /\A#{tests[key]}\Z/ === parts[key]
missing_keys ||= []
missing_keys << key
end
end
}
missing_keys
@ -134,7 +155,7 @@ def possibles(cache, options, depth = 0)
def build_cache
root = { ___routes: [] }
routes.each_with_index do |route, i|
routes.routes.each_with_index do |route, i|
leaf = route.required_defaults.inject(root) do |h, tuple|
h[tuple] ||= {}
end

@ -14,15 +14,15 @@ def initialize(left)
end
def each(&block)
Visitors::Each.new(block).accept(self)
Visitors::Each::INSTANCE.accept(self, block)
end
def to_s
Visitors::String.new.accept(self)
Visitors::String::INSTANCE.accept(self, '')
end
def to_dot
Visitors::Dot.new.accept(self)
Visitors::Dot::INSTANCE.accept(self)
end
def to_sym
@ -30,7 +30,7 @@ def to_sym
end
def name
left.tr '*:', ''
left.tr '*:'.freeze, ''.freeze
end
def type
@ -39,10 +39,14 @@ def type
def symbol?; false; end
def literal?; false; end
def terminal?; false; end
def star?; false; end
def cat?; false; end
end
class Terminal < Node # :nodoc:
alias :symbol :left
def terminal?; true; end
end
class Literal < Terminal # :nodoc:
@ -69,11 +73,13 @@ def type; :#{t.upcase}; end
class Symbol < Terminal # :nodoc:
attr_accessor :regexp
alias :symbol :regexp
attr_reader :name
DEFAULT_EXP = /[^\.\/\?]+/
def initialize(left)
super
@regexp = DEFAULT_EXP
@name = left.tr '*:'.freeze, ''.freeze
end
def default_regexp?
@ -92,6 +98,7 @@ def type; :GROUP; end
end
class Star < Unary # :nodoc:
def star?; true; end
def type; :STAR; end
def name
@ -111,6 +118,7 @@ def children; [left, right] end
end
class Cat < Binary # :nodoc:
def cat?; true; end
def type; :CAT; end
end

@ -6,6 +6,10 @@ module Journey # :nodoc:
class Parser < Racc::Parser # :nodoc:
include Journey::Nodes
def self.parse(string)
new.parse string
end
def initialize
@scanner = Scanner.new
end

@ -1,5 +1,3 @@
require 'action_dispatch/journey/router/strexp'
module ActionDispatch
module Journey # :nodoc:
module Path # :nodoc:
@ -7,14 +5,20 @@ class Pattern # :nodoc:
attr_reader :spec, :requirements, :anchored
def self.from_string string
new Journey::Router::Strexp.build(string, {}, ["/.?"], true)
build(string, {}, "/.?", true)
end
def initialize(strexp)
@spec = strexp.ast
@requirements = strexp.requirements
@separators = strexp.separators.join
@anchored = strexp.anchor
def self.build(path, requirements, separators, anchored)
parser = Journey::Parser.new
ast = parser.parse path
new ast, requirements, separators, anchored
end
def initialize(ast, requirements, separators, anchored)
@spec = ast
@requirements = requirements
@separators = separators
@anchored = anchored
@names = nil
@optional_names = nil
@ -28,12 +32,12 @@ def build_formatter
end
def ast
@spec.grep(Nodes::Symbol).each do |node|
@spec.find_all(&:symbol?).each do |node|
re = @requirements[node.to_sym]
node.regexp = re if re
end
@spec.grep(Nodes::Star).each do |node|
@spec.find_all(&:star?).each do |node|
node = node.left
node.regexp = @requirements[node.to_sym] || /(.+)/
end
@ -55,31 +59,6 @@ def optional_names
}.map(&:name).uniq
end
class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
attr_reader :offsets
def initialize(matchers)
@matchers = matchers
@capture_count = [0]
end
def visit(node)
super
@capture_count
end
def visit_SYMBOL(node)
node = node.to_sym
if @matchers.key?(node)
re = /#{@matchers[node]}|/
@capture_count.push((re.match('').length - 1) + (@capture_count.last || 0))
else
@capture_count << (@capture_count.last || 0)
end
end
end
class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc:
def initialize(separator, matchers)
@separator = separator
@ -189,8 +168,20 @@ def regexp_visitor
def offsets
return @offsets if @offsets
viz = RegexpOffsets.new(@requirements)
@offsets = viz.accept(spec)
@offsets = [0]
spec.find_all(&:symbol?).each do |node|
node = node.to_sym
if @requirements.key?(node)
re = /#{@requirements[node]}|/
@offsets.push((re.match('').length - 1) + @offsets.last)
else
@offsets << @offsets.last
end
end
@offsets
end
end
end

@ -1,36 +1,81 @@
module ActionDispatch
module Journey # :nodoc:
class Route # :nodoc:
attr_reader :app, :path, :defaults, :name
attr_reader :app, :path, :defaults, :name, :precedence
attr_reader :constraints
alias :conditions :constraints
attr_accessor :precedence
module VerbMatchers
VERBS = %w{ DELETE GET HEAD OPTIONS LINK PATCH POST PUT TRACE UNLINK }
VERBS.each do |v|
class_eval <<-eoc
class #{v}
def self.verb; name.split("::").last; end
def self.call(req); req.#{v.downcase}?; end
end
eoc
end
class Unknown
attr_reader :verb
def initialize(verb)
@verb = verb
end
def call(request); @verb === request.request_method; end
end
class All
def self.call(_); true; end
def self.verb; ''; end
end
VERB_TO_CLASS = VERBS.each_with_object({ :all => All }) do |verb, hash|
klass = const_get verb
hash[verb] = klass
hash[verb.downcase] = klass
hash[verb.downcase.to_sym] = klass
end
end
def self.verb_matcher(verb)
VerbMatchers::VERB_TO_CLASS.fetch(verb) do
VerbMatchers::Unknown.new verb.to_s.dasherize.upcase
end
end
def self.build(name, app, path, constraints, required_defaults, defaults)
request_method_match = verb_matcher(constraints.delete(:request_method))
new name, app, path, constraints, required_defaults, defaults, request_method_match, 0
end
##
# +path+ is a path constraint.
# +constraints+ is a hash of constraints to be applied to this route.
def initialize(name, app, path, constraints, required_defaults, defaults)
def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence)
@name = name
@app = app
@path = path
@request_method_match = request_method_match
@constraints = constraints
@defaults = defaults
@required_defaults = nil
@_required_defaults = required_defaults || []
@_required_defaults = required_defaults
@required_parts = nil
@parts = nil
@decorated_ast = nil
@precedence = 0
@precedence = precedence
@path_formatter = @path.build_formatter
end
def ast
@decorated_ast ||= begin
decorated_ast = path.ast
decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self }
decorated_ast.find_all(&:terminal?).each { |n| n.memo = self }
decorated_ast
end
end
@ -92,7 +137,8 @@ def dispatcher?
end
def matches?(request)
constraints.all? do |method, value|
match_verb(request) &&
constraints.all? { |method, value|
case value
when Regexp, String
value === request.send(method).to_s
@ -105,15 +151,28 @@ def matches?(request)
else
value === request.send(method)
end
end
}
end
def ip
constraints[:ip] || //
end
def requires_matching_verb?
!@request_method_match.all? { |x| x == VerbMatchers::All }
end
def verb
constraints[:request_method] || //
%r[^#{verbs.join('|')}$]
end
private
def verbs
@request_method_match.map(&:verb)
end
def match_verb(request)
@request_method_match.any? { |m| m.call request }
end
end
end

@ -1,5 +1,4 @@
require 'action_dispatch/journey/router/utils'
require 'action_dispatch/journey/router/strexp'
require 'action_dispatch/journey/routes'
require 'action_dispatch/journey/formatter'
@ -102,7 +101,7 @@ def find_routes req
}
routes =
if req.request_method == "HEAD"
if req.head?
match_head_routes(routes, req)
else
match_routes(routes, req)
@ -121,7 +120,7 @@ def find_routes req
end
def match_head_routes(routes, req)
verb_specific_routes = routes.reject { |route| route.verb == // }
verb_specific_routes = routes.select(&:requires_matching_verb?)
head_routes = match_routes(verb_specific_routes, req)
if head_routes.empty?

@ -1,27 +0,0 @@
module ActionDispatch
module Journey # :nodoc:
class Router # :nodoc:
class Strexp # :nodoc:
class << self
alias :compile :new
end
attr_reader :path, :requirements, :separators, :anchor, :ast
def self.build(path, requirements, separators, anchor = true)
parser = Journey::Parser.new
ast = parser.parse path
new ast, path, requirements, separators, anchor
end
def initialize(ast, path, requirements, separators, anchor = true)
@ast = ast
@path = path
@requirements = requirements
@separators = separators
@anchor = anchor
end
end
end
end
end

@ -14,10 +14,10 @@ class Utils # :nodoc:
# normalize_path("/%ab") # => "/%AB"
def self.normalize_path(path)
path = "/#{path}"
path.squeeze!('/')
path.sub!(%r{/+\Z}, '')
path.squeeze!('/'.freeze)
path.sub!(%r{/+\Z}, ''.freeze)
path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
path = '/' if path == ''
path = '/' if path == ''.freeze
path
end

@ -5,11 +5,10 @@ module Journey # :nodoc:
class Routes # :nodoc:
include Enumerable
attr_reader :routes, :named_routes, :custom_routes, :anchored_routes
attr_reader :routes, :custom_routes, :anchored_routes
def initialize
@routes = []
@named_routes = {}
@ast = nil
@anchored_routes = []
@custom_routes = []
@ -37,7 +36,6 @@ def clear
routes.clear
anchored_routes.clear
custom_routes.clear
named_routes.clear
end
def partition_route(route)
@ -62,13 +60,9 @@ def simulator
end
end
# Add a route to the routing table.
def add_route(app, path, conditions, required_defaults, defaults, name = nil)
route = Route.new(name, app, path, conditions, required_defaults, defaults)
route.precedence = routes.length
def add_route(name, mapping)
route = mapping.make_route name, routes.length
routes << route
named_routes[name] = route if name && !named_routes[name]
partition_route(route)
clear_cache!
route

@ -92,6 +92,45 @@ def visit_DOT(n); terminal(n); end
end
end
class FunctionalVisitor # :nodoc:
DISPATCH_CACHE = {}
def accept(node, seed)
visit(node, seed)
end
def visit node, seed
send(DISPATCH_CACHE[node.type], node, seed)
end
def binary(node, seed)
visit(node.right, visit(node.left, seed))
end
def visit_CAT(n, seed); binary(n, seed); end
def nary(node, seed)
node.children.inject(seed) { |s, c| visit(c, s) }
end
def visit_OR(n, seed); nary(n, seed); end
def unary(node, seed)
visit(node.left, seed)
end
def visit_GROUP(n, seed); unary(n, seed); end
def visit_STAR(n, seed); unary(n, seed); end
def terminal(node, seed); seed; end
def visit_LITERAL(n, seed); terminal(n, seed); end
def visit_SYMBOL(n, seed); terminal(n, seed); end
def visit_SLASH(n, seed); terminal(n, seed); end
def visit_DOT(n, seed); terminal(n, seed); end
instance_methods(false).each do |pim|
next unless pim =~ /^visit_(.*)$/
DISPATCH_CACHE[$1.to_sym] = pim
end
end
class FormatBuilder < Visitor # :nodoc:
def accept(node); Journey::Format.new(super); end
def terminal(node); [node.left]; end
@ -117,104 +156,110 @@ def visit_SYMBOL(n)
end
# Loop through the requirements AST
class Each < Visitor # :nodoc:
attr_reader :block
def initialize(block)
@block = block
end
def visit(node)
class Each < FunctionalVisitor # :nodoc:
def visit(node, block)
block.call(node)
super
end
INSTANCE = new
end
class String < Visitor # :nodoc:
class String < FunctionalVisitor # :nodoc:
private
def binary(node)
[visit(node.left), visit(node.right)].join
def binary(node, seed)
visit(node.right, visit(node.left, seed))
end
def nary(node)
node.children.map { |c| visit(c) }.join '|'
def nary(node, seed)
last_child = node.children.last
node.children.inject(seed) { |s, c|
string = visit(c, s)
string << "|".freeze unless last_child == c
string
}
end
def terminal(node)
node.left
def terminal(node, seed)
seed + node.left
end
def visit_GROUP(node)
"(#{visit(node.left)})"
def visit_GROUP(node, seed)
visit(node.left, seed << "(".freeze) << ")".freeze
end
INSTANCE = new
end
class Dot < Visitor # :nodoc:
class Dot < FunctionalVisitor # :nodoc:
def initialize
@nodes = []
@edges = []
end
def accept(node)
def accept(node, seed = [[], []])
super
nodes, edges = seed
<<-eodot
digraph parse_tree {
size="8,5"
node [shape = none];
edge [dir = none];
#{@nodes.join "\n"}
#{@edges.join("\n")}
#{nodes.join "\n"}
#{edges.join("\n")}
}
eodot
end
private
def binary(node)
node.children.each do |c|
@edges << "#{node.object_id} -> #{c.object_id};"
end
def binary(node, seed)
seed.last.concat node.children.map { |c|
"#{node.object_id} -> #{c.object_id};"
}
super
end
def nary(node)
node.children.each do |c|
@edges << "#{node.object_id} -> #{c.object_id};"
end
def nary(node, seed)
seed.last.concat node.children.map { |c|
"#{node.object_id} -> #{c.object_id};"
}
super
end
def unary(node)
@edges << "#{node.object_id} -> #{node.left.object_id};"
def unary(node, seed)
seed.last << "#{node.object_id} -> #{node.left.object_id};"
super
end
def visit_GROUP(node)
@nodes << "#{node.object_id} [label=\"()\"];"
def visit_GROUP(node, seed)
seed.first << "#{node.object_id} [label=\"()\"];"
super
end
def visit_CAT(node)
@nodes << "#{node.object_id} [label=\"\"];"
def visit_CAT(node, seed)
seed.first << "#{node.object_id} [label=\"\"];"
super
end
def visit_STAR(node)
@nodes << "#{node.object_id} [label=\"*\"];"
def visit_STAR(node, seed)
seed.first << "#{node.object_id} [label=\"*\"];"
super
end
def visit_OR(node)
@nodes << "#{node.object_id} [label=\"|\"];"
def visit_OR(node, seed)
seed.first << "#{node.object_id} [label=\"|\"];"
super
end
def terminal(node)
def terminal(node, seed)
value = node.left
@nodes << "#{node.object_id} [label=\"#{value}\"];"
seed.first << "#{node.object_id} [label=\"#{value}\"];"
seed
end
INSTANCE = new
end
end
end

@ -8,8 +8,52 @@
module ActionDispatch
class Request < Rack::Request
def cookie_jar
env['action_dispatch.cookies'] ||= Cookies::CookieJar.build(env, host, ssl?, cookies)
get_header('action_dispatch.cookies'.freeze) do |k|
self.cookie_jar = Cookies::CookieJar.build(self, cookies)
end
end
# :stopdoc:
def have_cookie_jar?
has_header? 'action_dispatch.cookies'.freeze
end
def cookie_jar=(jar)
set_header 'action_dispatch.cookies'.freeze, jar
end
def key_generator
get_header Cookies::GENERATOR_KEY
end
def signed_cookie_salt
get_header Cookies::SIGNED_COOKIE_SALT
end
def encrypted_cookie_salt
get_header Cookies::ENCRYPTED_COOKIE_SALT
end
def encrypted_signed_cookie_salt
get_header Cookies::ENCRYPTED_SIGNED_COOKIE_SALT
end
def secret_token
get_header Cookies::SECRET_TOKEN
end
def secret_key_base
get_header Cookies::SECRET_KEY_BASE
end
def cookies_serializer
get_header Cookies::COOKIES_SERIALIZER
end
def cookies_digest
get_header Cookies::COOKIES_DIGEST
end
# :startdoc:
end
# \Cookies are read and written through ActionController#cookies.
@ -118,7 +162,7 @@ module ChainedCookieJars
# cookies.permanent.signed[:remember_me] = current_user.id
# # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
def permanent
@permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
@permanent ||= PermanentCookieJar.new(self)
end
# Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
@ -138,10 +182,10 @@ def permanent
# cookies.signed[:discount] # => 45
def signed
@signed ||=
if @options[:upgrade_legacy_signed_cookies]
UpgradeLegacySignedCookieJar.new(self, @key_generator, @options)
if upgrade_legacy_signed_cookies?
UpgradeLegacySignedCookieJar.new(self)
else
SignedCookieJar.new(self, @key_generator, @options)
SignedCookieJar.new(self)
end
end
@ -161,10 +205,10 @@ def signed
# cookies.encrypted[:discount] # => 45
def encrypted
@encrypted ||=
if @options[:upgrade_legacy_signed_cookies]
UpgradeLegacyEncryptedCookieJar.new(self, @key_generator, @options)
if upgrade_legacy_signed_cookies?
UpgradeLegacyEncryptedCookieJar.new(self)
else
EncryptedCookieJar.new(self, @key_generator, @options)
EncryptedCookieJar.new(self)
end
end
@ -172,12 +216,26 @@ def encrypted
# Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores.
def signed_or_encrypted
@signed_or_encrypted ||=
if @options[:secret_key_base].present?
if request.secret_key_base.present?
encrypted
else
signed
end
end
protected
def request; @parent_jar.request; end
private
def upgrade_legacy_signed_cookies?
request.secret_token.present? && request.secret_key_base.present?
end
def key_generator
request.key_generator
end
end
# Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream
@ -187,7 +245,7 @@ def signed_or_encrypted
module VerifyAndUpgradeLegacySignedMessage # :nodoc:
def initialize(*args)
super
@legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: ActiveSupport::MessageEncryptor::NullSerializer)
@legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
def verify_and_upgrade_legacy_signed_message(name, signed_message)
@ -216,34 +274,18 @@ class CookieJar #:nodoc:
# $& => example.local
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
def self.options_for_env(env) #:nodoc:
{ signed_cookie_salt: env[SIGNED_COOKIE_SALT] || '',
encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT] || '',
encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
secret_token: env[SECRET_TOKEN],
secret_key_base: env[SECRET_KEY_BASE],
upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?,
serializer: env[COOKIES_SERIALIZER],
digest: env[COOKIES_DIGEST]
}
end
def self.build(env, host, secure, cookies)
key_generator = env[GENERATOR_KEY]
options = options_for_env env
new(key_generator, host, secure, options).tap do |hash|
def self.build(req, cookies)
new(req).tap do |hash|
hash.update(cookies)
end
end
def initialize(key_generator, host = nil, secure = false, options = {})
@key_generator = key_generator
attr_reader :request
def initialize(request)
@set_cookies = {}
@delete_cookies = {}
@host = host
@secure = secure
@options = options
@request = request
@cookies = {}
@committed = false
end
@ -292,12 +334,12 @@ def handle_options(options) #:nodoc:
# if host is not ip and matches domain regexp
# (ip confirms to domain regexp so we explicitly check for ip)
options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ domain_regexp)
options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp)
".#{$&}"
end
elsif options[:domain].is_a? Array
# if host matches one of the supplied domains without a dot in front of it
options[:domain] = options[:domain].find {|domain| @host.include? domain.sub(/^\./, '') }
options[:domain] = options[:domain].find {|domain| request.host.include? domain.sub(/^\./, '') }
end
end
@ -356,27 +398,20 @@ def write(headers)
@delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
end
def recycle! #:nodoc:
@set_cookies = {}
@delete_cookies = {}
end
mattr_accessor :always_write_cookie
self.always_write_cookie = false
private
def write_cookie?(cookie)
@secure || !cookie[:secure] || always_write_cookie
request.ssl? || !cookie[:secure] || always_write_cookie
end
end
class PermanentCookieJar #:nodoc:
include ChainedCookieJars
def initialize(parent_jar, key_generator, options = {})
def initialize(parent_jar)
@parent_jar = parent_jar
@key_generator = key_generator
@options = options
end
def [](name)
@ -410,7 +445,7 @@ module SerializedCookieJars # :nodoc:
protected
def needs_migration?(value)
@options[:serializer] == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
request.cookies_serializer == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
end
def serialize(value)
@ -430,7 +465,7 @@ def deserialize(name, value)
end
def serializer
serializer = @options[:serializer] || :marshal
serializer = request.cookies_serializer || :marshal
case serializer
when :marshal
Marshal
@ -442,7 +477,7 @@ def serializer
end
def digest
@options[:digest] || 'SHA1'
request.cookies_digest || 'SHA1'
end
end
@ -450,10 +485,9 @@ class SignedCookieJar #:nodoc:
include ChainedCookieJars
include SerializedCookieJars
def initialize(parent_jar, key_generator, options = {})
def initialize(parent_jar)
@parent_jar = parent_jar
@options = options
secret = key_generator.generate_key(@options[:signed_cookie_salt])
secret = key_generator.generate_key(request.signed_cookie_salt)
@verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
@ -505,16 +539,16 @@ class EncryptedCookieJar #:nodoc:
include ChainedCookieJars
include SerializedCookieJars
def initialize(parent_jar, key_generator, options = {})
def initialize(parent_jar)
@parent_jar = parent_jar
if ActiveSupport::LegacyKeyGenerator === key_generator
raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " +
"Read the upgrade documentation to learn more about this new config option."
end
@parent_jar = parent_jar
@options = options
secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
secret = key_generator.generate_key(request.encrypted_cookie_salt || '')
sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || '')
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
@ -568,9 +602,12 @@ def initialize(app)
end
def call(env)
request = ActionDispatch::Request.new env
status, headers, body = @app.call(env)
if cookie_jar = env['action_dispatch.cookies']
if request.have_cookie_jar?
cookie_jar = request.cookie_jar
unless cookie_jar.committed?
cookie_jar.write(headers)
if headers[HTTP_HEADER].respond_to?(:join)

@ -44,6 +44,7 @@ def initialize(app, routes_app = nil)
end
def call(env)
request = ActionDispatch::Request.new env
_, headers, body = response = @app.call(env)
if headers['X-Cascade'] == 'pass'
@ -53,18 +54,18 @@ def call(env)
response
rescue Exception => exception
raise exception if env['action_dispatch.show_exceptions'] == false
render_exception(env, exception)
raise exception unless request.show_exceptions?
render_exception(request, exception)
end
private
def render_exception(env, exception)
wrapper = ExceptionWrapper.new(env, exception)
log_error(env, wrapper)
def render_exception(request, exception)
backtrace_cleaner = request.get_header('action_dispatch.backtrace_cleaner')
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
log_error(request, wrapper)
if env['action_dispatch.show_detailed_exceptions']
request = Request.new(env)
if request.get_header('action_dispatch.show_detailed_exceptions')
traces = wrapper.traces
trace_to_show = 'Application Trace'
@ -106,8 +107,8 @@ def render(status, body, format)
[status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
end
def log_error(env, wrapper)
logger = logger(env)
def log_error(request, wrapper)
logger = logger(request)
return unless logger
exception = wrapper.exception
@ -123,8 +124,8 @@ def log_error(env, wrapper)
end
end
def logger(env)
env['action_dispatch.logger'] || stderr_logger
def logger(request)
request.logger || stderr_logger
end
def stderr_logger

@ -31,10 +31,10 @@ class ExceptionWrapper
'ActionView::Template::Error' => 'template_error'
)
attr_reader :env, :exception, :line_number, :file
attr_reader :backtrace_cleaner, :exception, :line_number, :file
def initialize(env, exception)
@env = env
def initialize(backtrace_cleaner, exception)
@backtrace_cleaner = backtrace_cleaner
@exception = original_exception(exception)
expand_backtrace if exception.is_a?(SyntaxError) || exception.try(:original_exception).try(:is_a?, SyntaxError)
@ -125,10 +125,6 @@ def clean_backtrace(*args)
end
end
def backtrace_cleaner
@backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner']
end
def source_fragment(path, line)
return unless Rails.respond_to?(:root) && Rails.root
full_path = Rails.root.join(path)

@ -6,7 +6,17 @@ class Request < Rack::Request
# read a notice you put there or <tt>flash["notice"] = "hello"</tt>
# to put a new one.
def flash
@env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"])
flash = flash_hash
return flash if flash
self.flash = Flash::FlashHash.from_session_value(session["flash"])
end
def flash=(flash)
set_header Flash::KEY, flash
end
def flash_hash # :nodoc:
get_header Flash::KEY
end
end
@ -263,14 +273,15 @@ def initialize(app)
end
def call(env)
req = ActionDispatch::Request.new env
@app.call(env)
ensure
session = Request::Session.find(env) || {}
flash_hash = env[KEY]
session = Request::Session.find(req) || {}
flash_hash = req.flash_hash
if flash_hash && (flash_hash.present? || session.key?('flash'))
session["flash"] = flash_hash.to_session_value
env[KEY] = flash_hash.dup
req.flash = flash_hash.dup
end
if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)

@ -1,9 +1,14 @@
require 'active_support/core_ext/hash/conversions'
require 'action_dispatch/http/request'
require 'active_support/core_ext/hash/indifferent_access'
module ActionDispatch
# ActionDispatch::ParamsParser works for all the requests having any Content-Length
# (like POST). It takes raw data from the request and puts it through the parser
# that is picked based on Content-Type header.
#
# In case of any error while parsing data ParamsParser::ParseError is raised.
class ParamsParser
# Raised when raw data from the request cannot be parsed by the parser
# defined for request's content mime type.
class ParseError < StandardError
attr_reader :original_exception
@ -17,39 +22,42 @@ def initialize(message, original_exception)
Mime::JSON => lambda { |raw_post|
data = ActiveSupport::JSON.decode(raw_post)
data = {:_json => data} unless data.is_a?(Hash)
Request::Utils.deep_munge(data).with_indifferent_access
Request::Utils.normalize_encode_params(data)
}
}
# Create a new +ParamsParser+ middleware instance.
#
# The +parsers+ argument can take Hash of parsers where key is identifying
# content mime type, and value is a lambda that is going to process data.
def initialize(app, parsers = {})
@app, @parsers = app, DEFAULT_PARSERS.merge(parsers)
end
def call(env)
default = env["action_dispatch.request.request_parameters"]
env["action_dispatch.request.request_parameters"] = parse_formatted_parameters(env, @parsers, default)
request = Request.new(env)
request.request_parameters = parse_formatted_parameters(request, @parsers)
@app.call(env)
end
private
def parse_formatted_parameters(env, parsers, default)
request = Request.new(env)
def parse_formatted_parameters(request, parsers)
return if request.content_length.zero?
return default if request.content_length.zero?
strategy = parsers.fetch(request.content_mime_type) { return default }
strategy = parsers.fetch(request.content_mime_type) { return nil }
strategy.call(request.raw_post)
rescue => e # JSON or Ruby code block errors
logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
logger(request).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
raise ParseError.new(e.message, e)
end
def logger(env)
env['action_dispatch.logger'] || ActiveSupport::Logger.new($stderr)
def logger(request)
request.logger || ActiveSupport::Logger.new($stderr)
end
end
end

@ -17,8 +17,8 @@ def initialize(public_path)
end
def call(env)
status = env["PATH_INFO"][1..-1].to_i
request = ActionDispatch::Request.new(env)
status = request.path_info[1..-1].to_i
content_type = request.formats.first
body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }

@ -74,16 +74,17 @@ def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
# requests. For those requests that do need to know the IP, the
# GetIp#calculate_ip method will calculate the memoized client IP address.
def call(env)
env["action_dispatch.remote_ip"] = GetIp.new(env, check_ip, proxies)
@app.call(env)
req = ActionDispatch::Request.new env
req.remote_ip = GetIp.new(req, check_ip, proxies)
@app.call(req.env)
end
# The GetIp class exists as a way to defer processing of the request data
# into an actual IP address. If the ActionDispatch::Request#remote_ip method
# is called, this class will calculate the value and then memoize it.
class GetIp
def initialize(env, check_ip, proxies)
@env = env
def initialize(req, check_ip, proxies)
@req = req
@check_ip = check_ip
@proxies = proxies
end
@ -108,11 +109,11 @@ def initialize(env, check_ip, proxies)
# the last address left, which was presumably set by one of those proxies.
def calculate_ip
# Set by the Rack web server, this is a single value.
remote_addr = ips_from('REMOTE_ADDR').last
remote_addr = ips_from(@req.remote_addr).last
# Could be a CSV list and/or repeated headers that were concatenated.
client_ips = ips_from('HTTP_CLIENT_IP').reverse
forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR').reverse
client_ips = ips_from(@req.client_ip).reverse
forwarded_ips = ips_from(@req.x_forwarded_for).reverse
# +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
# If they are both set, it means that this request passed through two
@ -123,8 +124,8 @@ def calculate_ip
if should_check_ip && !forwarded_ips.include?(client_ips.last)
# We don't know which came from the proxy, and which from the user
raise IpSpoofAttackError, "IP spoofing attack?! " +
"HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} " +
"HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
"HTTP_CLIENT_IP=#{@req.client_ip.inspect} " +
"HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
end
# We assume these things about the IP headers:
@ -147,8 +148,9 @@ def to_s
protected
def ips_from(header)
return [] unless header
# Split the comma-separated list into an array of strings
ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
ips = header.strip.split(/[,\s]+/)
ips.select do |ip|
begin
# Only return IPs that are valid according to the IPAddr#new method

@ -36,6 +36,11 @@ def initialize_sid
@default_options.delete(:sidbits)
@default_options.delete(:secure_random)
end
private
def make_request(env)
ActionDispatch::Request.new env
end
end
module StaleSessionCheck
@ -65,8 +70,8 @@ def stale_session_check!
end
module SessionObject # :nodoc:
def prepare_session(env)
Request::Session.create(self, env, @default_options)
def prepare_session(req)
Request::Session.create(self, req, @default_options)
end
def loaded_session?(session)
@ -81,8 +86,7 @@ class AbstractStore < Rack::Session::Abstract::ID
private
def set_cookie(env, session_id, cookie)
request = ActionDispatch::Request.new(env)
def set_cookie(request, session_id, cookie)
request.cookie_jar[key] = cookie
end
end

@ -71,16 +71,16 @@ def initialize(app, options={})
super(app, options.merge!(:cookie_only => true))
end
def destroy_session(env, session_id, options)
def destroy_session(req, session_id, options)
new_sid = generate_sid unless options[:drop]
# Reset hash and Assign the new session id
env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {}
req.set_header("action_dispatch.request.unsigned_session_cookie", new_sid ? { "session_id" => new_sid } : {})
new_sid
end
def load_session(env)
def load_session(req)
stale_session_check! do
data = unpacked_cookie_data(env)
data = unpacked_cookie_data(req)
data = persistent_session_id!(data)
[data["session_id"], data]
end
@ -88,20 +88,21 @@ def load_session(env)
private
def extract_session_id(env)
def extract_session_id(req)
stale_session_check! do
unpacked_cookie_data(env)["session_id"]
unpacked_cookie_data(req)["session_id"]
end
end
def unpacked_cookie_data(env)
env["action_dispatch.request.unsigned_session_cookie"] ||= begin
stale_session_check! do
if data = get_cookie(env)
def unpacked_cookie_data(req)
req.get_header("action_dispatch.request.unsigned_session_cookie") do |k|
v = stale_session_check! do
if data = get_cookie(req)
data.stringify_keys!
end
data || {}
end
req.set_header k, v
end
end
@ -111,21 +112,20 @@ def persistent_session_id!(data, sid=nil)
data
end
def set_session(env, sid, session_data, options)
def set_session(req, sid, session_data, options)
session_data["session_id"] = sid
session_data
end
def set_cookie(env, session_id, cookie)
cookie_jar(env)[@key] = cookie
def set_cookie(request, session_id, cookie)
cookie_jar(request)[@key] = cookie
end
def get_cookie(env)
cookie_jar(env)[@key]
def get_cookie(req)
cookie_jar(req)[@key]
end
def cookie_jar(env)
request = ActionDispatch::Request.new(env)
def cookie_jar(request)
request.cookie_jar.signed_or_encrypted
end
end

@ -27,24 +27,26 @@ def initialize(app, exceptions_app)
end
def call(env)
request = ActionDispatch::Request.new env
@app.call(env)
rescue Exception => exception
if env['action_dispatch.show_exceptions'] == false
raise exception
if request.show_exceptions?
render_exception(request, exception)
else
render_exception(env, exception)
raise exception
end
end
private
def render_exception(env, exception)
wrapper = ExceptionWrapper.new(env, exception)
def render_exception(request, exception)
backtrace_cleaner = request.get_header 'action_dispatch.backtrace_cleaner'
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
status = wrapper.status_code
env["action_dispatch.exception"] = wrapper.exception
env["action_dispatch.original_path"] = env["PATH_INFO"]
env["PATH_INFO"] = "/#{status}"
response = @exceptions_app.call(env)
request.set_header "action_dispatch.exception", wrapper.exception
request.set_header "action_dispatch.original_path", request.path_info
request.path_info = "/#{status}"
response = @exceptions_app.call(request.env)
response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response
rescue Exception => failsafe_error
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"

@ -4,36 +4,15 @@
module ActionDispatch
class MiddlewareStack
class Middleware
attr_reader :args, :block, :name, :classcache
attr_reader :args, :block, :klass
def initialize(klass_or_name, *args, &block)
@klass = nil
if klass_or_name.respond_to?(:name)
@klass = klass_or_name
@name = @klass.name
else
@name = klass_or_name.to_s
end
@classcache = ActiveSupport::Dependencies::Reference
@args, @block = args, block
def initialize(klass, args, block)
@klass = klass
@args = args
@block = block
end
def klass
@klass || classcache[@name]
end
def ==(middleware)
case middleware
when Middleware
klass == middleware.klass
when Class
klass == middleware
else
normalize(@name) == normalize(middleware)
end
end
def name; klass.name; end
def inspect
klass.to_s
@ -42,12 +21,6 @@ def inspect
def build(app)
klass.new(app, *args, &block)
end
private
def normalize(object)
object.to_s.strip.sub(/^::/, '')
end
end
include Enumerable
@ -75,19 +48,17 @@ def [](i)
middlewares[i]
end
def unshift(*args, &block)
middleware = self.class::Middleware.new(*args, &block)
middlewares.unshift(middleware)
def unshift(klass, *args, &block)
middlewares.unshift(build_middleware(klass, args, block))
end
def initialize_copy(other)
self.middlewares = other.middlewares.dup
end
def insert(index, *args, &block)
def insert(index, klass, *args, &block)
index = assert_index(index, :before)
middleware = self.class::Middleware.new(*args, &block)
middlewares.insert(index, middleware)
middlewares.insert(index, build_middleware(klass, args, block))
end
alias_method :insert_before, :insert
@ -104,26 +75,48 @@ def swap(target, *args, &block)
end
def delete(target)
middlewares.delete target
target = get_class target
middlewares.delete_if { |m| m.klass == target }
end
def use(*args, &block)
middleware = self.class::Middleware.new(*args, &block)
middlewares.push(middleware)
def use(klass, *args, &block)
middlewares.push(build_middleware(klass, args, block))
end
def build(app = nil, &block)
app ||= block
raise "MiddlewareStack#build requires an app" unless app
def build(app = Proc.new)
middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) }
end
protected
def assert_index(index, where)
i = index.is_a?(Integer) ? index : middlewares.index(index)
index = get_class index
i = index.is_a?(Integer) ? index : middlewares.index { |m| m.klass == index }
raise "No such middleware to insert #{where}: #{index.inspect}" unless i
i
end
private
def get_class(klass)
if klass.is_a?(String) || klass.is_a?(Symbol)
classcache = ActiveSupport::Dependencies::Reference
converted_klass = classcache[klass.to_s]
ActiveSupport::Deprecation.warn <<-eowarn
Passing strings or symbols to the middleware builder is deprecated, please change
them to actual class references. For example:
"#{klass}" => #{converted_klass}
eowarn
converted_klass
else
klass
end
end
def build_middleware(klass, args, block)
Middleware.new(get_class(klass), args, block)
end
end
end

@ -35,7 +35,7 @@ def match?(path)
paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
if match = paths.detect { |p|
path = File.join(@root, p.force_encoding('UTF-8'))
path = File.join(@root, p.force_encoding('UTF-8'.freeze))
begin
File.file?(path) && File.readable?(path)
rescue SystemCallError
@ -48,26 +48,30 @@ def match?(path)
end
def call(env)
path = env['PATH_INFO']
serve ActionDispatch::Request.new env
end
def serve(request)
path = request.path_info
gzip_path = gzip_file_path(path)
if gzip_path && gzip_encoding_accepted?(env)
env['PATH_INFO'] = gzip_path
status, headers, body = @file_server.call(env)
if gzip_path && gzip_encoding_accepted?(request)
request.path_info = gzip_path
status, headers, body = @file_server.call(request.env)
if status == 304
return [status, headers, body]
end
headers['Content-Encoding'] = 'gzip'
headers['Content-Type'] = content_type(path)
else
status, headers, body = @file_server.call(env)
status, headers, body = @file_server.call(request.env)
end
headers['Vary'] = 'Accept-Encoding' if gzip_path
return [status, headers, body]
ensure
env['PATH_INFO'] = path
request.path_info = path
end
private
@ -76,11 +80,11 @@ def ext
end
def content_type(path)
::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
::Rack::Mime.mime_type(::File.extname(path), 'text/plain'.freeze)
end
def gzip_encoding_accepted?(env)
env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/i
def gzip_encoding_accepted?(request)
request.accept_encoding =~ /\bgzip\b/i
end
def gzip_file_path(path)
@ -110,16 +114,17 @@ def initialize(app, path, cache_control = nil, index: 'index')
end
def call(env)
case env['REQUEST_METHOD']
when 'GET', 'HEAD'
path = env['PATH_INFO'].chomp('/')
req = ActionDispatch::Request.new env
if req.get? || req.head?
path = req.path_info.chomp('/'.freeze)
if match = @file_handler.match?(path)
env['PATH_INFO'] = match
return @file_handler.call(env)
req.path_info = match
return @file_handler.serve(req)
end
end
@app.call(env)
@app.call(req.env)
end
end
end

@ -4,38 +4,38 @@ module ActionDispatch
class Request < Rack::Request
# Session is responsible for lazily loading the session from store.
class Session # :nodoc:
ENV_SESSION_KEY = Rack::Session::Abstract::ENV_SESSION_KEY # :nodoc:
ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY # :nodoc:
ENV_SESSION_KEY = Rack::RACK_SESSION # :nodoc:
ENV_SESSION_OPTIONS_KEY = Rack::RACK_SESSION_OPTIONS # :nodoc:
# Singleton object used to determine if an optional param wasn't specified
Unspecified = Object.new
# Creates a session hash, merging the properties of the previous session if any
def self.create(store, env, default_options)
session_was = find env
session = Request::Session.new(store, env)
def self.create(store, req, default_options)
session_was = find req
session = Request::Session.new(store, req)
session.merge! session_was if session_was
set(env, session)
Options.set(env, Request::Session::Options.new(store, default_options))
set(req, session)
Options.set(req, Request::Session::Options.new(store, default_options))
session
end
def self.find(env)
env[ENV_SESSION_KEY]
def self.find(req)
req.get_header ENV_SESSION_KEY
end
def self.set(env, session)
env[ENV_SESSION_KEY] = session
def self.set(req, session)
req.set_header ENV_SESSION_KEY, session
end
class Options #:nodoc:
def self.set(env, options)
env[ENV_SESSION_OPTIONS_KEY] = options
def self.set(req, options)
req.set_header ENV_SESSION_OPTIONS_KEY, options
end
def self.find(env)
env[ENV_SESSION_OPTIONS_KEY]
def self.find(req)
req.get_header ENV_SESSION_OPTIONS_KEY
end
def initialize(by, default_options)
@ -47,9 +47,9 @@ def [](key)
@delegate[key]
end
def id(env)
def id(req)
@delegate.fetch(:id) {
@by.send(:extract_session_id, env)
@by.send(:extract_session_id, req)
}
end
@ -58,26 +58,26 @@ def to_hash; @delegate.dup; end
def values_at(*args); @delegate.values_at(*args); end
end
def initialize(by, env)
def initialize(by, req)
@by = by
@env = env
@req = req
@delegate = {}
@loaded = false
@exists = nil # we haven't checked yet
end
def id
options.id(@env)
options.id(@req)
end
def options
Options.find @env
Options.find @req
end
def destroy
clear
options = self.options || {}
@by.send(:destroy_session, @env, options.id(@env), options)
@by.send(:destroy_session, @req, options.id(@req), options)
# Load the new sid to be written with the response
@loaded = false
@ -181,7 +181,7 @@ def inspect
def exists?
return @exists unless @exists.nil?
@exists = @by.send(:session_exists?, @env)
@exists = @by.send(:session_exists?, @req)
end
def loaded?
@ -209,7 +209,7 @@ def load_for_write!
end
def load!
id, session = @by.load_session @env
id, session = @by.load_session @req
options[:id] = id
@delegate.replace(stringify_keys(session))
@loaded = true

@ -5,24 +5,45 @@ class Utils # :nodoc:
mattr_accessor :perform_deep_munge
self.perform_deep_munge = true
class << self
# Remove nils from the params hash
def deep_munge(hash, keys = [])
return hash unless perform_deep_munge
def self.normalize_encode_params(params)
if perform_deep_munge
NoNilParamEncoder.normalize_encode_params params
else
ParamEncoder.normalize_encode_params params
end
end
hash.each do |k, v|
keys << k
case v
when Array
v.grep(Hash) { |x| deep_munge(x, keys) }
v.compact!
when Hash
deep_munge(v, keys)
class ParamEncoder # :nodoc:
# Convert nested Hash to HashWithIndifferentAccess.
#
def self.normalize_encode_params(params)
case params
when Array
handle_array params
when Hash
if params.has_key?(:tempfile)
ActionDispatch::Http::UploadedFile.new(params)
else
params.each_with_object({}) do |(key, val), new_hash|
new_hash[key] = normalize_encode_params(val)
end.with_indifferent_access
end
keys.pop
else
params
end
end
hash
def self.handle_array(params)
params.map! { |el| normalize_encode_params(el) }
end
end
# Remove nils from the params hash
class NoNilParamEncoder < ParamEncoder # :nodoc:
def self.handle_array(params)
list = super
list.compact!
list
end
end
end

File diff suppressed because it is too large Load Diff

@ -24,7 +24,7 @@ def call(env)
def serve(req)
req.check_path_parameters!
uri = URI.parse(path(req.path_parameters, req))
unless uri.host
if relative_path?(uri.path)
uri.path = "#{req.script_name}/#{uri.path}"
@ -32,7 +32,7 @@ def serve(req)
uri.path = req.script_name.empty? ? "/" : req.script_name
end
end
uri.scheme ||= req.scheme
uri.host ||= req.host
uri.port ||= req.port unless req.standard_port?
@ -124,7 +124,7 @@ def path(params, request)
url_options[:script_name] = request.script_name
end
end
ActionDispatch::Http::URL.url_for url_options
end

@ -1,6 +1,5 @@
require 'action_dispatch/journey'
require 'forwardable'
require 'thread_safe'
require 'active_support/concern'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/hash/slice'
@ -21,64 +20,35 @@ class RouteSet
alias inspect to_s
class Dispatcher < Routing::Endpoint
def initialize(defaults)
@defaults = defaults
@controller_class_names = ThreadSafe::Cache.new
def initialize(raise_on_name_error)
@raise_on_name_error = raise_on_name_error
end
def dispatcher?; true; end
def serve(req)
req.check_path_parameters!
params = req.path_parameters
prepare_params!(params)
# Just raise undefined constant errors if a controller was specified as default.
unless controller = controller(params, @defaults.key?(:controller))
controller = controller_reference(req) do
return [404, {'X-Cascade' => 'pass'}, []]
end
dispatch(controller, params[:action], req.env)
end
def prepare_params!(params)
normalize_controller!(params)
merge_default_action!(params)
end
# If this is a default_controller (i.e. a controller specified by the user)
# we should raise an error in case it's not found, because it usually means
# a user error. However, if the controller was retrieved through a dynamic
# segment, as in :controller(/:action), we should simply return nil and
# delegate the control back to Rack cascade. Besides, if this is not a default
# controller, it means we should respect the @scope[:module] parameter.
def controller(params, default_controller=true)
if params && params.key?(:controller)
controller_param = params[:controller]
controller_reference(controller_param)
end
dispatch(controller, params[:action], req)
rescue NameError => e
raise ActionController::RoutingError, e.message, e.backtrace if default_controller
if @raise_on_name_error
raise ActionController::RoutingError, e.message, e.backtrace
else
return [404, {'X-Cascade' => 'pass'}, []]
end
end
protected
def controller_reference(req, &block)
req.controller_class(&block)
end
private
def controller_reference(controller_param)
const_name = @controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller"
ActiveSupport::Dependencies.constantize(const_name)
end
def dispatch(controller, action, env)
controller.action(action).call(env)
end
def normalize_controller!(params)
params[:controller] = params[:controller].underscore if params.key?(:controller)
end
def merge_default_action!(params)
params[:action] ||= 'index'
def dispatch(controller, action, req)
controller.action(action).call(req.env)
end
end
@ -88,6 +58,7 @@ def merge_default_action!(params)
class NamedRouteCollection
include Enumerable
attr_reader :routes, :url_helpers_module, :path_helpers_module
private :routes
def initialize
@routes = {}
@ -142,6 +113,7 @@ def get(name)
end
def key?(name)
return unless name
routes.key? name.to_sym
end
@ -267,9 +239,13 @@ def handle_positional_args(controller_options, inner_options, args, result, path
path_params -= controller_options.keys
path_params -= result.keys
end
path_params -= inner_options.keys
path_params.take(args.size).each do |param|
result[param] = args.shift
inner_options.each_key do |key|
path_params.delete(key)
end
args.each_with_index do |arg, index|
param = path_params[index]
result[param] = arg if param
end
end
@ -309,7 +285,7 @@ def define_url_helper(mod, route, name, opts, route_key, url_strategy)
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
attr_accessor :default_url_options
attr_accessor :default_url_options, :dispatcher_class
attr_reader :env_key
alias :routes :set
@ -351,7 +327,8 @@ def initialize(config = DEFAULT_CONFIG)
@set = Journey::Routes.new
@router = Journey::Router.new @set
@formatter = Journey::Formatter.new @set
@formatter = Journey::Formatter.new self
@dispatcher_class = Routing::RouteSet::Dispatcher
end
def relative_url_root
@ -409,8 +386,8 @@ def clear!
@prepend.each { |blk| eval_block(blk) }
end
def dispatcher(defaults)
Routing::RouteSet::Dispatcher.new(defaults)
def dispatcher(raise_on_name_error)
dispatcher_class.new(raise_on_name_error)
end
module MountedHelpers
@ -508,7 +485,7 @@ def empty?
routes.empty?
end
def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
def add_route(mapping, path_ast, name, anchor)
raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
if name && named_routes[name]
@ -519,74 +496,17 @@ def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil
"http://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
end
path = conditions.delete :path_info
ast = conditions.delete :parsed_path_info
required_defaults = conditions.delete :required_defaults
path = build_path(path, ast, requirements, anchor)
conditions = build_conditions(conditions)
route = @set.add_route(app, path, conditions, required_defaults, defaults, name)
route = @set.add_route(name, mapping)
named_routes[name] = route if name
route
end
def build_path(path, ast, requirements, anchor)
strexp = Journey::Router::Strexp.new(
ast,
path,
requirements,
SEPARATORS,
anchor)
pattern = Journey::Path::Pattern.new(strexp)
builder = Journey::GTG::Builder.new pattern.spec
# Get all the symbol nodes followed by literals that are not the
# dummy node.
symbols = pattern.spec.grep(Journey::Nodes::Symbol).find_all { |n|
builder.followpos(n).first.literal?
}
# Get all the symbol nodes preceded by literals.
symbols.concat pattern.spec.find_all(&:literal?).map { |n|
builder.followpos(n).first
}.find_all(&:symbol?)
symbols.each { |x|
x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/
}
pattern
end
private :build_path
def build_conditions(current_conditions)
conditions = current_conditions.dup
# Rack-Mount requires that :request_method be a regular expression.
# :request_method represents the HTTP verb that matches this route.
#
# Here we munge values before they get sent on to rack-mount.
verbs = conditions[:request_method] || []
unless verbs.empty?
conditions[:request_method] = %r[^#{verbs.join('|')}$]
end
conditions.keep_if do |k, _|
request_class.public_method_defined?(k)
end
end
private :build_conditions
class Generator
PARAMETERIZE = lambda do |name, value|
if name == :controller
value
elsif value.is_a?(Array)
value.map(&:to_param).join('/')
elsif param = value.to_param
param
else
value.to_param
end
end
@ -594,8 +514,8 @@ class Generator
def initialize(named_route, options, recall, set)
@named_route = named_route
@options = options.dup
@recall = recall.dup
@options = options
@recall = recall
@set = set
normalize_recall!
@ -617,7 +537,7 @@ def current_controller
def use_recall_for(key)
if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key])
if !named_route_exists? || segment_keys.include?(key)
@options[key] = @recall.delete(key)
@options[key] = @recall[key]
end
end
end
@ -671,12 +591,18 @@ def use_relative_controller!
# Remove leading slashes from controllers
def normalize_controller!
@options[:controller] = controller.sub(%r{^/}, '') if controller
if controller
if controller.start_with?("/".freeze)
@options[:controller] = controller[1..-1]
else
@options[:controller] = controller
end
end
end
# Move 'index' action from options to recall
def normalize_action!
if @options[:action] == 'index'
if @options[:action] == 'index'.freeze
@recall[:action] = @options.delete(:action)
end
end
@ -803,14 +729,13 @@ def recognize_path(path, environment = {})
req.path_parameters = old_params.merge params
app = route.app
if app.matches?(req) && app.dispatcher?
dispatcher = app.app
if dispatcher.controller(params, false)
dispatcher.prepare_params!(params)
return params
else
begin
req.controller_class
rescue NameError
raise ActionController::RoutingError, "A route matches #{path.inspect}, but references missing controller: #{params[:controller].camelize}Controller"
end
return req.path_parameters
end
end

@ -171,6 +171,10 @@ def url_for(options = nil)
route_name = options.delete :use_route
_routes.url_for(options.symbolize_keys.reverse_merge!(url_options),
route_name)
when ActionController::Parameters
route_name = options.delete :use_route
_routes.url_for(options.to_unsafe_h.symbolize_keys.
reverse_merge!(url_options), route_name)
when String
options
when Symbol

@ -3,6 +3,13 @@ module ActionDispatch
module Assertions
# A small suite of assertions that test responses from \Rails applications.
module ResponseAssertions
RESPONSE_PREDICATES = { # :nodoc:
success: :successful?,
missing: :not_found?,
redirect: :redirection?,
error: :server_error?,
}
# Asserts that the response is one of the following types:
#
# * <tt>:success</tt> - Status code was in the 200-299 range
@ -20,11 +27,9 @@ module ResponseAssertions
# # assert that the response code was status code 401 (unauthorized)
# assert_response 401
def assert_response(type, message = nil)
message ||= "Expected response to be a <#{type}>, but was <#{@response.response_code}>"
if Symbol === type
if [:success, :missing, :redirect, :error].include?(type)
assert @response.send("#{type}?"), message
assert_predicate @response, RESPONSE_PREDICATES[type], message
else
code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
if code.nil?

@ -86,8 +86,8 @@ def assert_generates(expected_path, options, defaults={}, extras={}, message=nil
end
# Load routes.rb if it hasn't been loaded.
generated_path, extra_keys = @routes.generate_extras(options, defaults)
found_extras = options.reject { |k, _| ! extra_keys.include? k }
generated_path, query_string_keys = @routes.generate_extras(options, defaults)
found_extras = options.reject { |k, _| ! query_string_keys.include? k }
msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras)
assert_equal(extras, found_extras, msg)
@ -165,7 +165,7 @@ def with_routing
# ROUTES TODO: These assertions should really work in an integration context
def method_missing(selector, *args, &block)
if defined?(@controller) && @controller && @routes && @routes.named_routes.route_defined?(selector)
if defined?(@controller) && @controller && defined?(@routes) && @routes && @routes.named_routes.route_defined?(selector)
@controller.send(selector, *args, &block)
else
super

@ -325,7 +325,11 @@ def process(method, path, params: nil, headers: nil, env: nil, xhr: false)
if path =~ %r{://}
location = URI.parse(path)
https! URI::HTTPS === location if location.scheme
host! "#{location.host}:#{location.port}" if location.host
if url_host = location.host
default = Rack::Request::DEFAULT_PORTS[location.scheme]
url_host += ":#{location.port}" if default != location.port
host! url_host
end
path = location.query ? "#{location.path}?#{location.query}" : location.path
end
@ -355,10 +359,10 @@ def process(method, path, params: nil, headers: nil, env: nil, xhr: false)
# this modifies the passed request_env directly
if headers.present?
Http::Headers.new(request_env).merge!(headers)
Http::Headers.from_hash(request_env).merge!(headers)
end
if env.present?
Http::Headers.new(request_env).merge!(env)
Http::Headers.from_hash(request_env).merge!(env)
end
session = Rack::Test::Session.new(_mock_session)
@ -374,7 +378,7 @@ def process(method, path, params: nil, headers: nil, env: nil, xhr: false)
@html_document = nil
@url_options = nil
@controller = session.last_request.env['action_controller.instance']
@controller = @request.controller_instance
response.status
end
@ -391,7 +395,7 @@ module Runner
attr_reader :app
def before_setup
def before_setup # :nodoc:
@app = nil
@integration_session = nil
super

@ -19,7 +19,7 @@ def flash
end
def cookies
@cookie_jar ||= Cookies::CookieJar.build(@request.env, @request.host, @request.ssl?, @request.cookies)
@cookie_jar ||= Cookies::CookieJar.build(@request, @request.cookies)
end
def redirect_to_url

@ -16,9 +16,6 @@ def self.from_response(response)
# Was the URL not found?
alias_method :missing?, :not_found?
# Were we redirected?
alias_method :redirect?, :redirection?
# Was there a server-side error?
alias_method :error?, :server_error?
end

@ -63,6 +63,10 @@ def env
SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
SharedTestRoutes.draw do
get ':controller(/:action)'
end
module ActionDispatch
module SharedRoutes
def before_setup
@ -70,35 +74,10 @@ def before_setup
super
end
end
# Hold off drawing routes until all the possible controller classes
# have been loaded.
module DrawOnce
class << self
attr_accessor :drew
end
self.drew = false
def before_setup
super
return if DrawOnce.drew
SharedTestRoutes.draw do
get ':controller(/:action)'
end
ActionDispatch::IntegrationTest.app.routes.draw do
get ':controller(/:action)'
end
DrawOnce.drew = true
end
end
end
module ActiveSupport
class TestCase
include ActionDispatch::DrawOnce
if RUBY_ENGINE == "ruby" && PROCESS_COUNT > 0
parallelize_me!
end
@ -119,29 +98,31 @@ def call(env)
end
class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
include ActionDispatch::SharedRoutes
def self.build_app(routes = nil)
RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware|
middleware.use "ActionDispatch::ShowExceptions", ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
middleware.use "ActionDispatch::DebugExceptions"
middleware.use "ActionDispatch::Callbacks"
middleware.use "ActionDispatch::ParamsParser"
middleware.use "ActionDispatch::Cookies"
middleware.use "ActionDispatch::Flash"
middleware.use "Rack::Head"
middleware.use ActionDispatch::ShowExceptions, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
middleware.use ActionDispatch::DebugExceptions
middleware.use ActionDispatch::Callbacks
middleware.use ActionDispatch::ParamsParser
middleware.use ActionDispatch::Cookies
middleware.use ActionDispatch::Flash
middleware.use Rack::Head
yield(middleware) if block_given?
end
end
self.app = build_app
app.routes.draw do
get ':controller(/:action)'
end
# Stub Rails dispatcher so it does not get controller references and
# simply return the controller#action as Rack::Body.
class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher
protected
def controller_reference(controller_param)
controller_param
controller_param.params[:controller]
end
def dispatch(controller, action, env)
@ -149,14 +130,10 @@ def dispatch(controller, action, env)
end
end
def self.stub_controllers
old_dispatcher = ActionDispatch::Routing::RouteSet::Dispatcher
ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, StubDispatcher }
yield ActionDispatch::Routing::RouteSet.new
ensure
ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, old_dispatcher }
def self.stub_controllers(config = nil)
route_set = ActionDispatch::Routing::RouteSet.new(*[config].compact)
route_set.dispatcher_class = StubDispatcher
yield route_set
end
def with_routing(&block)

@ -7,7 +7,7 @@ class ResponseAssertionsTest < ActiveSupport::TestCase
include ResponseAssertions
FakeResponse = Struct.new(:response_code) do
[:success, :missing, :redirect, :error].each do |sym|
[:successful, :not_found, :redirection, :server_error].each do |sym|
define_method("#{sym}?") do
sym == response_code
end
@ -16,7 +16,7 @@ class ResponseAssertionsTest < ActiveSupport::TestCase
def test_assert_response_predicate_methods
[:success, :missing, :redirect, :error].each do |sym|
@response = FakeResponse.new sym
@response = FakeResponse.new RESPONSE_PREDICATES[sym].to_s.sub(/\?/, '').to_sym
assert_response sym
assert_raises(Minitest::Assertion) {

@ -43,12 +43,12 @@ def response599() head '599 Whoah!' end
def flash_me
flash['hello'] = 'my name is inigo montoya...'
render :text => "Inconceivable!"
render plain: "Inconceivable!"
end
def flash_me_naked
flash.clear
render :text => "wow!"
render plain: "wow!"
end
def assign_this
@ -57,30 +57,30 @@ def assign_this
end
def render_based_on_parameters
render :text => "Mr. #{params[:name]}"
render plain: "Mr. #{params[:name]}"
end
def render_url
render :text => "<div>#{url_for(:action => 'flash_me', :only_path => true)}</div>"
render html: "<div>#{url_for(action: 'flash_me', only_path: true)}</div>"
end
def render_text_with_custom_content_type
render :text => "Hello!", :content_type => Mime::RSS
render body: "Hello!", content_type: Mime::RSS
end
def session_stuffing
session['xmas'] = 'turkey'
render :text => "ho ho ho"
render plain: "ho ho ho"
end
def raise_exception_on_get
raise "get" if request.get?
render :text => "request method: #{request.env['REQUEST_METHOD']}"
render plain: "request method: #{request.env['REQUEST_METHOD']}"
end
def raise_exception_on_post
raise "post" if request.post?
render :text => "request method: #{request.env['REQUEST_METHOD']}"
render plain: "request method: #{request.env['REQUEST_METHOD']}"
end
def render_file_absolute_path
@ -101,7 +101,7 @@ def index
end
def show
render :text => "Boom", :status => 500
render plain: "Boom", status: 500
end
end
@ -318,7 +318,7 @@ def test_server_error_response_code
def test_missing_response_code
process :response404
assert @response.missing?
assert @response.not_found?
end
def test_client_error_response_code
@ -346,12 +346,12 @@ def test_redirection
def test_successful_response_code
process :nothing
assert @response.success?
assert @response.successful?
end
def test_response_object
process :nothing
assert_kind_of ActionController::TestResponse, @response
assert_kind_of ActionDispatch::TestResponse, @response
end
def test_render_based_on_parameters

@ -7,12 +7,12 @@ class ConditionalGetApiController < ActionController::API
def one
if stale?(last_modified: Time.now.utc.beginning_of_day, etag: [:foo, 123])
render text: "Hi!"
render plain: "Hi!"
end
end
def two
render text: "Hi!"
render plain: "Hi!"
end
private

@ -7,7 +7,7 @@ class UsersController < ActionController::API
wrap_parameters :person, format: [:json]
def test
self.last_parameters = params.except(:controller, :action)
self.last_parameters = params.except(:controller, :action).to_unsafe_h
head :ok
end
end

@ -53,7 +53,7 @@ class RecordIdentifierIncludedController < ActionController::Base
class ActionMissingController < ActionController::Base
def action_missing(action)
render :text => "Response for #{action}"
render plain: "Response for #{action}"
end
end

@ -4,29 +4,29 @@ class OldContentTypeController < ActionController::Base
# :ported:
def render_content_type_from_body
response.content_type = Mime::RSS
render :text => "hello world!"
render body: "hello world!"
end
# :ported:
def render_defaults
render :text => "hello world!"
render body: "hello world!"
end
# :ported:
def render_content_type_from_render
render :text => "hello world!", :content_type => Mime::RSS
render body: "hello world!", :content_type => Mime::RSS
end
# :ported:
def render_charset_from_body
response.charset = "utf-16"
render :text => "hello world!"
render body: "hello world!"
end
# :ported:
def render_nil_charset_from_body
response.charset = nil
render :text => "hello world!"
render body: "hello world!"
end
def render_default_for_erb
@ -42,10 +42,10 @@ def render_change_for_builder
def render_default_content_types_for_respond_to
respond_to do |format|
format.html { render :text => "hello world!" }
format.xml { render :action => "render_default_content_types_for_respond_to" }
format.js { render :text => "hello world!" }
format.rss { render :text => "hello world!", :content_type => Mime::XML }
format.html { render body: "hello world!" }
format.xml { render action: "render_default_content_types_for_respond_to" }
format.js { render body: "hello world!" }
format.rss { render body: "hello world!", content_type: Mime::XML }
end
end
end
@ -64,14 +64,14 @@ def setup
def test_render_defaults
get :render_defaults
assert_equal "utf-8", @response.charset
assert_equal Mime::HTML, @response.content_type
assert_equal Mime::TEXT, @response.content_type
end
def test_render_changed_charset_default
with_default_charset "utf-16" do
get :render_defaults
assert_equal "utf-16", @response.charset
assert_equal Mime::HTML, @response.content_type
assert_equal Mime::TEXT, @response.content_type
end
end
@ -92,14 +92,14 @@ def test_content_type_from_render
# :ported:
def test_charset_from_body
get :render_charset_from_body
assert_equal Mime::HTML, @response.content_type
assert_equal Mime::TEXT, @response.content_type
assert_equal "utf-16", @response.charset
end
# :ported:
def test_nil_charset_from_body
get :render_nil_charset_from_body
assert_equal Mime::HTML, @response.content_type
assert_equal Mime::TEXT, @response.content_type
assert_equal "utf-8", @response.charset, @response.headers.inspect
end

@ -6,7 +6,7 @@ class ControllerWithBeforeActionAndDefaultUrlOptions < ActionController::Base
after_action { I18n.locale = "en" }
def target
render :text => "final response"
render plain: "final response"
end
def redirect

@ -40,7 +40,7 @@ class ChangingTheRequirementsController < TestController
before_action :ensure_login, :except => [:go_wild]
def go_wild
render :text => "gobble"
render plain: "gobble"
end
end
@ -51,7 +51,7 @@ class TestMultipleFiltersController < ActionController::Base
(1..3).each do |i|
define_method "fail_#{i}" do
render :text => i.to_s
render plain: i.to_s
end
end
@ -222,7 +222,7 @@ class SkipFilterUsingOnlyAndIf < ConditionalFilterController
skip_before_action :clean_up_tmp, only: :login, if: -> { true }
def login
render text: 'ok'
render plain: 'ok'
end
end
@ -234,7 +234,7 @@ class SkipFilterUsingIfAndExcept < ConditionalFilterController
skip_before_action :clean_up_tmp, if: -> { true }, except: :login
def login
render text: 'ok'
render plain: 'ok'
end
end
@ -258,11 +258,11 @@ class SkippingAndLimitedController < TestController
before_action :ensure_login, :only => :index
def index
render :text => 'ok'
render plain: 'ok'
end
def public
render :text => 'ok'
render plain: 'ok'
end
end
@ -272,7 +272,7 @@ class SkippingAndReorderingController < TestController
before_action :ensure_login
def index
render :text => 'ok'
render plain: 'ok'
end
private
@ -383,7 +383,7 @@ class AuditController < ActionController::Base
before_action(AuditFilter)
def show
render :text => "hello"
render plain: "hello"
end
end
@ -421,11 +421,11 @@ class OutOfOrder < StandardError; end
before_action :second, :only => :foo
def foo
render :text => 'foo'
render plain: 'foo'
end
def bar
render :text => 'bar'
render plain: 'bar'
end
protected
@ -442,7 +442,7 @@ class DynamicDispatchController < ActionController::Base
before_action :choose
%w(foo bar baz).each do |action|
define_method(action) { render :text => action }
define_method(action) { render plain: action }
end
private
@ -471,7 +471,7 @@ def between_before_all_and_after_all
@ran_filter << 'between_before_all_and_after_all'
end
def show
render :text => 'hello'
render plain: 'hello'
end
end
@ -481,7 +481,7 @@ class RescuingAroundFilterWithBlock
def around(controller)
yield
rescue ErrorToRescue => ex
controller.__send__ :render, :text => "I rescued this: #{ex.inspect}"
controller.__send__ :render, plain: "I rescued this: #{ex.inspect}"
end
end
@ -819,7 +819,7 @@ def test_a_rescuing_around_action
response = test_process(RescuedController)
end
assert response.success?
assert response.successful?
assert_equal("I rescued this: #<FilterTest::ErrorToRescue: Something made the bad noise.>", response.body)
end

@ -329,7 +329,7 @@ def with_test_route_set
@app = self.class.build_app(set) do |middleware|
middleware.use ActionDispatch::Session::CookieStore, :key => SessionKey
middleware.use ActionDispatch::Flash
middleware.delete "ActionDispatch::ShowExceptions"
middleware.delete ActionDispatch::ShowExceptions
end
yield

@ -2,11 +2,11 @@
class ForceSSLController < ActionController::Base
def banana
render :text => "monkey"
render plain: "monkey"
end
def cheeseburger
render :text => "sikachu"
render plain: "sikachu"
end
end
@ -26,7 +26,7 @@ class ForceSSLCustomOptions < ForceSSLController
force_ssl :notice => 'Foo, Bar!', :only => :redirect_notice
def force_ssl_action
render :text => action_name
render plain: action_name
end
alias_method :redirect_host, :force_ssl_action
@ -40,15 +40,15 @@ def force_ssl_action
alias_method :redirect_notice, :force_ssl_action
def use_flash
render :text => flash[:message]
render plain: flash[:message]
end
def use_alert
render :text => flash[:alert]
render plain: flash[:alert]
end
def use_notice
render :text => flash[:notice]
render plain: flash[:notice]
end
end
@ -85,10 +85,10 @@ def use_flash
class RedirectToSSL < ForceSSLController
def banana
force_ssl_redirect || render(:text => 'monkey')
force_ssl_redirect || render(plain: 'monkey')
end
def cheeseburger
force_ssl_redirect('secure.cheeseburger.host') || render(:text => 'ihaz')
force_ssl_redirect('secure.cheeseburger.host') || render(plain: 'ihaz')
end
end

@ -151,7 +151,7 @@ def test_helper_for_acronym_controller
assert_equal "test: baz", call_controller(Fun::PdfController, "test").last.body
#
# request = ActionController::TestRequest.new
# response = ActionController::TestResponse.new
# response = ActionDispatch::TestResponse.new
# request.action = 'test'
#
# assert_equal 'test: baz', Fun::PdfController.process(request, response).body

@ -9,19 +9,19 @@ class DummyController < ActionController::Base
http_basic_authenticate_with :name => "David", :password => "Goliath", :only => :search
def index
render :text => "Hello Secret"
render plain: "Hello Secret"
end
def display
render :text => 'Definitely Maybe' if @logged_in
render plain: 'Definitely Maybe' if @logged_in
end
def show
render :text => 'Only for loooooong credentials'
render plain: 'Only for loooooong credentials'
end
def search
render :text => 'All inline'
render plain: 'All inline'
end
private
@ -100,6 +100,14 @@ def test_encode_credentials_has_no_newline
assert_no_match(/\n/, result)
end
test "succesful authentication with uppercase authorization scheme" do
@request.env['HTTP_AUTHORIZATION'] = "BASIC #{::Base64.encode64("lifo:world")}"
get :index
assert_response :success
assert_equal 'Hello Secret', @response.body, 'Authentication failed when authorization scheme BASIC'
end
test "authentication request without credential" do
get :display

@ -10,11 +10,11 @@ class DummyDigestController < ActionController::Base
'dhh' => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":"))}
def index
render :text => "Hello Secret"
render plain: "Hello Secret"
end
def display
render :text => 'Definitely Maybe' if @logged_in
render plain: 'Definitely Maybe' if @logged_in
end
private

@ -7,15 +7,15 @@ class DummyController < ActionController::Base
before_action :authenticate_long_credentials, only: :show
def index
render :text => "Hello Secret"
render plain: "Hello Secret"
end
def display
render :text => 'Definitely Maybe'
render plain: 'Definitely Maybe'
end
def show
render :text => 'Only for loooooong credentials'
render plain: 'Only for loooooong credentials'
end
private

@ -359,28 +359,28 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
class IntegrationController < ActionController::Base
def get
respond_to do |format|
format.html { render :text => "OK", :status => 200 }
format.js { render :text => "JS OK", :status => 200 }
format.html { render plain: "OK", status: 200 }
format.js { render plain: "JS OK", status: 200 }
format.xml { render :xml => "<root></root>", :status => 200 }
end
end
def get_with_params
render :text => "foo: #{params[:foo]}", :status => 200
render plain: "foo: #{params[:foo]}", status: 200
end
def post
render :text => "Created", :status => 201
render plain: "Created", status: 201
end
def method
render :text => "method: #{request.method.downcase}"
render plain: "method: #{request.method.downcase}"
end
def cookie_monster
cookies["cookie_1"] = nil
cookies["cookie_3"] = "chocolate"
render :text => "Gone", :status => 410
render plain: "Gone", status: 410
end
def set_cookie
@ -389,7 +389,7 @@ def set_cookie
end
def get_cookie
render :text => cookies["foo"]
render plain: cookies["foo"]
end
def redirect
@ -755,12 +755,31 @@ def test_pass_env
assert_equal "http://test.com/", @request.env["HTTP_REFERER"]
end
def test_ignores_common_ports_in_host
get "http://test.com"
assert_equal "test.com", @request.env["HTTP_HOST"]
get "https://test.com"
assert_equal "test.com", @request.env["HTTP_HOST"]
end
def test_keeps_uncommon_ports_in_host
get "http://test.com:123"
assert_equal "test.com:123", @request.env["HTTP_HOST"]
get "http://test.com:443"
assert_equal "test.com:443", @request.env["HTTP_HOST"]
get "https://test.com:80"
assert_equal "test.com:80", @request.env["HTTP_HOST"]
end
end
class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
class TestController < ActionController::Base
def index
render :text => "index"
render plain: "index"
end
end
@ -847,7 +866,7 @@ def app
class EnvironmentFilterIntegrationTest < ActionDispatch::IntegrationTest
class TestController < ActionController::Base
def post
render :text => "Created", :status => 201
render plain: "Created", status: 201
end
end
@ -880,15 +899,15 @@ def app
class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest
class FooController < ActionController::Base
def index
render :text => "foo#index"
render plain: "foo#index"
end
def show
render :text => "foo#show"
render plain: "foo#show"
end
def edit
render :text => "foo#show"
render plain: "foo#show"
end
end
@ -898,7 +917,7 @@ def default_url_options
end
def index
render :text => "foo#index"
render plain: "foo#index"
end
end

@ -1,5 +1,5 @@
require 'abstract_unit'
require 'active_support/concurrency/latch'
require 'concurrent/atomics'
Thread.abort_on_exception = true
module ActionController
@ -125,7 +125,7 @@ def set_cookie
end
def render_text
render :text => 'zomg'
render plain: 'zomg'
end
def default_header
@ -145,7 +145,7 @@ def blocking_stream
response.headers['Content-Type'] = 'text/event-stream'
%w{ hello world }.each do |word|
response.stream.write word
latch.await
latch.wait
end
response.stream.close
end
@ -162,7 +162,7 @@ def thread_locals
end
def with_stale
render text: 'stale' if stale?(etag: "123", template: false)
render plain: 'stale' if stale?(etag: "123", template: false)
end
def exception_in_view
@ -212,7 +212,7 @@ def overfill_buffer_and_die
# .. plus one more, because the #each frees up a slot:
response.stream.write '.'
latch.release
latch.count_down
# This write will block, and eventually raise
response.stream.write 'x'
@ -233,7 +233,7 @@ def ignore_client_disconnect
end
logger.info 'Work complete'
latch.release
latch.count_down
end
end
@ -278,7 +278,7 @@ def test_write_to_stream
def test_async_stream
rubinius_skip "https://github.com/rubinius/rubinius/issues/2934"
@controller.latch = ActiveSupport::Concurrency::Latch.new
@controller.latch = Concurrent::CountDownLatch.new
parts = ['hello', 'world']
@controller.request = @request
@ -289,8 +289,8 @@ def test_async_stream
resp.stream.each do |part|
assert_equal parts.shift, part
ol = @controller.latch
@controller.latch = ActiveSupport::Concurrency::Latch.new
ol.release
@controller.latch = Concurrent::CountDownLatch.new
ol.count_down
end
}
@ -300,23 +300,23 @@ def test_async_stream
end
def test_abort_with_full_buffer
@controller.latch = ActiveSupport::Concurrency::Latch.new
@controller.latch = Concurrent::CountDownLatch.new
@request.parameters[:format] = 'plain'
@controller.request = @request
@controller.response = @response
got_error = ActiveSupport::Concurrency::Latch.new
got_error = Concurrent::CountDownLatch.new
@response.stream.on_error do
ActionController::Base.logger.warn 'Error while streaming'
got_error.release
got_error.count_down
end
t = Thread.new(@response) { |resp|
resp.await_commit
_, _, body = resp.to_a
body.each do |part|
@controller.latch.await
@controller.latch.wait
body.close
break
end
@ -325,13 +325,13 @@ def test_abort_with_full_buffer
capture_log_output do |output|
@controller.process :overfill_buffer_and_die
t.join
got_error.await
got_error.wait
assert_match 'Error while streaming', output.rewind && output.read
end
end
def test_ignore_client_disconnect
@controller.latch = ActiveSupport::Concurrency::Latch.new
@controller.latch = Concurrent::CountDownLatch.new
@controller.request = @request
@controller.response = @response
@ -349,7 +349,7 @@ def test_ignore_client_disconnect
@controller.process :ignore_client_disconnect
t.join
Timeout.timeout(3) do
@controller.latch.await
@controller.latch.wait
end
assert_match 'Work complete', output.rewind && output.read
end

@ -13,41 +13,41 @@ class RespondToController < ActionController::Base
def html_xml_or_rss
respond_to do |type|
type.html { render :text => "HTML" }
type.xml { render :text => "XML" }
type.rss { render :text => "RSS" }
type.all { render :text => "Nothing" }
type.html { render body: "HTML" }
type.xml { render body: "XML" }
type.rss { render body: "RSS" }
type.all { render body: "Nothing" }
end
end
def js_or_html
respond_to do |type|
type.html { render :text => "HTML" }
type.js { render :text => "JS" }
type.all { render :text => "Nothing" }
type.html { render body: "HTML" }
type.js { render body: "JS" }
type.all { render body: "Nothing" }
end
end
def json_or_yaml
respond_to do |type|
type.json { render :text => "JSON" }
type.yaml { render :text => "YAML" }
type.json { render body: "JSON" }
type.yaml { render body: "YAML" }
end
end
def html_or_xml
respond_to do |type|
type.html { render :text => "HTML" }
type.xml { render :text => "XML" }
type.all { render :text => "Nothing" }
type.html { render body: "HTML" }
type.xml { render body: "XML" }
type.all { render body: "Nothing" }
end
end
def json_xml_or_html
respond_to do |type|
type.json { render :text => 'JSON' }
type.json { render body: 'JSON' }
type.xml { render :xml => 'XML' }
type.html { render :text => 'HTML' }
type.html { render body: 'HTML' }
end
end
@ -56,14 +56,14 @@ def forced_xml
request.format = :xml
respond_to do |type|
type.html { render :text => "HTML" }
type.xml { render :text => "XML" }
type.html { render body: "HTML" }
type.xml { render body: "XML" }
end
end
def just_xml
respond_to do |type|
type.xml { render :text => "XML" }
type.xml { render body: "XML" }
end
end
@ -81,52 +81,52 @@ def using_defaults_with_type_list
def using_defaults_with_all
respond_to do |type|
type.html
type.all{ render text: "ALL" }
type.all{ render body: "ALL" }
end
end
def made_for_content_type
respond_to do |type|
type.rss { render :text => "RSS" }
type.atom { render :text => "ATOM" }
type.all { render :text => "Nothing" }
type.rss { render body: "RSS" }
type.atom { render body: "ATOM" }
type.all { render body: "Nothing" }
end
end
def custom_type_handling
respond_to do |type|
type.html { render :text => "HTML" }
type.custom("application/crazy-xml") { render :text => "Crazy XML" }
type.all { render :text => "Nothing" }
type.html { render body: "HTML" }
type.custom("application/crazy-xml") { render body: "Crazy XML" }
type.all { render body: "Nothing" }
end
end
def custom_constant_handling
respond_to do |type|
type.html { render :text => "HTML" }
type.mobile { render :text => "Mobile" }
type.html { render body: "HTML" }
type.mobile { render body: "Mobile" }
end
end
def custom_constant_handling_without_block
respond_to do |type|
type.html { render :text => "HTML" }
type.html { render body: "HTML" }
type.mobile
end
end
def handle_any
respond_to do |type|
type.html { render :text => "HTML" }
type.any(:js, :xml) { render :text => "Either JS or XML" }
type.html { render body: "HTML" }
type.any(:js, :xml) { render body: "Either JS or XML" }
end
end
def handle_any_any
respond_to do |type|
type.html { render :text => 'HTML' }
type.any { render :text => 'Whatever you ask for, I got it' }
type.html { render body: 'HTML' }
type.any { render body: 'Whatever you ask for, I got it' }
end
end
@ -167,15 +167,15 @@ def variant_with_format_and_custom_render
request.variant = :mobile
respond_to do |type|
type.html { render text: "mobile" }
type.html { render body: "mobile" }
end
end
def multiple_variants_for_format
respond_to do |type|
type.html do |html|
html.tablet { render text: "tablet" }
html.phone { render text: "phone" }
html.tablet { render body: "tablet" }
html.phone { render body: "phone" }
end
end
end
@ -183,7 +183,7 @@ def multiple_variants_for_format
def variant_plus_none_for_format
respond_to do |format|
format.html do |variant|
variant.phone { render text: "phone" }
variant.phone { render body: "phone" }
variant.none
end
end
@ -191,9 +191,9 @@ def variant_plus_none_for_format
def variant_inline_syntax
respond_to do |format|
format.js { render text: "js" }
format.html.none { render text: "none" }
format.html.phone { render text: "phone" }
format.js { render body: "js" }
format.html.none { render body: "none" }
format.html.phone { render body: "phone" }
end
end
@ -208,8 +208,8 @@ def variant_inline_syntax_without_block
def variant_any
respond_to do |format|
format.html do |variant|
variant.any(:tablet, :phablet){ render text: "any" }
variant.phone { render text: "phone" }
variant.any(:tablet, :phablet){ render body: "any" }
variant.phone { render body: "phone" }
end
end
end
@ -217,23 +217,23 @@ def variant_any
def variant_any_any
respond_to do |format|
format.html do |variant|
variant.any { render text: "any" }
variant.phone { render text: "phone" }
variant.any { render body: "any" }
variant.phone { render body: "phone" }
end
end
end
def variant_inline_any
respond_to do |format|
format.html.any(:tablet, :phablet){ render text: "any" }
format.html.phone { render text: "phone" }
format.html.any(:tablet, :phablet){ render body: "any" }
format.html.phone { render body: "phone" }
end
end
def variant_inline_any_any
respond_to do |format|
format.html.phone { render text: "phone" }
format.html.any { render text: "any" }
format.html.phone { render body: "phone" }
format.html.any { render body: "any" }
end
end
@ -246,16 +246,16 @@ def variant_any_implicit_render
def variant_any_with_none
respond_to do |format|
format.html.any(:none, :phone){ render text: "none or phone" }
format.html.any(:none, :phone){ render body: "none or phone" }
end
end
def format_any_variant_any
respond_to do |format|
format.html { render text: "HTML" }
format.html { render body: "HTML" }
format.any(:js, :xml) do |variant|
variant.phone{ render text: "phone" }
variant.any(:tablet, :phablet){ render text: "tablet" }
variant.phone{ render body: "phone" }
variant.any(:tablet, :phablet){ render body: "tablet" }
end
end
end
@ -780,7 +780,7 @@ def test_variant_negotiation_without_block
class RespondToWithBlockOnDefaultRenderController < ActionController::Base
def show
default_render do
render text: 'default_render yielded'
render body: 'default_render yielded'
end
end
end
@ -794,6 +794,6 @@ def setup
def test_default_render_uses_block_when_no_template_exists
get :show
assert_equal "default_render yielded", @response.body
assert_equal "text/html", @response.content_type
assert_equal "text/plain", @response.content_type
end
end

@ -6,7 +6,7 @@ class SimpleController < ActionController::Base
before_action :authenticate
def index
render :text => "success"
render body: "success"
end
def modify_response_body
@ -22,7 +22,7 @@ def modify_response_headers
end
def show_actions
render :text => "actions: #{action_methods.to_a.sort.join(', ')}"
render body: "actions: #{action_methods.to_a.sort.join(', ')}"
end
protected
@ -51,7 +51,7 @@ class BaseTest < Rack::TestCase
assert_body "success"
assert_status 200
assert_content_type "text/html; charset=utf-8"
assert_content_type "text/plain; charset=utf-8"
end
# :api: plugin

@ -9,7 +9,7 @@ class BasicController < ActionController::Base
)]
def all
render :text => self.formats.inspect
render plain: self.formats.inspect
end
end

@ -3,16 +3,16 @@
module ContentType
class BaseController < ActionController::Base
def index
render :text => "Hello world!"
render body: "Hello world!"
end
def set_on_response_obj
response.content_type = Mime::RSS
render :text => "Hello world!"
render body: "Hello world!"
end
def set_on_render
render :text => "Hello world!", :content_type => Mime::RSS
render body: "Hello world!", content_type: Mime::RSS
end
end
@ -30,17 +30,17 @@ class ImpliedController < ActionController::Base
class CharsetController < ActionController::Base
def set_on_response_obj
response.charset = "utf-16"
render :text => "Hello world!"
render body: "Hello world!"
end
def set_as_nil_on_response_obj
response.charset = nil
render :text => "Hello world!"
render body: "Hello world!"
end
end
class ExplicitContentTypeTest < Rack::TestCase
test "default response is HTML and UTF8" do
test "default response is text/plain and UTF8" do
with_routing do |set|
set.draw do
get ':controller', :action => 'index'
@ -49,7 +49,7 @@ class ExplicitContentTypeTest < Rack::TestCase
get "/content_type/base"
assert_body "Hello world!"
assert_header "Content-Type", "text/html; charset=utf-8"
assert_header "Content-Type", "text/plain; charset=utf-8"
end
end
@ -99,14 +99,14 @@ class ExplicitCharsetTest < Rack::TestCase
get "/content_type/charset/set_on_response_obj"
assert_body "Hello world!"
assert_header "Content-Type", "text/html; charset=utf-16"
assert_header "Content-Type", "text/plain; charset=utf-16"
end
test "setting the charset of the response as nil directly on the response object" do
get "/content_type/charset/set_as_nil_on_response_obj"
assert_body "Hello world!"
assert_header "Content-Type", "text/html; charset=utf-8"
assert_header "Content-Type", "text/plain; charset=utf-8"
end
end
end

Some files were not shown because too many files have changed in this diff Show More