Merge branch 'master' into laurocaetano-fix_send_file
* master: (536 commits)
doc, API example on how to use `Model#exists?` with multiple IDs. [ci skip]
Restore DATABASE_URL even if it's nil in connection_handler test
[ci skip] - error_messages_for has been deprecated since 2.3.8 - lets reduce any confusion for users
Ensure Active Record connection consistency
Revert "ask the fixture set for the sql statements"
Check `respond_to` before delegation due to: d781caaf31
Adding Hash#compact and Hash#compact! methods
MySQL version 4.1 was EOL on December 31, 2009 We should at least recommend modern versions of MySQL to users.
clear cache on body close so that cache remains during rendering
add a more restricted codepath for templates fixes #13390
refactor generator tests to use block form of Tempfile
Fix typo [ci skip]
Move finish_template as the last public method in the generator
Minor typos fix [ci skip]
make `change_column_null` reversible. Closes #13576.
create/drop test and development databases only if RAILS_ENV is nil
Revert "Speedup String#to"
typo fix in test name. [ci skip].
`core_ext/string/access.rb` test what we are documenting.
Fix typo in image_tag documentation
...
Conflicts:
actionpack/CHANGELOG.md
This commit is contained in:
commit
caa981d881
13
.travis.yml
13
.travis.yml
@ -5,8 +5,9 @@ before_install:
|
||||
rvm:
|
||||
- 1.9.3
|
||||
- 2.0.0
|
||||
- rbx-2.2.1
|
||||
- jruby-19mode
|
||||
- 2.1.0
|
||||
- rbx
|
||||
- jruby
|
||||
env:
|
||||
- "GEM=railties"
|
||||
- "GEM=ap,am,amo,as,av"
|
||||
@ -16,8 +17,9 @@ env:
|
||||
- "GEM=ar:postgresql"
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rvm: rbx-2.2.1
|
||||
- rvm: jruby-19mode
|
||||
- rvm: rbx
|
||||
- rvm: jruby
|
||||
fast_finish: true
|
||||
notifications:
|
||||
email: false
|
||||
irc:
|
||||
@ -31,3 +33,6 @@ notifications:
|
||||
rooms:
|
||||
- secure: "YA1alef1ESHWGFNVwvmVGCkMe4cUy4j+UcNvMUESraceiAfVyRMAovlQBGs6\n9kBRm7DHYBUXYC2ABQoJbQRLDr/1B5JPf/M8+Qd7BKu8tcDC03U01SMHFLpO\naOs/HLXcDxtnnpL07tGVsm0zhMc5N8tq4/L3SHxK7Vi+TacwQzI="
|
||||
bundler_args: --path vendor/bundle --without test
|
||||
services:
|
||||
- memcached
|
||||
|
||||
|
7
Gemfile
7
Gemfile
@ -12,7 +12,6 @@ gem 'bcrypt-ruby', '~> 3.1.2'
|
||||
gem 'jquery-rails', '~> 2.2.0'
|
||||
gem 'turbolinks'
|
||||
gem 'coffee-rails', '~> 4.0.0'
|
||||
gem 'arel', github: 'rails/arel', branch: 'master'
|
||||
|
||||
# This needs to be with require false to avoid
|
||||
# it being automatically loaded by sprockets
|
||||
@ -37,9 +36,9 @@ group :test do
|
||||
gem 'ruby-prof', '~> 0.11.2'
|
||||
end
|
||||
|
||||
platforms :mri_19, :mri_20 do
|
||||
gem 'debugger'
|
||||
end
|
||||
# platforms :mri_19, :mri_20 do
|
||||
# gem 'debugger'
|
||||
# end
|
||||
|
||||
gem 'benchmark-ips'
|
||||
end
|
||||
|
@ -1 +1 @@
|
||||
4.1.0.beta
|
||||
4.1.0.beta1
|
||||
|
@ -110,7 +110,7 @@ what to do in case anything goes wrong:
|
||||
|
||||
$ rake all:build
|
||||
$ git commit -am'updating RAILS_VERSION'
|
||||
$ git tag -m'tagging rc release' v3.0.10.rc1
|
||||
$ 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
|
||||
|
@ -1,3 +1,28 @@
|
||||
* Add mailer previews feature based on 37 Signals mail_view gem
|
||||
|
||||
*Andrew White*
|
||||
|
||||
* Calling `mail()` without arguments serves as getter for the current mail
|
||||
message and keeps previously set headers.
|
||||
|
||||
Fixes #13090.
|
||||
|
||||
Example:
|
||||
|
||||
class MailerWithCallback < ActionMailer::Base
|
||||
after_action :a_callback
|
||||
|
||||
def welcome
|
||||
mail subject: "subject", to: ["joe@example.com"]
|
||||
end
|
||||
|
||||
def a_callback
|
||||
mail # => returns the current mail message
|
||||
end
|
||||
end
|
||||
|
||||
*Yves Senn*
|
||||
|
||||
* Instrument the generation of Action Mailer messages. The time it takes to
|
||||
generate a message is written to the log.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2004-2013 David Heinemeier Hansson
|
||||
Copyright (c) 2004-2014 David Heinemeier Hansson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
@ -1,5 +1,5 @@
|
||||
#--
|
||||
# Copyright (c) 2004-2013 David Heinemeier Hansson
|
||||
# Copyright (c) 2004-2014 David Heinemeier Hansson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
@ -41,6 +41,8 @@ module ActionMailer
|
||||
autoload :Base
|
||||
autoload :DeliveryMethods
|
||||
autoload :MailHelper
|
||||
autoload :Preview
|
||||
autoload :Previews, 'action_mailer/preview'
|
||||
autoload :TestCase
|
||||
autoload :TestHelper
|
||||
end
|
||||
|
@ -50,7 +50,7 @@ module ActionMailer
|
||||
#
|
||||
# * <tt>mail</tt> - Allows you to specify email to be sent.
|
||||
#
|
||||
# The hash passed to the mail method allows you to specify any header that a Mail::Message
|
||||
# The hash passed to the mail method allows you to specify any header that a <tt>Mail::Message</tt>
|
||||
# will accept (any valid Email header including optional fields).
|
||||
#
|
||||
# The mail method, if not passed a block, will inspect your views and send all the views with
|
||||
@ -229,7 +229,7 @@ module ActionMailer
|
||||
# An interceptor class must implement the <tt>:delivering_email(message)</tt> method which will be
|
||||
# called before the email is sent, allowing you to make modifications to the email before it hits
|
||||
# the delivery agents. Your class should make any needed modifications directly to the passed
|
||||
# in Mail::Message instance.
|
||||
# in <tt>Mail::Message</tt> instance.
|
||||
#
|
||||
# = Default Hash
|
||||
#
|
||||
@ -308,6 +308,28 @@ module ActionMailer
|
||||
# Note that unless you have a specific reason to do so, you should prefer using before_action
|
||||
# rather than after_action in your ActionMailer classes so that headers are parsed properly.
|
||||
#
|
||||
# = Previewing emails
|
||||
#
|
||||
# You can preview your email templates visually by adding a mailer preview file to the
|
||||
# <tt>ActionMailer::Base.preview_path</tt>. Since most emails do something interesting
|
||||
# with database data, you'll need to write some scenarios to load messages with fake data:
|
||||
#
|
||||
# class NotifierPreview < ActionMailer::Preview
|
||||
# def welcome
|
||||
# Notifier.welcome(User.first)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Methods must return a <tt>Mail::Message</tt> object which can be generated by calling the mailer
|
||||
# method without the additional <tt>deliver</tt>. The location of the mailer previews
|
||||
# directory can be configured using the <tt>preview_path</tt> option which has a default
|
||||
# of <tt>test/mailers/previews</tt>:
|
||||
#
|
||||
# config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
|
||||
#
|
||||
# An overview of all previews is accessible at <tt>http://localhost:3000/rails/mailers</tt>
|
||||
# on a running development server instance.
|
||||
#
|
||||
# = Configuration options
|
||||
#
|
||||
# These options are specified on the class level, like
|
||||
@ -317,7 +339,7 @@ module ActionMailer
|
||||
# per the above section.
|
||||
#
|
||||
# * <tt>logger</tt> - the logger is used for generating information on the mailing run if available.
|
||||
# Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
|
||||
# Can be set to +nil+ for no logging. Compatible with both Ruby's own +Logger+ and Log4r loggers.
|
||||
#
|
||||
# * <tt>smtp_settings</tt> - Allows detailed configuration for <tt>:smtp</tt> delivery method:
|
||||
# * <tt>:address</tt> - Allows you to use a remote mail server. Just change it from its default
|
||||
@ -335,8 +357,9 @@ module ActionMailer
|
||||
# and starts to use it.
|
||||
# * <tt>:openssl_verify_mode</tt> - When using TLS, you can set how OpenSSL checks the certificate. This is
|
||||
# really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name
|
||||
# of an OpenSSL verify constant ('none', 'peer', 'client_once', 'fail_if_no_peer_cert') or directly the
|
||||
# constant (OpenSSL::SSL::VERIFY_NONE, OpenSSL::SSL::VERIFY_PEER, ...).
|
||||
# of an OpenSSL verify constant (<tt>'none'</tt>, <tt>'peer'</tt>, <tt>'client_once'</tt>,
|
||||
# <tt>'fail_if_no_peer_cert'</tt>) or directly the constant (<tt>OpenSSL::SSL::VERIFY_NONE</tt>,
|
||||
# <tt>OpenSSL::SSL::VERIFY_PEER</tt>, ...).
|
||||
#
|
||||
# * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
|
||||
# * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>.
|
||||
@ -351,7 +374,7 @@ module ActionMailer
|
||||
#
|
||||
# * <tt>delivery_method</tt> - Defines a delivery method. Possible values are <tt>:smtp</tt> (default),
|
||||
# <tt>:sendmail</tt>, <tt>:test</tt>, and <tt>:file</tt>. Or you may provide a custom delivery method
|
||||
# object e.g. MyOwnDeliveryMethodClass. See the Mail gem documentation on the interface you need to
|
||||
# object e.g. +MyOwnDeliveryMethodClass+. See the Mail gem documentation on the interface you need to
|
||||
# implement for a custom delivery agent.
|
||||
#
|
||||
# * <tt>perform_deliveries</tt> - Determines whether emails are actually sent from Action Mailer when you
|
||||
@ -362,6 +385,7 @@ module ActionMailer
|
||||
# <tt>delivery_method :test</tt>. Most useful for unit and functional testing.
|
||||
class Base < AbstractController::Base
|
||||
include DeliveryMethods
|
||||
include Previews
|
||||
|
||||
abstract!
|
||||
|
||||
@ -373,6 +397,8 @@ class Base < AbstractController::Base
|
||||
include AbstractController::AssetPaths
|
||||
include AbstractController::Callbacks
|
||||
|
||||
include ActionView::Layouts
|
||||
|
||||
PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [:@_action_has_layout]
|
||||
|
||||
def _protected_ivars # :nodoc:
|
||||
@ -404,7 +430,7 @@ def register_interceptors(*interceptors)
|
||||
|
||||
# Register an Observer which will be notified when mail is delivered.
|
||||
# Either a class or a string can be passed in as the Observer. If a string is passed in
|
||||
# it will be +constantize+d.
|
||||
# it will be <tt>constantize</tt>d.
|
||||
def register_observer(observer)
|
||||
delivery_observer = (observer.is_a?(String) ? observer.constantize : observer)
|
||||
Mail.register_observer(delivery_observer)
|
||||
@ -462,11 +488,11 @@ def receive(raw_mail)
|
||||
end
|
||||
end
|
||||
|
||||
# Wraps an email delivery inside of ActiveSupport::Notifications instrumentation.
|
||||
# Wraps an email delivery inside of <tt>ActiveSupport::Notifications</tt> instrumentation.
|
||||
#
|
||||
# This method is actually called by the Mail::Message object itself
|
||||
# through a callback when you call +:deliver+ on the Mail::Message,
|
||||
# calling +deliver_mail+ directly and passing a Mail::Message will do
|
||||
# This method is actually called by the <tt>Mail::Message</tt> object itself
|
||||
# through a callback when you call <tt>:deliver</tt> on the <tt>Mail::Message</tt>,
|
||||
# calling +deliver_mail+ directly and passing a <tt>Mail::Message</tt> will do
|
||||
# nothing except tell the logger you sent the email.
|
||||
def deliver_mail(mail) #:nodoc:
|
||||
ActiveSupport::Notifications.instrument("deliver.action_mailer") do |payload|
|
||||
@ -542,18 +568,18 @@ def mailer_name
|
||||
self.class.mailer_name
|
||||
end
|
||||
|
||||
# Allows you to pass random and unusual headers to the new Mail::Message
|
||||
# Allows you to pass random and unusual headers to the new <tt>Mail::Message</tt>
|
||||
# object which will add them to itself.
|
||||
#
|
||||
# headers['X-Special-Domain-Specific-Header'] = "SecretValue"
|
||||
#
|
||||
# You can also pass a hash into headers of header field names and values,
|
||||
# which will then be set on the Mail::Message object:
|
||||
# which will then be set on the <tt>Mail::Message</tt> object:
|
||||
#
|
||||
# headers 'X-Special-Domain-Specific-Header' => "SecretValue",
|
||||
# 'In-Reply-To' => incoming.message_id
|
||||
#
|
||||
# The resulting Mail::Message will have the following in its header:
|
||||
# The resulting <tt>Mail::Message</tt> will have the following in its header:
|
||||
#
|
||||
# X-Special-Domain-Specific-Header: SecretValue
|
||||
def headers(args = nil)
|
||||
@ -642,8 +668,8 @@ def attachments
|
||||
# templates in the view paths using by default the mailer name and the
|
||||
# method name that it is being called from, it will then create parts for
|
||||
# each of these templates intelligently, making educated guesses on correct
|
||||
# content type and sequence, and return a fully prepared Mail::Message
|
||||
# ready to call +:deliver+ on to send.
|
||||
# content type and sequence, and return a fully prepared <tt>Mail::Message</tt>
|
||||
# ready to call <tt>:deliver</tt> on to send.
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
@ -689,6 +715,8 @@ def attachments
|
||||
# end
|
||||
#
|
||||
def mail(headers = {}, &block)
|
||||
return @_message if @_mail_was_called && headers.blank? && !block
|
||||
|
||||
@_mail_was_called = true
|
||||
m = @_message
|
||||
|
||||
|
73
actionmailer/lib/action_mailer/preview.rb
Normal file
73
actionmailer/lib/action_mailer/preview.rb
Normal file
@ -0,0 +1,73 @@
|
||||
require 'active_support/descendants_tracker'
|
||||
|
||||
module ActionMailer
|
||||
module Previews #:nodoc:
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# Set the location of mailer previews through app configuration:
|
||||
#
|
||||
# config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
|
||||
#
|
||||
class_attribute :preview_path, instance_writer: false
|
||||
end
|
||||
end
|
||||
|
||||
class Preview
|
||||
extend ActiveSupport::DescendantsTracker
|
||||
|
||||
class << self
|
||||
# Returns all mailer preview classes
|
||||
def all
|
||||
load_previews if descendants.empty?
|
||||
descendants
|
||||
end
|
||||
|
||||
# Returns the mail object for the given email name
|
||||
def call(email)
|
||||
preview = self.new
|
||||
preview.public_send(email)
|
||||
end
|
||||
|
||||
# Returns all of the available email previews
|
||||
def emails
|
||||
public_instance_methods(false).map(&:to_s).sort
|
||||
end
|
||||
|
||||
# Returns true if the email exists
|
||||
def email_exists?(email)
|
||||
emails.include?(email)
|
||||
end
|
||||
|
||||
# Returns true if the preview exists
|
||||
def exists?(preview)
|
||||
all.any?{ |p| p.preview_name == preview }
|
||||
end
|
||||
|
||||
# Find a mailer preview by its underscored class name
|
||||
def find(preview)
|
||||
all.find{ |p| p.preview_name == preview }
|
||||
end
|
||||
|
||||
# Returns the underscored name of the mailer preview without the suffix
|
||||
def preview_name
|
||||
name.sub(/Preview$/, '').underscore
|
||||
end
|
||||
|
||||
protected
|
||||
def load_previews #:nodoc:
|
||||
if preview_path?
|
||||
Dir["#{preview_path}/**/*_preview.rb"].each{ |file| require_dependency file }
|
||||
end
|
||||
end
|
||||
|
||||
def preview_path #:nodoc:
|
||||
Base.preview_path
|
||||
end
|
||||
|
||||
def preview_path? #:nodoc:
|
||||
Base.preview_path?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -19,6 +19,10 @@ class Railtie < Rails::Railtie # :nodoc:
|
||||
options.javascripts_dir ||= paths["public/javascripts"].first
|
||||
options.stylesheets_dir ||= paths["public/stylesheets"].first
|
||||
|
||||
if Rails.env.development?
|
||||
options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/mailers/previews" : nil
|
||||
end
|
||||
|
||||
# make sure readers methods get compiled
|
||||
options.asset_host ||= app.config.asset_host
|
||||
options.relative_url_root ||= app.config.relative_url_root
|
||||
@ -40,5 +44,11 @@ class Railtie < Rails::Railtie # :nodoc:
|
||||
config.compile_methods! if config.respond_to?(:compile_methods!)
|
||||
end
|
||||
end
|
||||
|
||||
config.after_initialize do
|
||||
if ActionMailer::Base.preview_path?
|
||||
ActiveSupport::Dependencies.autoload_paths << ActionMailer::Base.preview_path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,7 +1,7 @@
|
||||
module ActionMailer
|
||||
# Returns the version of the currently loaded ActionMailer as a Gem::Version
|
||||
def self.version
|
||||
Gem::Version.new "4.1.0.beta"
|
||||
Gem::Version.new "4.1.0.beta1"
|
||||
end
|
||||
|
||||
module VERSION #:nodoc:
|
||||
|
@ -20,6 +20,9 @@
|
||||
# Show backtraces for deprecated behavior for quicker cleanup.
|
||||
ActiveSupport::Deprecation.debug = true
|
||||
|
||||
# Disable available locale checks to avoid warnings running the test suite.
|
||||
I18n.enforce_available_locales = false
|
||||
|
||||
# Bogus template processors
|
||||
ActionView::Template.register_template_handler :haml, lambda { |template| "Look its HAML!".inspect }
|
||||
ActionView::Template.register_template_handler :bak, lambda { |template| "Lame backup".inspect }
|
||||
|
@ -671,6 +671,27 @@ def welcome
|
||||
assert_equal ["robert.pankowecki@gmail.com"], DefaultFromMailer.welcome.from
|
||||
end
|
||||
|
||||
test "mail() without arguments serves as getter for the current mail message" do
|
||||
class MailerWithCallback < ActionMailer::Base
|
||||
after_action :a_callback
|
||||
|
||||
def welcome
|
||||
headers('X-Special-Header' => 'special indeed!')
|
||||
mail subject: "subject", body: "hello world", to: ["joe@example.com"]
|
||||
end
|
||||
|
||||
def a_callback
|
||||
mail.to << "jane@example.com"
|
||||
end
|
||||
end
|
||||
|
||||
mail = MailerWithCallback.welcome
|
||||
assert_equal "subject", mail.subject
|
||||
assert_equal ["joe@example.com", "jane@example.com"], mail.to
|
||||
assert_equal "hello world", mail.body.encoded.strip
|
||||
assert_equal "special indeed!", mail["X-Special-Header"].to_s
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Execute the block setting the given values and restoring old values after
|
||||
|
@ -15,9 +15,6 @@ def mail_with_i18n_subject(recipient)
|
||||
end
|
||||
end
|
||||
|
||||
# Emulate AV railtie
|
||||
ActionController::Base.superclass.send(:include, ActionView::Layouts)
|
||||
|
||||
class TestController < ActionController::Base
|
||||
def send_mail
|
||||
I18nTestMailer.mail_with_i18n_subject("test@localhost").deliver
|
||||
|
@ -4,6 +4,199 @@
|
||||
|
||||
*Alessandro Diaferia*
|
||||
|
||||
* Allow an absolute controller path inside a module scope. Fixes #12777.
|
||||
|
||||
Example:
|
||||
|
||||
namespace :foo do
|
||||
# will route to BarController without the namespace.
|
||||
get '/special', to: '/bar#index'
|
||||
end
|
||||
|
||||
|
||||
* Unique the segment keys array for non-optimized url helpers
|
||||
|
||||
In Rails 3.2 you only needed pass an argument for dynamic segment once so
|
||||
unique the segment keys array to match the number of args. Since the number
|
||||
of args is less than required parts the non-optimized code path is selected.
|
||||
This means to benefit from optimized url generation the arg needs to be
|
||||
specified as many times as it appears in the path.
|
||||
|
||||
Fixes #12808.
|
||||
|
||||
*Andrew White*
|
||||
|
||||
* Show full route constraints in error message
|
||||
|
||||
When an optimized helper fails to generate, show the full route constraints
|
||||
in the error message. Previously it would only show the contraints that were
|
||||
required as part of the path.
|
||||
|
||||
Fixes #13592.
|
||||
|
||||
*Andrew White*
|
||||
|
||||
* Use a custom route visitor for optimized url generation. Fixes #13349.
|
||||
|
||||
*Andrew White*
|
||||
|
||||
* Allow engine root relative redirects using an empty string.
|
||||
|
||||
Example:
|
||||
|
||||
# application routes.rb
|
||||
mount BlogEngine => '/blog'
|
||||
|
||||
# engine routes.rb
|
||||
get '/welcome' => redirect('')
|
||||
|
||||
This now redirects to the path `/blog`, whereas before it would redirect
|
||||
to the application root path. In the case of a path redirect or a custom
|
||||
redirect if the path returned contains a host then the path is treated as
|
||||
absolute. Similarly for option redirects, if the options hash returned
|
||||
contains a `:host` or `:domain` key then the path is treated as absolute.
|
||||
|
||||
Fixes #7977.
|
||||
|
||||
*Andrew White*
|
||||
|
||||
* Fix `Encoding::CompatibilityError` when public path is UTF-8
|
||||
|
||||
In #5337 we forced the path encoding to ASCII-8BIT to prevent static file handling
|
||||
from blowing up before an application has had chance to deal with possibly invalid
|
||||
urls. However this has a negative side effect of making it an incompatible encoding
|
||||
if the application's public path has UTF-8 characters in it.
|
||||
|
||||
To work around the problem we check to see if the path has a valid encoding once
|
||||
it has been unescaped. If it is not valid then we can return early since it will
|
||||
not match any file anyway.
|
||||
|
||||
Fixes #13518.
|
||||
|
||||
*Andrew White*
|
||||
|
||||
* `ActionController::Parameters#permit!` permits hashes in array values.
|
||||
|
||||
*Xavier Noria*
|
||||
|
||||
* Converts hashes in arrays of unfiltered params to unpermitted params.
|
||||
|
||||
Fixes #13382.
|
||||
|
||||
*Xavier Noria*
|
||||
|
||||
* New config option to opt out of params "deep munging" that was used to
|
||||
address security vulnerability CVE-2013-0155. In your app config:
|
||||
|
||||
config.action_dispatch.perform_deep_munge = false
|
||||
|
||||
Take care to understand the security risk involved before disabling this.
|
||||
[Read more.](https://groups.google.com/forum/#!topic/rubyonrails-security/t1WFuuQyavI)
|
||||
|
||||
*Bernard Potocki*
|
||||
|
||||
* `rake routes` shows routes defined under assets prefix.
|
||||
|
||||
*Ryunosuke SATO*
|
||||
|
||||
* Extend cross-site request forgery (CSRF) protection to GET requests with
|
||||
JavaScript responses, protecting apps from cross-origin `<script>` tags.
|
||||
|
||||
*Jeremy Kemper*
|
||||
|
||||
* Fix generating a path for engine inside a resources block.
|
||||
|
||||
Fixes #8533.
|
||||
|
||||
*Piotr Sarnacki*
|
||||
|
||||
* Add `Mime::Type.register "text/vcard", :vcf` to the default list of mime types.
|
||||
|
||||
*DHH*
|
||||
|
||||
* Remove deprecated `ActionController::RecordIdentifier`, use
|
||||
`ActionView::RecordIdentifier` instead.
|
||||
|
||||
*kennyj*
|
||||
|
||||
* Fix regression when using `ActionView::Helpers::TranslationHelper#translate` with
|
||||
`options[:raise]`.
|
||||
|
||||
This regression was introduced at ec16ba75a5493b9da972eea08bae630eba35b62f.
|
||||
|
||||
*Shota Fukumori (sora_h)*
|
||||
|
||||
* Introducing Variants
|
||||
|
||||
We often want to render different html/json/xml templates for phones,
|
||||
tablets, and desktop browsers. Variants make it easy.
|
||||
|
||||
The request variant is a specialization of the request format, like `:tablet`,
|
||||
`:phone`, or `:desktop`.
|
||||
|
||||
You can set the variant in a `before_action`:
|
||||
|
||||
request.variant = :tablet if request.user_agent =~ /iPad/
|
||||
|
||||
Respond to variants in the action just like you respond to formats:
|
||||
|
||||
respond_to do |format|
|
||||
format.html do |html|
|
||||
html.tablet # renders app/views/projects/show.html+tablet.erb
|
||||
html.phone { extra_setup; render ... }
|
||||
end
|
||||
end
|
||||
|
||||
Provide separate templates for each format and variant:
|
||||
|
||||
app/views/projects/show.html.erb
|
||||
app/views/projects/show.html+tablet.erb
|
||||
app/views/projects/show.html+phone.erb
|
||||
|
||||
You can also simplify the variants definition using the inline syntax:
|
||||
|
||||
respond_to do |format|
|
||||
format.js { render "trash" }
|
||||
format.html.phone { redirect_to progress_path }
|
||||
format.html.none { render "trash" }
|
||||
end
|
||||
|
||||
Variants also support common `any`/`all` block that formats have.
|
||||
|
||||
It works for both inline:
|
||||
|
||||
respond_to do |format|
|
||||
format.html.any { render text: "any" }
|
||||
format.html.phone { render text: "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" }
|
||||
end
|
||||
end
|
||||
|
||||
*Łukasz Strzałkowski*
|
||||
|
||||
* Fix render of localized templates without an explicit format using wrong
|
||||
content header and not passing correct formats to template due to the
|
||||
introduction of the `NullType` for mimes.
|
||||
|
||||
Templates like `hello.it.erb` were subject to this issue.
|
||||
|
||||
Fixes #13064.
|
||||
|
||||
*Angelo Capilleri*, *Carlos Antonio da Silva*
|
||||
|
||||
* Try to escape each part of a url correctly when using a redirect route.
|
||||
|
||||
Fixes #13110.
|
||||
|
||||
*Andrew White*
|
||||
|
||||
* Better error message for typos in assert_response argument.
|
||||
|
||||
When the response type argument to `assert_response` is not a known
|
||||
@ -27,9 +220,7 @@
|
||||
|
||||
* Add `session#fetch` method
|
||||
|
||||
fetch behaves similarly to [Hash#fetch](http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-fetch),
|
||||
with the exception that the returned value is always saved into the session.
|
||||
|
||||
fetch behaves like [Hash#fetch](http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-fetch).
|
||||
It returns a value from the hash for the given key.
|
||||
If the key can’t be found, there are several options:
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2004-2013 David Heinemeier Hansson
|
||||
Copyright (c) 2004-2014 David Heinemeier Hansson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
s.add_dependency 'rack', '~> 1.5.2'
|
||||
s.add_dependency 'rack-test', '~> 0.6.2'
|
||||
s.add_dependency 'actionview', version
|
||||
|
||||
s.add_development_dependency 'actionview', version
|
||||
s.add_development_dependency 'activemodel', version
|
||||
end
|
||||
|
@ -23,7 +23,17 @@ def #{sym}(*args, &block) # def html(*args, &block)
|
||||
protected
|
||||
|
||||
def method_missing(symbol, &block)
|
||||
mime_constant = Mime.const_get(symbol.upcase)
|
||||
const_name = symbol.upcase
|
||||
|
||||
unless Mime.const_defined?(const_name)
|
||||
raise NoMethodError, "To respond to a custom format, register it as a MIME type first: " \
|
||||
"http://guides.rubyonrails.org/action_controller_overview.html#restful-downloads. " \
|
||||
"If you meant to respond to a variant like :tablet or :phone, not a custom format, " \
|
||||
"be sure to nest your variant response within a format response: " \
|
||||
"format.html { |html| html.tablet { ... } }"
|
||||
end
|
||||
|
||||
mime_constant = Mime.const_get(const_name)
|
||||
|
||||
if Mime::SET.include?(mime_constant)
|
||||
AbstractController::Collector.generate_method_for_mime(mime_constant)
|
||||
|
@ -1,5 +1,6 @@
|
||||
require 'active_support/concern'
|
||||
require 'active_support/core_ext/class/attribute'
|
||||
require 'action_view/view_paths'
|
||||
require 'set'
|
||||
|
||||
module AbstractController
|
||||
@ -13,6 +14,7 @@ def initialize(message = nil)
|
||||
|
||||
module Rendering
|
||||
extend ActiveSupport::Concern
|
||||
include ActionView::ViewPaths
|
||||
|
||||
# Normalize arguments, options and then delegates render_to_body and
|
||||
# sticks the result in self.response_body.
|
||||
@ -20,7 +22,7 @@ module Rendering
|
||||
def render(*args, &block)
|
||||
options = _normalize_render(*args, &block)
|
||||
self.response_body = render_to_body(options)
|
||||
_process_format(rendered_format)
|
||||
_process_format(rendered_format) if rendered_format
|
||||
self.response_body
|
||||
end
|
||||
|
||||
@ -45,7 +47,7 @@ def render_to_string(*args, &block)
|
||||
def render_to_body(options = {})
|
||||
end
|
||||
|
||||
# Return Content-Type of rendered content
|
||||
# Returns Content-Type of rendered content
|
||||
# :api: public
|
||||
def rendered_format
|
||||
Mime::TEXT
|
||||
@ -102,6 +104,8 @@ def _process_format(format)
|
||||
# :api: private
|
||||
def _normalize_render(*args, &block)
|
||||
options = _normalize_args(*args, &block)
|
||||
#TODO: remove defined? when we restore AP <=> AV dependency
|
||||
options[:variant] = request.variant if defined?(request) && request.variant.present?
|
||||
_normalize_options(options)
|
||||
options
|
||||
end
|
||||
|
@ -50,7 +50,7 @@ def self.eager_load!
|
||||
end
|
||||
|
||||
# Common Active Support usage in Action Controller
|
||||
require 'active_support/core_ext/class/attribute_accessors'
|
||||
require 'active_support/core_ext/module/attribute_accessors'
|
||||
require 'active_support/core_ext/load_error'
|
||||
require 'active_support/core_ext/module/attr_internal'
|
||||
require 'active_support/core_ext/name_error'
|
||||
|
@ -1,21 +1,8 @@
|
||||
require 'action_view'
|
||||
require "action_controller/log_subscriber"
|
||||
require "action_controller/metal/params_wrapper"
|
||||
|
||||
module ActionController
|
||||
# The <tt>metal</tt> anonymous class was introduced to solve issue with including modules in <tt>ActionController::Base</tt>.
|
||||
# Modules needs to be included in particluar order. First we need to have <tt>AbstractController::Rendering</tt> included,
|
||||
# next we should include actuall implementation which would be for example <tt>ActionView::Rendering</tt> and after that
|
||||
# <tt>ActionController::Rendering</tt>. This order must be preserved and as we want to have middle module included dynamicaly
|
||||
# <tt>metal</tt> class was introduced. It has <tt>AbstractController::Rendering</tt> included and is parent class of
|
||||
# <tt>ActionController::Base</tt> which includes <tt>ActionController::Rendering</tt>. If we include <tt>ActionView::Rendering</tt>
|
||||
# beetween them to perserve the required order, we can simply do this by:
|
||||
#
|
||||
# ActionController::Base.superclass.send(:include, ActionView::Rendering)
|
||||
#
|
||||
metal = Class.new(Metal) do
|
||||
include AbstractController::Rendering
|
||||
end
|
||||
|
||||
# Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed
|
||||
# on request and then either it renders a template or redirects to another action. An action is defined as a public method
|
||||
# on the controller, which will automatically be made accessible to the web-server through \Rails Routes.
|
||||
@ -99,7 +86,7 @@ module ActionController
|
||||
# or you can remove the entire session with +reset_session+.
|
||||
#
|
||||
# Sessions are stored by default in a browser cookie that's cryptographically signed, but unencrypted.
|
||||
# This prevents the user from tampering with the session but also allows him to see its contents.
|
||||
# This prevents the user from tampering with the session but also allows them to see its contents.
|
||||
#
|
||||
# Do not put secret information in cookie-based sessions!
|
||||
#
|
||||
@ -174,7 +161,7 @@ module ActionController
|
||||
# render action: "overthere" # won't be called if monkeys is nil
|
||||
# end
|
||||
#
|
||||
class Base < metal
|
||||
class Base < Metal
|
||||
abstract!
|
||||
|
||||
# We document the request and response methods here because albeit they are
|
||||
@ -214,6 +201,7 @@ def self.without_modules(*modules)
|
||||
end
|
||||
|
||||
MODULES = [
|
||||
AbstractController::Rendering,
|
||||
AbstractController::Translation,
|
||||
AbstractController::AssetPaths,
|
||||
|
||||
@ -221,6 +209,7 @@ def self.without_modules(*modules)
|
||||
HideActions,
|
||||
UrlFor,
|
||||
Redirecting,
|
||||
ActionView::Layouts,
|
||||
Rendering,
|
||||
Renderers::All,
|
||||
ConditionalGet,
|
||||
|
@ -1,6 +1,6 @@
|
||||
module ActionController
|
||||
module Head
|
||||
# Return a response that has no content (merely headers). The options
|
||||
# Returns a response that has no content (merely headers). The options
|
||||
# argument is interpreted to be a hash of header names and values.
|
||||
# This allows you to easily return a response that consists only of
|
||||
# significant headers:
|
||||
|
@ -67,7 +67,7 @@ def redirect_to(*args)
|
||||
|
||||
private
|
||||
|
||||
# A hook invoked everytime a before callback is halted.
|
||||
# A hook invoked every time a before callback is halted.
|
||||
def halted_callback_hook(filter)
|
||||
ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter)
|
||||
end
|
||||
|
@ -48,7 +48,7 @@ module Live
|
||||
# the server will receive a +Last-Event-ID+ header with value equal to +id+.
|
||||
#
|
||||
# After setting an option in the constructor of the SSE object, all future
|
||||
# SSEs sent accross the stream will use those options unless overridden.
|
||||
# SSEs sent across the stream will use those options unless overridden.
|
||||
#
|
||||
# Example Usage:
|
||||
#
|
||||
|
@ -181,6 +181,61 @@ def clear_respond_to
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Formats can have different variants.
|
||||
#
|
||||
# The request variant is a specialization of the request format, like <tt>:tablet</tt>,
|
||||
# <tt>:phone</tt>, or <tt>:desktop</tt>.
|
||||
#
|
||||
# We often want to render different html/json/xml templates for phones,
|
||||
# tablets, and desktop browsers. Variants make it easy.
|
||||
#
|
||||
# You can set the variant in a +before_action+:
|
||||
#
|
||||
# request.variant = :tablet if request.user_agent =~ /iPad/
|
||||
#
|
||||
# Respond to variants in the action just like you respond to formats:
|
||||
#
|
||||
# respond_to do |format|
|
||||
# format.html do |variant|
|
||||
# variant.tablet # renders app/views/projects/show.html+tablet.erb
|
||||
# variant.phone { extra_setup; render ... }
|
||||
# variant.none { special_setup } # executed only if there is no variant set
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Provide separate templates for each format and variant:
|
||||
#
|
||||
# app/views/projects/show.html.erb
|
||||
# app/views/projects/show.html+tablet.erb
|
||||
# app/views/projects/show.html+phone.erb
|
||||
#
|
||||
# When you're not sharing any code within the format, you can simplify defining variants
|
||||
# using the inline syntax:
|
||||
#
|
||||
# respond_to do |format|
|
||||
# format.js { render "trash" }
|
||||
# format.html.phone { redirect_to progress_path }
|
||||
# format.html.none { render "trash" }
|
||||
# end
|
||||
#
|
||||
# Variants also support common `any`/`all` block that formats have.
|
||||
#
|
||||
# It works for both inline:
|
||||
#
|
||||
# respond_to do |format|
|
||||
# format.html.any { render text: "any" }
|
||||
# format.html.phone { render text: "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" }
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Be sure to check the documentation of +respond_with+ and
|
||||
# <tt>ActionController::MimeResponds.respond_to</tt> for more examples.
|
||||
def respond_to(*mimes, &block)
|
||||
@ -260,7 +315,7 @@ def respond_to(*mimes, &block)
|
||||
# * for other requests - i.e. data formats such as xml, json, csv etc, if
|
||||
# the resource passed to +respond_with+ responds to <code>to_<format></code>,
|
||||
# the method attempts to render the resource in the requested format
|
||||
# directly, e.g. for an xml request, the response is equivalent to calling
|
||||
# directly, e.g. for an xml request, the response is equivalent to calling
|
||||
# <code>render xml: resource</code>.
|
||||
#
|
||||
# === Nested resources
|
||||
@ -321,8 +376,10 @@ def respond_to(*mimes, &block)
|
||||
# 2. <tt>:action</tt> - overwrites the default render action used after an
|
||||
# unsuccessful html +post+ request.
|
||||
def respond_with(*resources, &block)
|
||||
raise "In order to use respond_with, first you need to declare the formats your " \
|
||||
"controller responds to in the class level" if self.class.mimes_for_respond_to.empty?
|
||||
if self.class.mimes_for_respond_to.empty?
|
||||
raise "In order to use respond_with, first you need to declare the " \
|
||||
"formats your controller responds to in the class level."
|
||||
end
|
||||
|
||||
if collector = retrieve_collector_from_mimes(&block)
|
||||
options = resources.size == 1 ? {} : resources.extract_options!
|
||||
@ -360,7 +417,7 @@ def collect_mimes_from_class_level #:nodoc:
|
||||
# is available.
|
||||
def retrieve_collector_from_mimes(mimes=nil, &block) #:nodoc:
|
||||
mimes ||= collect_mimes_from_class_level
|
||||
collector = Collector.new(mimes)
|
||||
collector = Collector.new(mimes, request.variant)
|
||||
block.call(collector) if block_given?
|
||||
format = collector.negotiate_format(request)
|
||||
|
||||
@ -398,9 +455,11 @@ class Collector
|
||||
include AbstractController::Collector
|
||||
attr_accessor :format
|
||||
|
||||
def initialize(mimes)
|
||||
def initialize(mimes, variant = nil)
|
||||
@responses = {}
|
||||
mimes.each { |mime| send(mime) }
|
||||
@variant = variant
|
||||
|
||||
mimes.each { |mime| @responses["Mime::#{mime.upcase}".constantize] = nil }
|
||||
end
|
||||
|
||||
def any(*args, &block)
|
||||
@ -414,16 +473,63 @@ def any(*args, &block)
|
||||
|
||||
def custom(mime_type, &block)
|
||||
mime_type = Mime::Type.lookup(mime_type.to_s) unless mime_type.is_a?(Mime::Type)
|
||||
@responses[mime_type] ||= block
|
||||
@responses[mime_type] ||= if block_given?
|
||||
block
|
||||
else
|
||||
VariantCollector.new(@variant)
|
||||
end
|
||||
end
|
||||
|
||||
def response
|
||||
@responses.fetch(format, @responses[Mime::ALL])
|
||||
response = @responses.fetch(format, @responses[Mime::ALL])
|
||||
if response.is_a?(VariantCollector) # `format.html.phone` - variant inline syntax
|
||||
response.variant
|
||||
elsif response.nil? || response.arity == 0 # `format.html` - just a format, call its block
|
||||
response
|
||||
else # `format.html{ |variant| variant.phone }` - variant block syntax
|
||||
variant_collector = VariantCollector.new(@variant)
|
||||
response.call(variant_collector) #call format block with variants collector
|
||||
variant_collector.variant
|
||||
end
|
||||
end
|
||||
|
||||
def negotiate_format(request)
|
||||
@format = request.negotiate_mime(@responses.keys)
|
||||
end
|
||||
|
||||
class VariantCollector #:nodoc:
|
||||
def initialize(variant = nil)
|
||||
@variant = variant
|
||||
@variants = {}
|
||||
end
|
||||
|
||||
def any(*args, &block)
|
||||
if block_given?
|
||||
if args.any? && args.none?{ |a| a == @variant }
|
||||
args.each{ |v| @variants[v] = block }
|
||||
else
|
||||
@variants[:any] = block
|
||||
end
|
||||
end
|
||||
end
|
||||
alias :all :any
|
||||
|
||||
def method_missing(name, *args, &block)
|
||||
@variants[name] = block if block_given?
|
||||
end
|
||||
|
||||
def variant
|
||||
key = if @variant.nil?
|
||||
:none
|
||||
elsif @variants.has_key?(@variant)
|
||||
@variant
|
||||
else
|
||||
:any
|
||||
end
|
||||
|
||||
@variants[key]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -58,7 +58,7 @@ module Redirecting
|
||||
# redirect_to post_url(@post), alert: "Watch it, mister!"
|
||||
# redirect_to post_url(@post), status: :found, notice: "Pay attention to the road"
|
||||
# redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
|
||||
# redirect_to { action: 'atom' }, alert: "Something serious happened"
|
||||
# redirect_to({ action: 'atom' }, alert: "Something serious happened")
|
||||
#
|
||||
# When using <tt>redirect_to :back</tt>, if there is no referrer, ActionController::RedirectBackError will be raised. You may specify some fallback
|
||||
# behavior for this case by rescuing ActionController::RedirectBackError.
|
||||
|
@ -43,7 +43,7 @@ def _handle_render_options(options)
|
||||
end
|
||||
|
||||
# Hash of available renderers, mapping a renderer name to its proc.
|
||||
# Default keys are :json, :js, :xml.
|
||||
# Default keys are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
|
||||
RENDERERS = Set.new
|
||||
|
||||
# Adds a new renderer to call within controller actions.
|
||||
|
@ -5,14 +5,24 @@ module ActionController #:nodoc:
|
||||
class InvalidAuthenticityToken < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
class InvalidCrossOriginRequest < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
# Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
|
||||
# by including a token in the rendered html for your application. This token is
|
||||
# stored as a random string in the session, to which an attacker does not have
|
||||
# access. When a request reaches your application, \Rails verifies the received
|
||||
# token with the token in the session. Only HTML and JavaScript requests are checked,
|
||||
# so this will not protect your XML API (presumably you'll have a different
|
||||
# authentication scheme there anyway). Also, GET requests are not protected as these
|
||||
# should be idempotent.
|
||||
# authentication scheme there anyway).
|
||||
#
|
||||
# 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
|
||||
# an exception: a third-party site can use a <script> tag to reference a JavaScript
|
||||
# URL on your site. When your JavaScript response loads on their site, it executes.
|
||||
# With carefully crafted JavaScript on their end, sensitive data in your JavaScript
|
||||
# response may be extracted. To prevent this, only XmlHttpRequest (known as XHR or
|
||||
# Ajax) requests are allowed to make GET requests for JavaScript responses.
|
||||
#
|
||||
# It's important to remember that XML or JSON requests are also affected and if
|
||||
# you're building an API you'll need something like:
|
||||
@ -65,17 +75,16 @@ module RequestForgeryProtection
|
||||
module ClassMethods
|
||||
# Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# protect_from_forgery
|
||||
# end
|
||||
#
|
||||
# class FooController < ApplicationController
|
||||
# protect_from_forgery except: :index
|
||||
#
|
||||
# You can disable csrf protection on controller-by-controller basis:
|
||||
#
|
||||
# You can disable CSRF protection on controller by skipping the verification before_action:
|
||||
# skip_before_action :verify_authenticity_token
|
||||
#
|
||||
# It can also be disabled for specific controller actions:
|
||||
#
|
||||
# skip_before_action :verify_authenticity_token, except: [:create]
|
||||
#
|
||||
# Valid Options:
|
||||
#
|
||||
# * <tt>:only/:except</tt> - Passed to the <tt>before_action</tt> call. Set which actions are verified.
|
||||
@ -89,6 +98,7 @@ def protect_from_forgery(options = {})
|
||||
self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
|
||||
self.request_forgery_protection_token ||= :authenticity_token
|
||||
prepend_before_action :verify_authenticity_token, options
|
||||
append_after_action :verify_same_origin_request
|
||||
end
|
||||
|
||||
private
|
||||
@ -169,18 +179,61 @@ def handle_unverified_request
|
||||
end
|
||||
|
||||
protected
|
||||
# The actual before_action that is used to verify the CSRF token.
|
||||
# Don't override this directly. Provide your own forgery protection
|
||||
# strategy instead. If you override, you'll disable same-origin
|
||||
# `<script>` verification.
|
||||
#
|
||||
# Lean on the protect_from_forgery declaration to mark which actions are
|
||||
# due for same-origin request verification. If protect_from_forgery is
|
||||
# enabled on an action, this before_action flags its after_action to
|
||||
# verify that JavaScript responses are for XHR requests, ensuring they
|
||||
# follow the browser's same-origin policy.
|
||||
def verify_authenticity_token
|
||||
mark_for_same_origin_verification!
|
||||
|
||||
if !verified_request?
|
||||
logger.warn "Can't verify CSRF token authenticity" if logger
|
||||
handle_unverified_request
|
||||
end
|
||||
end
|
||||
|
||||
def handle_unverified_request
|
||||
forgery_protection_strategy.new(self).handle_unverified_request
|
||||
end
|
||||
|
||||
# The actual before_action that is used. Modify this to change how you handle unverified requests.
|
||||
def verify_authenticity_token
|
||||
unless verified_request?
|
||||
logger.warn "Can't verify CSRF token authenticity" if logger
|
||||
handle_unverified_request
|
||||
CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \
|
||||
"<script> tag on another site requested protected JavaScript. " \
|
||||
"If you know what you're doing, go ahead and disable forgery " \
|
||||
"protection on this action to permit cross-origin JavaScript embedding."
|
||||
private_constant :CROSS_ORIGIN_JAVASCRIPT_WARNING
|
||||
|
||||
# If `verify_authenticity_token` was run (indicating that we have
|
||||
# forgery protection enabled for this request) then also verify that
|
||||
# we aren't serving an unauthorized cross-origin response.
|
||||
def verify_same_origin_request
|
||||
if marked_for_same_origin_verification? && non_xhr_javascript_response?
|
||||
logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING if logger
|
||||
raise ActionController::InvalidCrossOriginRequest, CROSS_ORIGIN_JAVASCRIPT_WARNING
|
||||
end
|
||||
end
|
||||
|
||||
# GET requests are checked for cross-origin JavaScript after rendering.
|
||||
def mark_for_same_origin_verification!
|
||||
@marked_for_same_origin_verification = request.get?
|
||||
end
|
||||
|
||||
# If the `verify_authenticity_token` before_action ran, verify that
|
||||
# JavaScript responses are only served to same-origin GET requests.
|
||||
def marked_for_same_origin_verification?
|
||||
@marked_for_same_origin_verification ||= false
|
||||
end
|
||||
|
||||
# Check for cross-origin JavaScript responses.
|
||||
def non_xhr_javascript_response?
|
||||
content_type =~ %r(\Atext/javascript) && !request.xhr?
|
||||
end
|
||||
|
||||
# Returns true or false if a request is verified. Checks:
|
||||
#
|
||||
# * is it a GET or HEAD request? Gets should be safe and idempotent
|
||||
|
@ -270,7 +270,7 @@ def has_errors?
|
||||
resource.respond_to?(:errors) && !resource.errors.empty?
|
||||
end
|
||||
|
||||
# Check whether the neceessary Renderer is available
|
||||
# Check whether the necessary Renderer is available
|
||||
def has_renderer?
|
||||
Renderers::RENDERERS.include?(format)
|
||||
end
|
||||
|
@ -3,6 +3,7 @@
|
||||
require 'active_support/rescuable'
|
||||
require 'action_dispatch/http/upload'
|
||||
require 'stringio'
|
||||
require 'set'
|
||||
|
||||
module ActionController
|
||||
# Raised when a required parameter is missing.
|
||||
@ -125,6 +126,13 @@ def initialize(attributes = nil)
|
||||
@permitted = self.class.permit_all_parameters
|
||||
end
|
||||
|
||||
# Attribute that keeps track of converted arrays, if any, to avoid double
|
||||
# looping in the common use case permit + mass-assignment. Defined in a
|
||||
# method to instantiate it only if needed.
|
||||
def converted_arrays
|
||||
@converted_arrays ||= Set.new
|
||||
end
|
||||
|
||||
# Returns +true+ if the parameter is permitted, +false+ otherwise.
|
||||
#
|
||||
# params = ActionController::Parameters.new
|
||||
@ -149,8 +157,10 @@ def permitted?
|
||||
# Person.new(params) # => #<Person id: nil, name: "Francesco">
|
||||
def permit!
|
||||
each_pair do |key, value|
|
||||
convert_hashes_to_parameters(key, value)
|
||||
self[key].permit! if self[key].respond_to? :permit!
|
||||
value = convert_hashes_to_parameters(key, value)
|
||||
Array.wrap(value).each do |_|
|
||||
_.permit! if _.respond_to? :permit!
|
||||
end
|
||||
end
|
||||
|
||||
@permitted = true
|
||||
@ -284,14 +294,7 @@ def [](key)
|
||||
# params.fetch(:none, 'Francesco') # => "Francesco"
|
||||
# params.fetch(:none) { 'Francesco' } # => "Francesco"
|
||||
def fetch(key, *args)
|
||||
value = super
|
||||
# Don't rely on +convert_hashes_to_parameters+
|
||||
# so as to not mutate via a +fetch+
|
||||
if value.is_a?(Hash)
|
||||
value = self.class.new(value)
|
||||
value.permit! if permitted?
|
||||
end
|
||||
value
|
||||
convert_hashes_to_parameters(key, super, false)
|
||||
rescue KeyError
|
||||
raise ActionController::ParameterMissing.new(key)
|
||||
end
|
||||
@ -329,12 +332,21 @@ def permitted=(new_permitted)
|
||||
end
|
||||
|
||||
private
|
||||
def convert_hashes_to_parameters(key, value)
|
||||
if value.is_a?(Parameters) || !value.is_a?(Hash)
|
||||
def convert_hashes_to_parameters(key, value, assign_if_converted=true)
|
||||
converted = convert_value_to_parameters(value)
|
||||
self[key] = converted if assign_if_converted && !converted.equal?(value)
|
||||
converted
|
||||
end
|
||||
|
||||
def convert_value_to_parameters(value)
|
||||
if value.is_a?(Array) && !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
|
||||
# Convert to Parameters on first access
|
||||
self[key] = self.class.new(value)
|
||||
self.class.new(value)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
require "action_dispatch/railtie"
|
||||
require "abstract_controller/railties/routes_helpers"
|
||||
require "action_controller/railties/helpers"
|
||||
require "action_view/railtie"
|
||||
|
||||
module ActionController
|
||||
class Railtie < Rails::Railtie #:nodoc:
|
||||
|
@ -1,5 +1,5 @@
|
||||
#--
|
||||
# Copyright (c) 2004-2013 David Heinemeier Hansson
|
||||
# Copyright (c) 2004-2014 David Heinemeier Hansson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
|
@ -10,6 +10,8 @@ module MimeNegotiation
|
||||
self.ignore_accept_header = false
|
||||
end
|
||||
|
||||
attr_reader :variant
|
||||
|
||||
# The MIME type of the HTTP request, such as Mime::XML.
|
||||
#
|
||||
# For backward compatibility, the post \format is extracted from the
|
||||
@ -48,7 +50,7 @@ def accepts
|
||||
# GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first
|
||||
#
|
||||
def format(view_path = [])
|
||||
formats.first
|
||||
formats.first || Mime::NullType.instance
|
||||
end
|
||||
|
||||
def formats
|
||||
@ -64,6 +66,18 @@ def formats
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the \variant for template.
|
||||
def variant=(variant)
|
||||
if variant.is_a? Symbol
|
||||
@variant = variant
|
||||
else
|
||||
raise ArgumentError, "request.variant must be set to a Symbol, not a #{variant.class}. " \
|
||||
"For security reasons, never directly set the variant to a user-provided value, " \
|
||||
"like params[:variant].to_sym. Check user-provided value against a whitelist first, " \
|
||||
"then set the variant: request.variant = :tablet if params[:variant] == 'tablet'"
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the \format by string extension, which can be used to force custom formats
|
||||
# that are not controlled by the extension.
|
||||
#
|
||||
|
@ -1,5 +1,6 @@
|
||||
require 'set'
|
||||
require 'active_support/core_ext/class/attribute_accessors'
|
||||
require 'singleton'
|
||||
require 'active_support/core_ext/module/attribute_accessors'
|
||||
require 'active_support/core_ext/string/starts_ends_with'
|
||||
|
||||
module Mime
|
||||
@ -27,7 +28,7 @@ def #{method}(*)
|
||||
class << self
|
||||
def [](type)
|
||||
return type if type.is_a?(Type)
|
||||
Type.lookup_by_extension(type) || NullType.new
|
||||
Type.lookup_by_extension(type)
|
||||
end
|
||||
|
||||
def fetch(type)
|
||||
@ -292,13 +293,13 @@ def respond_to_missing?(method, include_private = false) #:nodoc:
|
||||
end
|
||||
|
||||
class NullType
|
||||
include Singleton
|
||||
|
||||
def nil?
|
||||
true
|
||||
end
|
||||
|
||||
def ref
|
||||
nil
|
||||
end
|
||||
def ref; end
|
||||
|
||||
def respond_to_missing?(method, include_private = false)
|
||||
method.to_s.ends_with? '?'
|
||||
|
@ -7,6 +7,7 @@
|
||||
Mime::Type.register "text/css", :css
|
||||
Mime::Type.register "text/calendar", :ics
|
||||
Mime::Type.register "text/csv", :csv
|
||||
Mime::Type.register "text/vcard", :vcf
|
||||
|
||||
Mime::Type.register "image/png", :png, [], %w(png)
|
||||
Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
|
||||
|
@ -271,7 +271,7 @@ def session_options=(options)
|
||||
|
||||
# Override Rack's GET method to support indifferent access
|
||||
def GET
|
||||
@env["action_dispatch.request.query_parameters"] ||= (normalize_encode_params(super) || {})
|
||||
@env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {}))
|
||||
rescue TypeError => e
|
||||
raise ActionController::BadRequest.new(:query, e)
|
||||
end
|
||||
@ -279,7 +279,7 @@ def GET
|
||||
|
||||
# Override Rack's POST method to support indifferent access
|
||||
def POST
|
||||
@env["action_dispatch.request.request_parameters"] ||= (normalize_encode_params(super) || {})
|
||||
@env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {}))
|
||||
rescue TypeError => e
|
||||
raise ActionController::BadRequest.new(:request, e)
|
||||
end
|
||||
|
@ -1,4 +1,4 @@
|
||||
require 'active_support/core_ext/class/attribute_accessors'
|
||||
require 'active_support/core_ext/module/attribute_accessors'
|
||||
require 'monitor'
|
||||
|
||||
module ActionDispatch # :nodoc:
|
||||
|
@ -33,8 +33,8 @@ def generate(type, name, options, recall = {}, parameterize = nil)
|
||||
return [route.format(parameterized_parts), params]
|
||||
end
|
||||
|
||||
message = "No route matches #{constraints.inspect}"
|
||||
message << " missing required keys: #{missing_keys.inspect}" if name
|
||||
message = "No route matches #{Hash[constraints.sort].inspect}"
|
||||
message << " missing required keys: #{missing_keys.sort.inspect}" if name
|
||||
|
||||
raise ActionController::UrlGenerationError, message
|
||||
end
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# DO NOT MODIFY!!!!
|
||||
# This file is automatically generated by Racc 1.4.9
|
||||
# from Racc grammer file "".
|
||||
# from Racc grammar file "".
|
||||
#
|
||||
|
||||
require 'racc/parser.rb'
|
||||
|
@ -77,12 +77,32 @@ def visit_GROUP(node)
|
||||
end
|
||||
end
|
||||
|
||||
class OptimizedPath < String # :nodoc:
|
||||
class OptimizedPath < Visitor # :nodoc:
|
||||
def accept(node)
|
||||
Array(visit(node))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def visit_GROUP(node)
|
||||
""
|
||||
end
|
||||
def visit_CAT(node)
|
||||
[visit(node.left), visit(node.right)].flatten
|
||||
end
|
||||
|
||||
def visit_SYMBOL(node)
|
||||
node.left[1..-1].to_sym
|
||||
end
|
||||
|
||||
def visit_STAR(node)
|
||||
visit(node.left)
|
||||
end
|
||||
|
||||
def visit_GROUP(node)
|
||||
[]
|
||||
end
|
||||
|
||||
%w{ LITERAL SLASH DOT }.each do |t|
|
||||
class_eval %{ def visit_#{t}(n); n.left; end }, __FILE__, __LINE__
|
||||
end
|
||||
end
|
||||
|
||||
# Used for formatting urls (url_for)
|
||||
|
@ -30,7 +30,7 @@ def cookie_jar
|
||||
# cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
|
||||
#
|
||||
# # Sets a signed cookie, which prevents users from tampering with its value.
|
||||
# # The cookie is signed by your app's <tt>config.secret_key_base</tt> value.
|
||||
# # The cookie is signed by your app's <tt>secrets.secret_key_base</tt> value.
|
||||
# # It can be read using the signed method <tt>cookies.signed[:name]</tt>
|
||||
# cookies.signed[:user_id] = current_user.id
|
||||
#
|
||||
@ -117,10 +117,10 @@ def permanent
|
||||
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
|
||||
# cookie was tampered with by the user (or a 3rd party), nil will be returned.
|
||||
#
|
||||
# If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
|
||||
# If +secrets.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
|
||||
# legacy cookies signed with the old key generator will be transparently upgraded.
|
||||
#
|
||||
# This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
|
||||
# This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
@ -140,10 +140,10 @@ def signed
|
||||
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
|
||||
# If the cookie was tampered with by the user (or a 3rd party), nil will be returned.
|
||||
#
|
||||
# If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
|
||||
# If +secrets.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
|
||||
# legacy cookies signed with the old key generator will be transparently upgraded.
|
||||
#
|
||||
# This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
|
||||
# This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
@ -409,7 +409,7 @@ def verify(signed_message)
|
||||
end
|
||||
|
||||
# UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
|
||||
# config.secret_token and config.secret_key_base are both set. It reads
|
||||
# config.secret_token and secrets.secret_key_base are both set. It reads
|
||||
# legacy cookies signed with the old dummy key generator and re-saves
|
||||
# them using the new key generator to provide a smooth upgrade path.
|
||||
class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
|
||||
@ -427,7 +427,7 @@ class EncryptedCookieJar #:nodoc:
|
||||
|
||||
def initialize(parent_jar, key_generator, options = {})
|
||||
if ActiveSupport::LegacyKeyGenerator === key_generator
|
||||
raise "You didn't set config.secret_key_base, which is required for this cookie jar. " +
|
||||
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
|
||||
|
||||
@ -465,7 +465,7 @@ def decrypt_and_verify(encrypted_message)
|
||||
end
|
||||
|
||||
# UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
|
||||
# instead of EncryptedCookieJar if config.secret_token and config.secret_key_base
|
||||
# instead of EncryptedCookieJar if config.secret_token and secrets.secret_key_base
|
||||
# are both set. It reads legacy cookies signed with the old dummy key generator and
|
||||
# encrypts and re-saves them using the new key generator to provide a smooth upgrade path.
|
||||
class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc:
|
||||
|
@ -1,5 +1,5 @@
|
||||
require 'action_controller/metal/exceptions'
|
||||
require 'active_support/core_ext/class/attribute_accessors'
|
||||
require 'active_support/core_ext/module/attribute_accessors'
|
||||
|
||||
module ActionDispatch
|
||||
class ExceptionWrapper
|
||||
|
@ -15,8 +15,8 @@ module Session
|
||||
# best possible option given your application's configuration.
|
||||
#
|
||||
# If you only have secret_token set, your cookies will be signed, but
|
||||
# not encrypted. This means a user cannot alter his +user_id+ without
|
||||
# knowing your app's secret key, but can easily read his +user_id+. This
|
||||
# not encrypted. This means a user cannot alter their +user_id+ without
|
||||
# knowing your app's secret key, but can easily read their +user_id+. This
|
||||
# was the default for Rails 3 apps.
|
||||
#
|
||||
# If you have secret_key_base set, your cookies will be encrypted. This
|
||||
@ -31,9 +31,10 @@ module Session
|
||||
#
|
||||
# Myapp::Application.config.session_store :cookie_store, key: '_your_app_session'
|
||||
#
|
||||
# Configure your secret key in config/initializers/secret_token.rb:
|
||||
# Configure your secret key in config/secrets.yml:
|
||||
#
|
||||
# Myapp::Application.config.secret_key_base 'secret key'
|
||||
# development:
|
||||
# secret_key_base: 'secret key'
|
||||
#
|
||||
# To generate a secret key for an existing application, run `rake secret`.
|
||||
#
|
||||
|
@ -11,9 +11,10 @@ def initialize(root, cache_control)
|
||||
end
|
||||
|
||||
def match?(path)
|
||||
path = path.dup
|
||||
path = unescape_path(path)
|
||||
return false unless path.valid_encoding?
|
||||
|
||||
full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(unescape_path(path)))
|
||||
full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(path))
|
||||
paths = "#{full_path}#{ext}"
|
||||
|
||||
matches = Dir[paths]
|
||||
@ -40,7 +41,6 @@ def unescape_path(path)
|
||||
end
|
||||
|
||||
def escape_glob_chars(path)
|
||||
path.force_encoding('binary') if path.respond_to? :force_encoding
|
||||
path.gsub(/[*?{}\[\]]/, "\\\\\\&")
|
||||
end
|
||||
end
|
||||
|
@ -89,8 +89,8 @@
|
||||
}
|
||||
|
||||
// takes an array of elements with a data-regexp attribute and
|
||||
// passes their their parent <tr> into the callback function
|
||||
// if the regexp matchs a given path
|
||||
// passes their parent <tr> into the callback function
|
||||
// if the regexp matches a given path
|
||||
function eachElemsForPath(elems, path, func) {
|
||||
each(elems, function(e){
|
||||
var reg = e.getAttribute("data-regexp");
|
||||
|
@ -16,6 +16,7 @@ class Railtie < Rails::Railtie # :nodoc:
|
||||
config.action_dispatch.signed_cookie_salt = 'signed cookie'
|
||||
config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie'
|
||||
config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie'
|
||||
config.action_dispatch.perform_deep_munge = true
|
||||
|
||||
config.action_dispatch.default_headers = {
|
||||
'X-Frame-Options' => 'SAMEORIGIN',
|
||||
@ -28,6 +29,7 @@ class Railtie < Rails::Railtie # :nodoc:
|
||||
initializer "action_dispatch.configure" do |app|
|
||||
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
|
||||
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
|
||||
ActionDispatch::Request::Utils.perform_deep_munge = app.config.action_dispatch.perform_deep_munge
|
||||
ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding
|
||||
ActionDispatch::Response.default_headers = app.config.action_dispatch.default_headers
|
||||
|
||||
|
@ -7,6 +7,9 @@ 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:
|
||||
|
||||
# Singleton object used to determine if an optional param wasn't specified
|
||||
Unspecified = Object.new
|
||||
|
||||
def self.create(store, env, default_options)
|
||||
session_was = find env
|
||||
session = Request::Session.new(store, env)
|
||||
@ -127,15 +130,12 @@ def delete(key)
|
||||
@delegate.delete key.to_s
|
||||
end
|
||||
|
||||
def fetch(key, default=nil)
|
||||
if self.key?(key)
|
||||
self[key]
|
||||
elsif default
|
||||
self[key] = default
|
||||
elsif block_given?
|
||||
self[key] = yield(key)
|
||||
def fetch(key, default=Unspecified, &block)
|
||||
load_for_read!
|
||||
if default == Unspecified
|
||||
@delegate.fetch(key.to_s, &block)
|
||||
else
|
||||
raise KeyError
|
||||
@delegate.fetch(key.to_s, default, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,9 +1,15 @@
|
||||
module ActionDispatch
|
||||
class Request < Rack::Request
|
||||
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)
|
||||
return hash unless perform_deep_munge
|
||||
|
||||
hash.each do |k, v|
|
||||
case v
|
||||
when Array
|
||||
|
@ -69,7 +69,7 @@ def action
|
||||
end
|
||||
|
||||
def internal?
|
||||
controller.to_s =~ %r{\Arails/(info|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}}
|
||||
controller.to_s =~ %r{\Arails/(info|mailers|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}\z}
|
||||
end
|
||||
|
||||
def engine?
|
||||
|
@ -3,6 +3,7 @@
|
||||
require 'active_support/core_ext/hash/slice'
|
||||
require 'active_support/core_ext/enumerable'
|
||||
require 'active_support/core_ext/array/extract_options'
|
||||
require 'active_support/core_ext/module/remove_method'
|
||||
require 'active_support/inflector'
|
||||
require 'action_dispatch/routing/redirection'
|
||||
|
||||
@ -217,8 +218,12 @@ def default_controller_and_action
|
||||
controller ||= default_controller
|
||||
action ||= default_action
|
||||
|
||||
unless controller.is_a?(Regexp)
|
||||
controller = [@scope[:module], controller].compact.join("/").presence
|
||||
if @scope[:module] && !controller.is_a?(Regexp)
|
||||
if controller =~ %r{\A/}
|
||||
controller = controller[1..-1]
|
||||
else
|
||||
controller = [@scope[:module], controller].compact.join("/").presence
|
||||
end
|
||||
end
|
||||
|
||||
if controller.is_a?(String) && controller =~ %r{\A/}
|
||||
@ -502,11 +507,12 @@ def mount(app, options = nil)
|
||||
raise "A rack application must be specified" unless path
|
||||
|
||||
options[:as] ||= app_name(app)
|
||||
target_as = name_for_action(options[:as], path)
|
||||
options[:via] ||= :all
|
||||
|
||||
match(path, options.merge(:to => app, :anchor => false, :format => false))
|
||||
|
||||
define_generate_prefix(app, options[:as])
|
||||
define_generate_prefix(app, target_as)
|
||||
self
|
||||
end
|
||||
|
||||
@ -545,11 +551,11 @@ def define_generate_prefix(app, name)
|
||||
_routes = @set
|
||||
app.routes.define_mounted_helper(name)
|
||||
app.routes.singleton_class.class_eval do
|
||||
define_method :mounted? do
|
||||
redefine_method :mounted? do
|
||||
true
|
||||
end
|
||||
|
||||
define_method :_generate_prefix do |options|
|
||||
redefine_method :_generate_prefix do |options|
|
||||
prefix_options = options.slice(*_route.segment_keys)
|
||||
# we must actually delete prefix segment keys to avoid passing them to next url_for
|
||||
_route.segment_keys.each { |k| options.delete(k) }
|
||||
|
@ -26,14 +26,19 @@ def call(env)
|
||||
end
|
||||
|
||||
uri = URI.parse(path(req.symbolized_path_parameters, req))
|
||||
|
||||
unless uri.host
|
||||
if relative_path?(uri.path)
|
||||
uri.path = "#{req.script_name}/#{uri.path}"
|
||||
elsif uri.path.empty?
|
||||
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?
|
||||
|
||||
if relative_path?(uri.path)
|
||||
uri.path = "#{req.script_name}/#{uri.path}"
|
||||
end
|
||||
|
||||
body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)
|
||||
|
||||
headers = {
|
||||
@ -57,11 +62,33 @@ def inspect
|
||||
def relative_path?(path)
|
||||
path && !path.empty? && path[0] != '/'
|
||||
end
|
||||
|
||||
def escape(params)
|
||||
Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }]
|
||||
end
|
||||
|
||||
def escape_fragment(params)
|
||||
Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_fragment(v)] }]
|
||||
end
|
||||
|
||||
def escape_path(params)
|
||||
Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_path(v)] }]
|
||||
end
|
||||
end
|
||||
|
||||
class PathRedirect < Redirect
|
||||
URL_PARTS = /\A([^?]+)?(\?[^#]+)?(#.+)?\z/
|
||||
|
||||
def path(params, request)
|
||||
(params.empty? || !block.match(/%\{\w*\}/)) ? block : (block % escape(params))
|
||||
if block.match(URL_PARTS)
|
||||
path = interpolation_required?($1, params) ? $1 % escape_path(params) : $1
|
||||
query = interpolation_required?($2, params) ? $2 % escape(params) : $2
|
||||
fragment = interpolation_required?($3, params) ? $3 % escape_fragment(params) : $3
|
||||
|
||||
"#{path}#{query}#{fragment}"
|
||||
else
|
||||
interpolation_required?(block, params) ? block % escape(params) : block
|
||||
end
|
||||
end
|
||||
|
||||
def inspect
|
||||
@ -69,8 +96,8 @@ def inspect
|
||||
end
|
||||
|
||||
private
|
||||
def escape(params)
|
||||
Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }]
|
||||
def interpolation_required?(string, params)
|
||||
!params.empty? && string && string.match(/%\{\w*\}/)
|
||||
end
|
||||
end
|
||||
|
||||
@ -90,22 +117,22 @@ def path(params, request)
|
||||
url_options[:path] = (url_options[:path] % escape_path(params))
|
||||
end
|
||||
|
||||
if relative_path?(url_options[:path])
|
||||
url_options[:path] = "/#{url_options[:path]}"
|
||||
url_options[:script_name] = request.script_name
|
||||
unless options[:host] || options[:domain]
|
||||
if relative_path?(url_options[:path])
|
||||
url_options[:path] = "/#{url_options[:path]}"
|
||||
url_options[:script_name] = request.script_name
|
||||
elsif url_options[:path].empty?
|
||||
url_options[:path] = request.script_name.empty? ? "/" : ""
|
||||
url_options[:script_name] = request.script_name
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
ActionDispatch::Http::URL.url_for url_options
|
||||
end
|
||||
|
||||
def inspect
|
||||
"redirect(#{status}, #{options.map{ |k,v| "#{k}: #{v}" }.join(', ')})"
|
||||
end
|
||||
|
||||
private
|
||||
def escape_path(params)
|
||||
Hash[params.map{ |k,v| [k, URI.parser.escape(v)] }]
|
||||
end
|
||||
end
|
||||
|
||||
module Redirection
|
||||
|
@ -163,9 +163,10 @@ class OptimizedUrlHelper < UrlHelper # :nodoc:
|
||||
|
||||
def initialize(route, options)
|
||||
super
|
||||
@path_parts = @route.required_parts
|
||||
@arg_size = @path_parts.size
|
||||
@string_route = @route.optimized_path
|
||||
@klass = Journey::Router::Utils
|
||||
@required_parts = @route.required_parts
|
||||
@arg_size = @required_parts.size
|
||||
@optimized_path = @route.optimized_path
|
||||
end
|
||||
|
||||
def call(t, args)
|
||||
@ -182,43 +183,36 @@ def call(t, args)
|
||||
private
|
||||
|
||||
def optimized_helper(args)
|
||||
path = @string_route.dup
|
||||
klass = Journey::Router::Utils
|
||||
params = Hash[parameterize_args(args)]
|
||||
missing_keys = missing_keys(params)
|
||||
|
||||
@path_parts.zip(args) do |part, arg|
|
||||
parameterized_arg = arg.to_param
|
||||
|
||||
if parameterized_arg.nil? || parameterized_arg.empty?
|
||||
raise_generation_error(args)
|
||||
end
|
||||
|
||||
# Replace each route parameter
|
||||
# e.g. :id for regular parameter or *path for globbing
|
||||
# with ruby string interpolation code
|
||||
path.gsub!(/(\*|:)#{part}/, klass.escape_fragment(parameterized_arg))
|
||||
unless missing_keys.empty?
|
||||
raise_generation_error(params, missing_keys)
|
||||
end
|
||||
path
|
||||
|
||||
@optimized_path.map{ |segment| replace_segment(params, segment) }.join
|
||||
end
|
||||
|
||||
def replace_segment(params, segment)
|
||||
Symbol === segment ? @klass.escape_fragment(params[segment]) : segment
|
||||
end
|
||||
|
||||
def optimize_routes_generation?(t)
|
||||
t.send(:optimize_routes_generation?)
|
||||
end
|
||||
|
||||
def raise_generation_error(args)
|
||||
parts, missing_keys = [], []
|
||||
def parameterize_args(args)
|
||||
@required_parts.zip(args.map(&:to_param))
|
||||
end
|
||||
|
||||
@path_parts.zip(args) do |part, arg|
|
||||
parameterized_arg = arg.to_param
|
||||
def missing_keys(args)
|
||||
args.select{ |part, arg| arg.nil? || arg.empty? }.keys
|
||||
end
|
||||
|
||||
if parameterized_arg.nil? || parameterized_arg.empty?
|
||||
missing_keys << part
|
||||
end
|
||||
|
||||
parts << [part, arg]
|
||||
end
|
||||
|
||||
message = "No route matches #{Hash[parts].inspect}"
|
||||
message << " missing required keys: #{missing_keys.inspect}"
|
||||
def raise_generation_error(args, missing_keys)
|
||||
constraints = Hash[@route.requirements.merge(args).sort]
|
||||
message = "No route matches #{constraints.inspect}"
|
||||
message << " missing required keys: #{missing_keys.sort.inspect}"
|
||||
|
||||
raise ActionController::UrlGenerationError, message
|
||||
end
|
||||
@ -226,7 +220,7 @@ def raise_generation_error(args)
|
||||
|
||||
def initialize(route, options)
|
||||
@options = options
|
||||
@segment_keys = route.segment_keys
|
||||
@segment_keys = route.segment_keys.uniq
|
||||
@route = route
|
||||
end
|
||||
|
||||
@ -361,7 +355,7 @@ module MountedHelpers #:nodoc:
|
||||
include UrlFor
|
||||
end
|
||||
|
||||
# Contains all the mounted helpers accross different
|
||||
# Contains all the mounted helpers across different
|
||||
# engines and the `main_app` helper for the application.
|
||||
# You can include this in your classes if you want to
|
||||
# access routes for other engines.
|
||||
|
@ -211,7 +211,7 @@ def recognized_request_for(path, extras = {})
|
||||
def fail_on(exception_class)
|
||||
yield
|
||||
rescue exception_class => e
|
||||
raise MiniTest::Assertion, e.message
|
||||
raise Minitest::Assertion, e.message
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -137,7 +137,7 @@ def delete_via_redirect(path, parameters = nil, headers_or_env = nil)
|
||||
class Session
|
||||
DEFAULT_HOST = "www.example.com"
|
||||
|
||||
include MiniTest::Assertions
|
||||
include Minitest::Assertions
|
||||
include TestProcess, RequestHelpers, Assertions
|
||||
|
||||
%w( status status_message headers body redirect? ).each do |method|
|
||||
@ -242,7 +242,7 @@ def https!(flag = true)
|
||||
@https = flag
|
||||
end
|
||||
|
||||
# Return +true+ if the session is mimicking a secure HTTPS request.
|
||||
# Returns +true+ if the session is mimicking a secure HTTPS request.
|
||||
#
|
||||
# if session.https?
|
||||
# ...
|
||||
|
@ -1,5 +1,5 @@
|
||||
#--
|
||||
# Copyright (c) 2004-2013 David Heinemeier Hansson
|
||||
# Copyright (c) 2004-2014 David Heinemeier Hansson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
|
@ -1,7 +1,7 @@
|
||||
module ActionPack
|
||||
# Returns the version of the currently loaded ActionPack as a Gem::Version
|
||||
def self.version
|
||||
Gem::Version.new "4.1.0.beta"
|
||||
Gem::Version.new "4.1.0.beta1"
|
||||
end
|
||||
|
||||
module VERSION #:nodoc:
|
||||
|
@ -37,7 +37,7 @@ class TestCollector < ActiveSupport::TestCase
|
||||
|
||||
test "does not register unknown mime types" do
|
||||
collector = MyCollector.new
|
||||
assert_raise NameError do
|
||||
assert_raise NoMethodError do
|
||||
collector.unknown
|
||||
end
|
||||
end
|
||||
|
@ -43,6 +43,9 @@ def env
|
||||
# Show backtraces for deprecated behavior for quicker cleanup.
|
||||
ActiveSupport::Deprecation.debug = true
|
||||
|
||||
# Disable available locale checks to avoid warnings running the test suite.
|
||||
I18n.enforce_available_locales = false
|
||||
|
||||
# Register danish language for testing
|
||||
I18n.backend.store_translations 'da', {}
|
||||
I18n.backend.store_translations 'pt-BR', {}
|
||||
@ -246,8 +249,6 @@ def assert_header(name, value)
|
||||
end
|
||||
end
|
||||
|
||||
ActionController::Base.superclass.send(:include, ActionView::Layouts)
|
||||
|
||||
module ActionController
|
||||
class Base
|
||||
include ActionController::Testing
|
||||
@ -333,7 +334,6 @@ class ThreadsController < ResourcesController; end
|
||||
class MessagesController < ResourcesController; end
|
||||
class CommentsController < ResourcesController; end
|
||||
class ReviewsController < ResourcesController; end
|
||||
class AuthorsController < ResourcesController; end
|
||||
class LogosController < ResourcesController; end
|
||||
|
||||
class AccountsController < ResourcesController; end
|
||||
@ -344,8 +344,6 @@ class PreferencesController < ResourcesController; end
|
||||
|
||||
module Backoffice
|
||||
class ProductsController < ResourcesController; end
|
||||
class TagsController < ResourcesController; end
|
||||
class ManufacturersController < ResourcesController; end
|
||||
class ImagesController < ResourcesController; end
|
||||
|
||||
module Admin
|
||||
|
@ -19,7 +19,7 @@ def test_assert_response_predicate_methods
|
||||
@response = FakeResponse.new sym
|
||||
assert_response sym
|
||||
|
||||
assert_raises(MiniTest::Assertion) {
|
||||
assert_raises(Minitest::Assertion) {
|
||||
assert_response :unauthorized
|
||||
}
|
||||
end
|
||||
@ -29,11 +29,11 @@ def test_assert_response_fixnum
|
||||
@response = FakeResponse.new 400
|
||||
assert_response 400
|
||||
|
||||
assert_raises(MiniTest::Assertion) {
|
||||
assert_raises(Minitest::Assertion) {
|
||||
assert_response :unauthorized
|
||||
}
|
||||
|
||||
assert_raises(MiniTest::Assertion) {
|
||||
assert_raises(Minitest::Assertion) {
|
||||
assert_response 500
|
||||
}
|
||||
end
|
||||
@ -42,11 +42,11 @@ def test_assert_response_sym_status
|
||||
@response = FakeResponse.new 401
|
||||
assert_response :unauthorized
|
||||
|
||||
assert_raises(MiniTest::Assertion) {
|
||||
assert_raises(Minitest::Assertion) {
|
||||
assert_response :ok
|
||||
}
|
||||
|
||||
assert_raises(MiniTest::Assertion) {
|
||||
assert_raises(Minitest::Assertion) {
|
||||
assert_response :success
|
||||
}
|
||||
end
|
||||
|
@ -444,22 +444,18 @@ def test_redirected_to_with_nested_controller
|
||||
|
||||
def test_assert_response_uses_exception_message
|
||||
@controller = AssertResponseWithUnexpectedErrorController.new
|
||||
get :index
|
||||
e = assert_raise RuntimeError, 'Expected non-success response' do
|
||||
get :index
|
||||
end
|
||||
assert_response :success
|
||||
flunk 'Expected non-success response'
|
||||
rescue RuntimeError => e
|
||||
assert e.message.include?('FAIL')
|
||||
assert_includes 'FAIL', e.message
|
||||
end
|
||||
|
||||
def test_assert_response_failure_response_with_no_exception
|
||||
@controller = AssertResponseWithUnexpectedErrorController.new
|
||||
get :show
|
||||
assert_response :success
|
||||
flunk 'Expected non-success response'
|
||||
rescue ActiveSupport::TestCase::Assertion
|
||||
# success
|
||||
rescue
|
||||
flunk "assert_response failed to handle failure response with missing, but optional, exception."
|
||||
assert_response 500
|
||||
assert_equal 'Boom', response.body
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -893,17 +893,6 @@ def around(controller)
|
||||
around_filter YieldingFilter.new, :only => :raises_after
|
||||
end
|
||||
|
||||
class ControllerWithFilterMethod < PostsController
|
||||
class YieldingFilter < DefaultFilter
|
||||
def around(controller)
|
||||
yield
|
||||
raise After
|
||||
end
|
||||
end
|
||||
|
||||
around_filter YieldingFilter.new.method(:around), :only => :raises_after
|
||||
end
|
||||
|
||||
class ControllerWithProcFilter < PostsController
|
||||
around_filter(:only => :no_raise) do |c,b|
|
||||
c.instance_variable_set(:"@before", true)
|
||||
|
@ -21,7 +21,7 @@ def display
|
||||
|
||||
def authenticate
|
||||
authenticate_or_request_with_http_digest("SuperSecret") do |username|
|
||||
# Return the password
|
||||
# Returns the password
|
||||
USERS[username]
|
||||
end
|
||||
end
|
||||
|
@ -34,4 +34,15 @@ def test_use_fallback_locales
|
||||
get :hello_world
|
||||
assert_equal "Gutten Tag", @response.body
|
||||
end
|
||||
|
||||
def test_localized_template_has_correct_header_with_no_format_in_template_name
|
||||
old_locale = I18n.locale
|
||||
I18n.locale = :it
|
||||
|
||||
get :hello_world
|
||||
assert_equal "Ciao Mondo", @response.body
|
||||
assert_equal "text/html", @response.content_type
|
||||
ensure
|
||||
I18n.locale = old_locale
|
||||
end
|
||||
end
|
||||
|
@ -146,6 +146,106 @@ def iphone_with_html_response_type_without_layout
|
||||
end
|
||||
end
|
||||
|
||||
def variant_with_implicit_rendering
|
||||
end
|
||||
|
||||
def variant_with_format_and_custom_render
|
||||
request.variant = :mobile
|
||||
|
||||
respond_to do |type|
|
||||
type.html { render text: "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" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def variant_plus_none_for_format
|
||||
respond_to do |format|
|
||||
format.html do |variant|
|
||||
variant.phone { render text: "phone" }
|
||||
variant.none
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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" }
|
||||
end
|
||||
end
|
||||
|
||||
def variant_inline_syntax_without_block
|
||||
respond_to do |format|
|
||||
format.js
|
||||
format.html.none
|
||||
format.html.phone
|
||||
end
|
||||
end
|
||||
|
||||
def variant_any
|
||||
respond_to do |format|
|
||||
format.html do |variant|
|
||||
variant.any(:tablet, :phablet){ render text: "any" }
|
||||
variant.phone { render text: "phone" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def variant_any_any
|
||||
respond_to do |format|
|
||||
format.html do |variant|
|
||||
variant.any { render text: "any" }
|
||||
variant.phone { render text: "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" }
|
||||
end
|
||||
end
|
||||
|
||||
def variant_inline_any_any
|
||||
respond_to do |format|
|
||||
format.html.phone { render text: "phone" }
|
||||
format.html.any { render text: "any" }
|
||||
end
|
||||
end
|
||||
|
||||
def variant_any_implicit_render
|
||||
respond_to do |format|
|
||||
format.html.phone
|
||||
format.html.any(:tablet, :phablet)
|
||||
end
|
||||
end
|
||||
|
||||
def variant_any_with_none
|
||||
respond_to do |format|
|
||||
format.html.any(:none, :phone){ render text: "none or phone" }
|
||||
end
|
||||
end
|
||||
|
||||
def format_any_variant_any
|
||||
respond_to do |format|
|
||||
format.html { render text: "HTML" }
|
||||
format.any(:js, :xml) do |variant|
|
||||
variant.phone{ render text: "phone" }
|
||||
variant.any(:tablet, :phablet){ render text: "tablet" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def set_layout
|
||||
case action_name
|
||||
@ -490,4 +590,154 @@ def test_invalid_format
|
||||
get :using_defaults, :format => "invalidformat"
|
||||
end
|
||||
end
|
||||
|
||||
def test_invalid_variant
|
||||
@request.variant = :invalid
|
||||
assert_raises(ActionView::MissingTemplate) do
|
||||
get :variant_with_implicit_rendering
|
||||
end
|
||||
end
|
||||
|
||||
def test_variant_not_set_regular_template_missing
|
||||
assert_raises(ActionView::MissingTemplate) do
|
||||
get :variant_with_implicit_rendering
|
||||
end
|
||||
end
|
||||
|
||||
def test_variant_with_implicit_rendering
|
||||
@request.variant = :mobile
|
||||
get :variant_with_implicit_rendering
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "mobile", @response.body
|
||||
end
|
||||
|
||||
def test_variant_with_format_and_custom_render
|
||||
@request.variant = :phone
|
||||
get :variant_with_format_and_custom_render
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "mobile", @response.body
|
||||
end
|
||||
|
||||
def test_multiple_variants_for_format
|
||||
@request.variant = :tablet
|
||||
get :multiple_variants_for_format
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "tablet", @response.body
|
||||
end
|
||||
|
||||
def test_no_variant_in_variant_setup
|
||||
get :variant_plus_none_for_format
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "none", @response.body
|
||||
end
|
||||
|
||||
def test_variant_inline_syntax
|
||||
get :variant_inline_syntax, format: :js
|
||||
assert_equal "text/javascript", @response.content_type
|
||||
assert_equal "js", @response.body
|
||||
|
||||
get :variant_inline_syntax
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "none", @response.body
|
||||
|
||||
@request.variant = :phone
|
||||
get :variant_inline_syntax
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "phone", @response.body
|
||||
end
|
||||
|
||||
def test_variant_inline_syntax_without_block
|
||||
@request.variant = :phone
|
||||
get :variant_inline_syntax_without_block
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "phone", @response.body
|
||||
end
|
||||
|
||||
def test_variant_any
|
||||
@request.variant = :phone
|
||||
get :variant_any
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "phone", @response.body
|
||||
|
||||
@request.variant = :tablet
|
||||
get :variant_any
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "any", @response.body
|
||||
|
||||
@request.variant = :phablet
|
||||
get :variant_any
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "any", @response.body
|
||||
end
|
||||
|
||||
def test_variant_any_any
|
||||
@request.variant = :phone
|
||||
get :variant_any_any
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "phone", @response.body
|
||||
|
||||
@request.variant = :yolo
|
||||
get :variant_any_any
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "any", @response.body
|
||||
end
|
||||
|
||||
def test_variant_inline_any
|
||||
@request.variant = :phone
|
||||
get :variant_any
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "phone", @response.body
|
||||
|
||||
@request.variant = :tablet
|
||||
get :variant_inline_any
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "any", @response.body
|
||||
|
||||
@request.variant = :phablet
|
||||
get :variant_inline_any
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "any", @response.body
|
||||
end
|
||||
|
||||
def test_variant_inline_any_any
|
||||
@request.variant = :phone
|
||||
get :variant_inline_any_any
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "phone", @response.body
|
||||
|
||||
@request.variant = :yolo
|
||||
get :variant_inline_any_any
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "any", @response.body
|
||||
end
|
||||
|
||||
def test_variant_any_implicit_render
|
||||
@request.variant = :tablet
|
||||
get :variant_any_implicit_render
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "tablet", @response.body
|
||||
|
||||
@request.variant = :phablet
|
||||
get :variant_any_implicit_render
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "phablet", @response.body
|
||||
end
|
||||
|
||||
def test_variant_any_with_none
|
||||
get :variant_any_with_none
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "none or phone", @response.body
|
||||
|
||||
@request.variant = :phone
|
||||
get :variant_any_with_none
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "none or phone", @response.body
|
||||
end
|
||||
|
||||
def test_format_any_variant_any
|
||||
@request.variant = :tablet
|
||||
get :format_any_variant_any, format: :js
|
||||
assert_equal "text/javascript", @response.content_type
|
||||
assert_equal "tablet", @response.body
|
||||
end
|
||||
end
|
||||
|
@ -8,9 +8,16 @@ def assert_filtered_out(params, key)
|
||||
end
|
||||
|
||||
setup do
|
||||
@params = ActionController::Parameters.new({ person: {
|
||||
age: "32", name: { first: "David", last: "Heinemeier Hansson" }
|
||||
}})
|
||||
@params = ActionController::Parameters.new(
|
||||
person: {
|
||||
age: '32',
|
||||
name: {
|
||||
first: 'David',
|
||||
last: 'Heinemeier Hansson'
|
||||
},
|
||||
addresses: [{city: 'Chicago', state: 'Illinois'}]
|
||||
}
|
||||
)
|
||||
|
||||
@struct_fields = []
|
||||
%w(0 1 12).each do |number|
|
||||
@ -153,6 +160,18 @@ def assert_filtered_out(params, key)
|
||||
assert_equal nil, params[:foo]
|
||||
end
|
||||
|
||||
test 'hashes in array values get wrapped' do
|
||||
params = ActionController::Parameters.new(foo: [{}, {}])
|
||||
params[:foo].each do |hash|
|
||||
assert !hash.permitted?
|
||||
end
|
||||
end
|
||||
|
||||
test 'arrays are converted at most once' do
|
||||
params = ActionController::Parameters.new(foo: [{}])
|
||||
assert params[:foo].equal?(params[:foo])
|
||||
end
|
||||
|
||||
test "fetch doesnt raise ParameterMissing exception if there is a default" do
|
||||
assert_equal "monkey", @params.fetch(:foo, "monkey")
|
||||
assert_equal "monkey", @params.fetch(:foo) { "monkey" }
|
||||
@ -221,6 +240,7 @@ def assert_filtered_out(params, key)
|
||||
assert @params.permitted?
|
||||
assert @params[:person].permitted?
|
||||
assert @params[:person][:name].permitted?
|
||||
assert @params[:person][:addresses][0].permitted?
|
||||
end
|
||||
|
||||
test "permitted takes a default value when Parameters.permit_all_parameters is set" do
|
||||
|
@ -22,7 +22,7 @@ def show_partial
|
||||
tests TestController
|
||||
|
||||
def test_render_vanilla_js
|
||||
get :render_vanilla_js_hello
|
||||
xhr :get, :render_vanilla_js_hello
|
||||
assert_equal "alert('hello')", @response.body
|
||||
assert_equal "text/javascript", @response.content_type
|
||||
end
|
||||
|
@ -100,13 +100,13 @@ def test_render_json_with_status
|
||||
end
|
||||
|
||||
def test_render_json_with_callback
|
||||
get :render_json_hello_world_with_callback
|
||||
xhr :get, :render_json_hello_world_with_callback
|
||||
assert_equal 'alert({"hello":"world"})', @response.body
|
||||
assert_equal 'text/javascript', @response.content_type
|
||||
end
|
||||
|
||||
def test_render_json_with_custom_content_type
|
||||
get :render_json_with_custom_content_type
|
||||
xhr :get, :render_json_with_custom_content_type
|
||||
assert_equal '{"hello":"world"}', @response.body
|
||||
assert_equal 'text/javascript', @response.content_type
|
||||
end
|
||||
|
@ -529,4 +529,4 @@ def test_head_with_status_code_first
|
||||
assert_equal "something", @response.headers["X-Custom-Header"]
|
||||
assert_response :forbidden
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -52,18 +52,36 @@ def form_for_remote_with_external_token
|
||||
render :inline => "<%= form_for(:some_resource, :remote => true, :authenticity_token => 'external_token') {} %>"
|
||||
end
|
||||
|
||||
def same_origin_js
|
||||
render js: 'foo();'
|
||||
end
|
||||
|
||||
def negotiate_same_origin
|
||||
respond_to do |format|
|
||||
format.js { same_origin_js }
|
||||
end
|
||||
end
|
||||
|
||||
def cross_origin_js
|
||||
same_origin_js
|
||||
end
|
||||
|
||||
def negotiate_cross_origin
|
||||
negotiate_same_origin
|
||||
end
|
||||
|
||||
def rescue_action(e) raise e end
|
||||
end
|
||||
|
||||
# sample controllers
|
||||
class RequestForgeryProtectionControllerUsingResetSession < ActionController::Base
|
||||
include RequestForgeryProtectionActions
|
||||
protect_from_forgery :only => %w(index meta), :with => :reset_session
|
||||
protect_from_forgery :only => %w(index meta same_origin_js negotiate_same_origin), :with => :reset_session
|
||||
end
|
||||
|
||||
class RequestForgeryProtectionControllerUsingException < ActionController::Base
|
||||
include RequestForgeryProtectionActions
|
||||
protect_from_forgery :only => %w(index meta), :with => :exception
|
||||
protect_from_forgery :only => %w(index meta same_origin_js negotiate_same_origin), :with => :exception
|
||||
end
|
||||
|
||||
class RequestForgeryProtectionControllerUsingNullSession < ActionController::Base
|
||||
@ -201,7 +219,7 @@ def test_should_not_allow_post_without_token
|
||||
end
|
||||
|
||||
def test_should_not_allow_post_without_token_irrespective_of_format
|
||||
assert_blocked { post :index, :format=>'xml' }
|
||||
assert_blocked { post :index, format: 'xml' }
|
||||
end
|
||||
|
||||
def test_should_not_allow_patch_without_token
|
||||
@ -271,6 +289,48 @@ def test_should_warn_on_missing_csrf_token
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_only_allow_same_origin_js_get_with_xhr_header
|
||||
assert_cross_origin_blocked { get :same_origin_js }
|
||||
assert_cross_origin_blocked { get :same_origin_js, format: 'js' }
|
||||
assert_cross_origin_blocked do
|
||||
@request.accept = 'text/javascript'
|
||||
get :negotiate_same_origin
|
||||
end
|
||||
|
||||
assert_cross_origin_not_blocked { xhr :get, :same_origin_js }
|
||||
assert_cross_origin_not_blocked { xhr :get, :same_origin_js, format: 'js' }
|
||||
assert_cross_origin_not_blocked do
|
||||
@request.accept = 'text/javascript'
|
||||
xhr :get, :negotiate_same_origin
|
||||
end
|
||||
end
|
||||
|
||||
# Allow non-GET requests since GET is all a remote <script> tag can muster.
|
||||
def test_should_allow_non_get_js_without_xhr_header
|
||||
assert_cross_origin_not_blocked { post :same_origin_js, custom_authenticity_token: @token }
|
||||
assert_cross_origin_not_blocked { post :same_origin_js, format: 'js', custom_authenticity_token: @token }
|
||||
assert_cross_origin_not_blocked do
|
||||
@request.accept = 'text/javascript'
|
||||
post :negotiate_same_origin, custom_authenticity_token: @token
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_only_allow_cross_origin_js_get_without_xhr_header_if_protection_disabled
|
||||
assert_cross_origin_not_blocked { get :cross_origin_js }
|
||||
assert_cross_origin_not_blocked { get :cross_origin_js, format: 'js' }
|
||||
assert_cross_origin_not_blocked do
|
||||
@request.accept = 'text/javascript'
|
||||
get :negotiate_cross_origin
|
||||
end
|
||||
|
||||
assert_cross_origin_not_blocked { xhr :get, :cross_origin_js }
|
||||
assert_cross_origin_not_blocked { xhr :get, :cross_origin_js, format: 'js' }
|
||||
assert_cross_origin_not_blocked do
|
||||
@request.accept = 'text/javascript'
|
||||
xhr :get, :negotiate_cross_origin
|
||||
end
|
||||
end
|
||||
|
||||
def assert_blocked
|
||||
session[:something_like_user_id] = 1
|
||||
yield
|
||||
@ -282,6 +342,16 @@ def assert_not_blocked
|
||||
assert_nothing_raised { yield }
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
def assert_cross_origin_blocked
|
||||
assert_raises(ActionController::InvalidCrossOriginRequest) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def assert_cross_origin_not_blocked
|
||||
assert_not_blocked { yield }
|
||||
end
|
||||
end
|
||||
|
||||
# OK let's get our test on
|
||||
@ -305,13 +375,13 @@ class RequestForgeryProtectionControllerUsingResetSessionTest < ActionController
|
||||
end
|
||||
end
|
||||
|
||||
class NullSessionDummyKeyGenerator
|
||||
def generate_key(secret)
|
||||
'03312270731a2ed0d11ed091c2338a06'
|
||||
class RequestForgeryProtectionControllerUsingNullSessionTest < ActionController::TestCase
|
||||
class NullSessionDummyKeyGenerator
|
||||
def generate_key(secret)
|
||||
'03312270731a2ed0d11ed091c2338a06'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class RequestForgeryProtectionControllerUsingNullSessionTest < ActionController::TestCase
|
||||
def setup
|
||||
@request.env[ActionDispatch::Cookies::GENERATOR_KEY] = NullSessionDummyKeyGenerator.new
|
||||
end
|
||||
@ -375,8 +445,8 @@ def test_should_allow_all_methods_without_token
|
||||
|
||||
class CustomAuthenticityParamControllerTest < ActionController::TestCase
|
||||
def setup
|
||||
ActionController::Base.request_forgery_protection_token = :custom_token_name
|
||||
super
|
||||
ActionController::Base.request_forgery_protection_token = :custom_token_name
|
||||
end
|
||||
|
||||
def teardown
|
||||
|
@ -1833,11 +1833,11 @@ def test_recognize_path
|
||||
assert_equal({:controller => 'foo', :action => 'id_default', :id => 1 }, @routes.recognize_path('/id_default'))
|
||||
assert_equal({:controller => 'foo', :action => 'get_or_post'}, @routes.recognize_path('/get_or_post', :method => :get))
|
||||
assert_equal({:controller => 'foo', :action => 'get_or_post'}, @routes.recognize_path('/get_or_post', :method => :post))
|
||||
assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/get_or_post', :method => :put) }
|
||||
assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/get_or_post', :method => :delete) }
|
||||
assert_raise(ActionController::RoutingError) { @routes.recognize_path('/get_or_post', :method => :put) }
|
||||
assert_raise(ActionController::RoutingError) { @routes.recognize_path('/get_or_post', :method => :delete) }
|
||||
|
||||
assert_equal({:controller => 'posts', :action => 'index', :optional => 'bar'}, @routes.recognize_path('/optional/bar'))
|
||||
assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/optional') }
|
||||
assert_raise(ActionController::RoutingError) { @routes.recognize_path('/optional') }
|
||||
|
||||
assert_equal({:controller => 'posts', :action => 'show', :id => '1', :ws => true}, @routes.recognize_path('/ws/posts/show/1', :method => :get))
|
||||
assert_equal({:controller => 'posts', :action => 'list', :ws => true}, @routes.recognize_path('/ws/posts/list', :method => :get))
|
||||
@ -1916,11 +1916,4 @@ def sort_extras!(extras)
|
||||
end
|
||||
extras
|
||||
end
|
||||
|
||||
def assert_raise(e)
|
||||
result = yield
|
||||
flunk "Did not raise #{e}, but returned #{result.inspect}"
|
||||
rescue e
|
||||
assert true
|
||||
end
|
||||
end
|
||||
|
@ -148,7 +148,7 @@ def test_send_file_headers_with_bad_symbol
|
||||
}
|
||||
|
||||
@controller.headers = {}
|
||||
assert !@controller.send(:send_file_headers!, options)
|
||||
assert_raise(ArgumentError) { @controller.send(:send_file_headers!, options) }
|
||||
end
|
||||
|
||||
def test_send_file_headers_guess_type_from_extension
|
||||
|
@ -31,21 +31,21 @@ class MimeTypeTest < ActiveSupport::TestCase
|
||||
|
||||
test "parse text with trailing star at the beginning" do
|
||||
accept = "text/*, text/html, application/json, multipart/form-data"
|
||||
expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::XML, Mime::YAML, Mime::JSON, Mime::MULTIPART_FORM]
|
||||
expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML, Mime::JSON, Mime::MULTIPART_FORM]
|
||||
parsed = Mime::Type.parse(accept)
|
||||
assert_equal expect, parsed
|
||||
end
|
||||
|
||||
test "parse text with trailing star in the end" do
|
||||
accept = "text/html, application/json, multipart/form-data, text/*"
|
||||
expect = [Mime::HTML, Mime::JSON, Mime::MULTIPART_FORM, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::XML, Mime::YAML]
|
||||
expect = [Mime::HTML, Mime::JSON, Mime::MULTIPART_FORM, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML]
|
||||
parsed = Mime::Type.parse(accept)
|
||||
assert_equal expect, parsed
|
||||
end
|
||||
|
||||
test "parse text with trailing star" do
|
||||
accept = "text/*"
|
||||
expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::XML, Mime::YAML, Mime::JSON]
|
||||
expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML, Mime::JSON]
|
||||
parsed = Mime::Type.parse(accept)
|
||||
assert_equal expect, parsed
|
||||
end
|
||||
|
@ -5,7 +5,7 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
|
||||
|
||||
class FakeEngine
|
||||
def self.routes
|
||||
Object.new
|
||||
@routes ||= ActionDispatch::Routing::RouteSet.new
|
||||
end
|
||||
|
||||
def self.call(env)
|
||||
@ -27,12 +27,23 @@ def self.call(env)
|
||||
scope "/its_a" do
|
||||
mount SprocketsApp, :at => "/sprocket"
|
||||
end
|
||||
|
||||
resources :users do
|
||||
mount FakeEngine, :at => "/fakeengine", :as => :fake_mounted_at_resource
|
||||
end
|
||||
end
|
||||
|
||||
def app
|
||||
Router
|
||||
end
|
||||
|
||||
def test_app_name_is_properly_generated_when_engine_is_mounted_in_resources
|
||||
assert Router.mounted_helpers.method_defined?(:user_fake_mounted_at_resource),
|
||||
"A mounted helper should be defined with a parent's prefix"
|
||||
assert Router.named_routes.routes[:user_fake_mounted_at_resource],
|
||||
"A named route should be defined with a parent's prefix"
|
||||
end
|
||||
|
||||
def test_trailing_slash_is_not_removed_from_path_info
|
||||
get "/sprockets/omg/"
|
||||
assert_equal "/sprockets -- /omg/", response.body
|
||||
|
@ -32,12 +32,18 @@ def self.routes
|
||||
get "/conflicting_url", :to => "inside_engine_generating#conflicting"
|
||||
get "/foo", :to => "never#invoked", :as => :named_helper_that_should_be_invoked_only_in_respond_to_test
|
||||
|
||||
get "/relative_path_redirect", :to => redirect("foo")
|
||||
get "/relative_path_root", :to => redirect("")
|
||||
get "/relative_path_redirect", :to => redirect("foo")
|
||||
get "/relative_option_root", :to => redirect(:path => "")
|
||||
get "/relative_option_redirect", :to => redirect(:path => "foo")
|
||||
get "/relative_custom_root", :to => redirect { |params, request| "" }
|
||||
get "/relative_custom_redirect", :to => redirect { |params, request| "foo" }
|
||||
|
||||
get "/absolute_path_redirect", :to => redirect("/foo")
|
||||
get "/absolute_path_root", :to => redirect("/")
|
||||
get "/absolute_path_redirect", :to => redirect("/foo")
|
||||
get "/absolute_option_root", :to => redirect(:path => "/")
|
||||
get "/absolute_option_redirect", :to => redirect(:path => "/foo")
|
||||
get "/absolute_custom_root", :to => redirect { |params, request| "/" }
|
||||
get "/absolute_custom_redirect", :to => redirect { |params, request| "/foo" }
|
||||
end
|
||||
|
||||
@ -190,46 +196,64 @@ def setup
|
||||
assert_equal "engine", last_response.body
|
||||
end
|
||||
|
||||
test "[ENGINE] relative path root uses SCRIPT_NAME from request" do
|
||||
get "/awesome/blog/relative_path_root"
|
||||
verify_redirect "http://example.org/awesome/blog"
|
||||
end
|
||||
|
||||
test "[ENGINE] relative path redirect uses SCRIPT_NAME from request" do
|
||||
get "/awesome/blog/relative_path_redirect"
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal "http://example.org/awesome/blog/foo", last_response.headers["Location"]
|
||||
assert_equal %(<html><body>You are being <a href="http://example.org/awesome/blog/foo">redirected</a>.</body></html>), last_response.body
|
||||
verify_redirect "http://example.org/awesome/blog/foo"
|
||||
end
|
||||
|
||||
test "[ENGINE] relative option root uses SCRIPT_NAME from request" do
|
||||
get "/awesome/blog/relative_option_root"
|
||||
verify_redirect "http://example.org/awesome/blog"
|
||||
end
|
||||
|
||||
test "[ENGINE] relative option redirect uses SCRIPT_NAME from request" do
|
||||
get "/awesome/blog/relative_option_redirect"
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal "http://example.org/awesome/blog/foo", last_response.headers["Location"]
|
||||
assert_equal %(<html><body>You are being <a href="http://example.org/awesome/blog/foo">redirected</a>.</body></html>), last_response.body
|
||||
verify_redirect "http://example.org/awesome/blog/foo"
|
||||
end
|
||||
|
||||
test "[ENGINE] relative custom root uses SCRIPT_NAME from request" do
|
||||
get "/awesome/blog/relative_custom_root"
|
||||
verify_redirect "http://example.org/awesome/blog"
|
||||
end
|
||||
|
||||
test "[ENGINE] relative custom redirect uses SCRIPT_NAME from request" do
|
||||
get "/awesome/blog/relative_custom_redirect"
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal "http://example.org/awesome/blog/foo", last_response.headers["Location"]
|
||||
assert_equal %(<html><body>You are being <a href="http://example.org/awesome/blog/foo">redirected</a>.</body></html>), last_response.body
|
||||
verify_redirect "http://example.org/awesome/blog/foo"
|
||||
end
|
||||
|
||||
test "[ENGINE] absolute path root doesn't use SCRIPT_NAME from request" do
|
||||
get "/awesome/blog/absolute_path_root"
|
||||
verify_redirect "http://example.org/"
|
||||
end
|
||||
|
||||
test "[ENGINE] absolute path redirect doesn't use SCRIPT_NAME from request" do
|
||||
get "/awesome/blog/absolute_path_redirect"
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal "http://example.org/foo", last_response.headers["Location"]
|
||||
assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
|
||||
verify_redirect "http://example.org/foo"
|
||||
end
|
||||
|
||||
test "[ENGINE] absolute option root doesn't use SCRIPT_NAME from request" do
|
||||
get "/awesome/blog/absolute_option_root"
|
||||
verify_redirect "http://example.org/"
|
||||
end
|
||||
|
||||
test "[ENGINE] absolute option redirect doesn't use SCRIPT_NAME from request" do
|
||||
get "/awesome/blog/absolute_option_redirect"
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal "http://example.org/foo", last_response.headers["Location"]
|
||||
assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
|
||||
verify_redirect "http://example.org/foo"
|
||||
end
|
||||
|
||||
test "[ENGINE] absolute custom root doesn't use SCRIPT_NAME from request" do
|
||||
get "/awesome/blog/absolute_custom_root"
|
||||
verify_redirect "http://example.org/"
|
||||
end
|
||||
|
||||
test "[ENGINE] absolute custom redirect doesn't use SCRIPT_NAME from request" do
|
||||
get "/awesome/blog/absolute_custom_redirect"
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal "http://example.org/foo", last_response.headers["Location"]
|
||||
assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
|
||||
verify_redirect "http://example.org/foo"
|
||||
end
|
||||
|
||||
# Inside Application
|
||||
@ -320,6 +344,17 @@ def setup
|
||||
path = engine_object.polymorphic_url(Post.new, :host => "www.example.com")
|
||||
assert_equal "http://www.example.com/awesome/blog/posts/1", path
|
||||
end
|
||||
|
||||
private
|
||||
def verify_redirect(url, status = 301)
|
||||
assert_equal status, last_response.status
|
||||
assert_equal url, last_response.headers["Location"]
|
||||
assert_equal expected_redirect_body(url), last_response.body
|
||||
end
|
||||
|
||||
def expected_redirect_body(url)
|
||||
%(<html><body>You are being <a href="#{url}">redirected</a>.</body></html>)
|
||||
end
|
||||
end
|
||||
|
||||
class EngineMountedAtRoot < ActionDispatch::IntegrationTest
|
||||
@ -332,12 +367,18 @@ def self.routes
|
||||
routes.draw do
|
||||
get "/posts/:id", :to => "posts#show", :as => :post
|
||||
|
||||
get "/relative_path_redirect", :to => redirect("foo")
|
||||
get "/relative_path_root", :to => redirect("")
|
||||
get "/relative_path_redirect", :to => redirect("foo")
|
||||
get "/relative_option_root", :to => redirect(:path => "")
|
||||
get "/relative_option_redirect", :to => redirect(:path => "foo")
|
||||
get "/relative_custom_root", :to => redirect { |params, request| "" }
|
||||
get "/relative_custom_redirect", :to => redirect { |params, request| "foo" }
|
||||
|
||||
get "/absolute_path_redirect", :to => redirect("/foo")
|
||||
get "/absolute_path_root", :to => redirect("/")
|
||||
get "/absolute_path_redirect", :to => redirect("/foo")
|
||||
get "/absolute_option_root", :to => redirect(:path => "/")
|
||||
get "/absolute_option_redirect", :to => redirect(:path => "/foo")
|
||||
get "/absolute_custom_root", :to => redirect { |params, request| "/" }
|
||||
get "/absolute_custom_redirect", :to => redirect { |params, request| "/foo" }
|
||||
end
|
||||
|
||||
@ -390,46 +431,75 @@ def app
|
||||
assert_equal "/posts/1", last_response.body
|
||||
end
|
||||
|
||||
test "[ENGINE] relative path root uses SCRIPT_NAME from request" do
|
||||
get "/relative_path_root"
|
||||
verify_redirect "http://example.org/"
|
||||
end
|
||||
|
||||
test "[ENGINE] relative path redirect uses SCRIPT_NAME from request" do
|
||||
get "/relative_path_redirect"
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal "http://example.org/foo", last_response.headers["Location"]
|
||||
assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
|
||||
verify_redirect "http://example.org/foo"
|
||||
end
|
||||
|
||||
test "[ENGINE] relative option root uses SCRIPT_NAME from request" do
|
||||
get "/relative_option_root"
|
||||
verify_redirect "http://example.org/"
|
||||
end
|
||||
|
||||
test "[ENGINE] relative option redirect uses SCRIPT_NAME from request" do
|
||||
get "/relative_option_redirect"
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal "http://example.org/foo", last_response.headers["Location"]
|
||||
assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
|
||||
verify_redirect "http://example.org/foo"
|
||||
end
|
||||
|
||||
test "[ENGINE] relative custom root uses SCRIPT_NAME from request" do
|
||||
get "/relative_custom_root"
|
||||
verify_redirect "http://example.org/"
|
||||
end
|
||||
|
||||
test "[ENGINE] relative custom redirect uses SCRIPT_NAME from request" do
|
||||
get "/relative_custom_redirect"
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal "http://example.org/foo", last_response.headers["Location"]
|
||||
assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
|
||||
verify_redirect "http://example.org/foo"
|
||||
end
|
||||
|
||||
test "[ENGINE] absolute path root doesn't use SCRIPT_NAME from request" do
|
||||
get "/absolute_path_root"
|
||||
verify_redirect "http://example.org/"
|
||||
end
|
||||
|
||||
test "[ENGINE] absolute path redirect doesn't use SCRIPT_NAME from request" do
|
||||
get "/absolute_path_redirect"
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal "http://example.org/foo", last_response.headers["Location"]
|
||||
assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
|
||||
verify_redirect "http://example.org/foo"
|
||||
end
|
||||
|
||||
test "[ENGINE] absolute option root doesn't use SCRIPT_NAME from request" do
|
||||
get "/absolute_option_root"
|
||||
verify_redirect "http://example.org/"
|
||||
end
|
||||
|
||||
test "[ENGINE] absolute option redirect doesn't use SCRIPT_NAME from request" do
|
||||
get "/absolute_option_redirect"
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal "http://example.org/foo", last_response.headers["Location"]
|
||||
assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
|
||||
verify_redirect "http://example.org/foo"
|
||||
end
|
||||
|
||||
test "[ENGINE] absolute custom root doesn't use SCRIPT_NAME from request" do
|
||||
get "/absolute_custom_root"
|
||||
verify_redirect "http://example.org/"
|
||||
end
|
||||
|
||||
test "[ENGINE] absolute custom redirect doesn't use SCRIPT_NAME from request" do
|
||||
get "/absolute_custom_redirect"
|
||||
assert_equal 301, last_response.status
|
||||
assert_equal "http://example.org/foo", last_response.headers["Location"]
|
||||
assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
|
||||
verify_redirect "http://example.org/foo"
|
||||
end
|
||||
|
||||
private
|
||||
def verify_redirect(url, status = 301)
|
||||
assert_equal status, last_response.status
|
||||
assert_equal url, last_response.headers["Location"]
|
||||
assert_equal expected_redirect_body(url), last_response.body
|
||||
end
|
||||
|
||||
def expected_redirect_body(url)
|
||||
%(<html><body>You are being <a href="#{url}">redirected</a>.</body></html>)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -11,6 +11,17 @@ def parse
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
class EarlyParse
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
# Trigger a Rack parse so that env caches the query params
|
||||
Rack::Request.new(env).params
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
def teardown
|
||||
TestController.last_query_parameters = nil
|
||||
@ -93,6 +104,21 @@ def test_array_parses_without_nil
|
||||
assert_parses({"action" => ['1']}, "action[]=1&action[]")
|
||||
end
|
||||
|
||||
test "perform_deep_munge" do
|
||||
ActionDispatch::Request::Utils.perform_deep_munge = false
|
||||
begin
|
||||
assert_parses({"action" => nil}, "action")
|
||||
assert_parses({"action" => {"foo" => nil}}, "action[foo]")
|
||||
assert_parses({"action" => {"foo" => {"bar" => nil}}}, "action[foo][bar]")
|
||||
assert_parses({"action" => {"foo" => {"bar" => [nil]}}}, "action[foo][bar][]")
|
||||
assert_parses({"action" => {"foo" => [nil]}}, "action[foo][]")
|
||||
assert_parses({"action" => {"foo" => [{"bar" => nil}]}}, "action[foo][][bar]")
|
||||
assert_parses({"action" => ['1',nil]}, "action[]=1&action[]")
|
||||
ensure
|
||||
ActionDispatch::Request::Utils.perform_deep_munge = true
|
||||
end
|
||||
end
|
||||
|
||||
test "query string with empty key" do
|
||||
assert_parses(
|
||||
{ "action" => "create_customer", "full_name" => "David Heinemeier Hansson" },
|
||||
@ -131,6 +157,10 @@ def assert_parses(expected, actual)
|
||||
set.draw do
|
||||
get ':action', :to => ::QueryStringParsingTest::TestController
|
||||
end
|
||||
@app = self.class.build_app(set) do |middleware|
|
||||
middleware.use(EarlyParse)
|
||||
end
|
||||
|
||||
|
||||
get "/parse", actual
|
||||
assert_response :ok
|
||||
|
@ -68,13 +68,12 @@ def test_fetch
|
||||
assert_equal '1', session.fetch(:one)
|
||||
|
||||
assert_equal '2', session.fetch(:two, '2')
|
||||
assert_equal '2', session.fetch(:two)
|
||||
assert_nil session.fetch(:two, nil)
|
||||
|
||||
assert_equal 'three', session.fetch(:three) {|el| el.to_s }
|
||||
assert_equal 'three', session.fetch(:three)
|
||||
|
||||
assert_raise KeyError do
|
||||
session.fetch(:four)
|
||||
session.fetch(:three)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -93,6 +93,14 @@ def url_for(options = {})
|
||||
assert_equal '1.1.1.1', request.remote_ip
|
||||
end
|
||||
|
||||
test "remote ip spoof protection ignores private addresses" do
|
||||
request = stub_request 'HTTP_X_FORWARDED_FOR' => '172.17.19.51',
|
||||
'HTTP_CLIENT_IP' => '172.17.19.51',
|
||||
'REMOTE_ADDR' => '1.1.1.1',
|
||||
'HTTP_X_BLUECOAT_VIA' => 'de462e07a2db325e'
|
||||
assert_equal '1.1.1.1', request.remote_ip
|
||||
end
|
||||
|
||||
test "remote ip v6" do
|
||||
request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334'
|
||||
assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
|
||||
@ -598,7 +606,7 @@ def url_for(options = {})
|
||||
'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
|
||||
request.expects(:parameters).at_least_once.returns({})
|
||||
assert_equal [Mime::JS], request.formats
|
||||
|
||||
|
||||
request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8',
|
||||
'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
|
||||
request.expects(:parameters).at_least_once.returns({})
|
||||
@ -616,10 +624,10 @@ def url_for(options = {})
|
||||
test "format is not nil with unknown format" do
|
||||
request = stub_request
|
||||
request.expects(:parameters).at_least_once.returns({ format: :hello })
|
||||
assert_equal request.format.nil?, true
|
||||
assert_equal request.format.html?, false
|
||||
assert_equal request.format.xml?, false
|
||||
assert_equal request.format.json?, false
|
||||
assert request.format.nil?
|
||||
assert_not request.format.html?
|
||||
assert_not request.format.xml?
|
||||
assert_not request.format.json?
|
||||
end
|
||||
|
||||
test "formats with xhr request" do
|
||||
@ -836,6 +844,19 @@ def url_for(options = {})
|
||||
end
|
||||
end
|
||||
|
||||
test "setting variant" do
|
||||
request = stub_request
|
||||
request.variant = :mobile
|
||||
assert_equal :mobile, request.variant
|
||||
end
|
||||
|
||||
test "setting variant with non symbol value" do
|
||||
request = stub_request
|
||||
assert_raise ArgumentError do
|
||||
request.variant = "mobile"
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def stub_request(env = {})
|
||||
|
@ -203,6 +203,18 @@ def test_rake_routes_dont_show_app_mounted_in_assets_prefix
|
||||
assert_no_match(/\/sprockets/, output.first)
|
||||
end
|
||||
|
||||
def test_rake_routes_shows_route_defined_in_under_assets_prefix
|
||||
output = draw do
|
||||
scope '/sprockets' do
|
||||
get '/foo' => 'foo#bar'
|
||||
end
|
||||
end
|
||||
assert_equal [
|
||||
"Prefix Verb URI Pattern Controller#Action",
|
||||
" foo GET /sprockets/foo(.:format) foo#bar"
|
||||
], output
|
||||
end
|
||||
|
||||
def test_redirect
|
||||
output = draw do
|
||||
get "/foo" => redirect("/foo/bar"), :constraints => { :subdomain => "admin" }
|
||||
|
@ -2864,6 +2864,36 @@ def test_action_from_path_is_not_frozen
|
||||
assert !@request.params[:action].frozen?
|
||||
end
|
||||
|
||||
def test_multiple_positional_args_with_the_same_name
|
||||
draw do
|
||||
get '/downloads/:id/:id.tar' => 'downloads#show', as: :download, format: false
|
||||
end
|
||||
|
||||
expected_params = {
|
||||
controller: 'downloads',
|
||||
action: 'show',
|
||||
id: '1'
|
||||
}
|
||||
|
||||
get '/downloads/1/1.tar'
|
||||
assert_equal 'downloads#show', @response.body
|
||||
assert_equal expected_params, @request.symbolized_path_parameters
|
||||
assert_equal '/downloads/1/1.tar', download_path('1')
|
||||
assert_equal '/downloads/1/1.tar', download_path('1', '1')
|
||||
end
|
||||
|
||||
def test_absolute_controller_namespace
|
||||
draw do
|
||||
namespace :foo do
|
||||
get '/', to: '/bar#index', as: 'root'
|
||||
end
|
||||
end
|
||||
|
||||
get '/foo'
|
||||
assert_equal 'bar#index', @response.body
|
||||
assert_equal '/foo', foo_root_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def draw(&block)
|
||||
@ -3235,7 +3265,9 @@ class TestRedirectInterpolation < ActionDispatch::IntegrationTest
|
||||
|
||||
get "/foo/:id" => redirect("/foo/bar/%{id}")
|
||||
get "/bar/:id" => redirect(:path => "/foo/bar/%{id}")
|
||||
get "/baz/:id" => redirect("/baz?id=%{id}&foo=?&bar=1#id-%{id}")
|
||||
get "/foo/bar/:id" => ok
|
||||
get "/baz" => ok
|
||||
end
|
||||
end
|
||||
|
||||
@ -3251,6 +3283,14 @@ def app; Routes end
|
||||
verify_redirect "http://www.example.com/foo/bar/1%3E"
|
||||
end
|
||||
|
||||
test "path redirect escapes interpolated parameters correctly" do
|
||||
get "/foo/1%201"
|
||||
verify_redirect "http://www.example.com/foo/bar/1%201"
|
||||
|
||||
get "/baz/1%201"
|
||||
verify_redirect "http://www.example.com/baz?id=1+1&foo=?&bar=1#id-1%201"
|
||||
end
|
||||
|
||||
private
|
||||
def verify_redirect(url, status=301)
|
||||
assert_equal status, @response.status
|
||||
@ -3317,6 +3357,8 @@ class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest
|
||||
ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
|
||||
get '/foo' => ok, as: :foo
|
||||
get '/post(/:action(/:id))' => ok, as: :posts
|
||||
get '/:foo/:foo_type/bars/:id' => ok, as: :bar
|
||||
get '/projects/:id.:format' => ok, as: :project
|
||||
end
|
||||
end
|
||||
|
||||
@ -3339,6 +3381,16 @@ def app; Routes end
|
||||
assert_equal '/post', Routes.url_helpers.posts_path
|
||||
assert_equal '/post', posts_path
|
||||
end
|
||||
|
||||
test 'segments with same prefix are replaced correctly' do
|
||||
assert_equal '/foo/baz/bars/1', Routes.url_helpers.bar_path('foo', 'baz', '1')
|
||||
assert_equal '/foo/baz/bars/1', bar_path('foo', 'baz', '1')
|
||||
end
|
||||
|
||||
test 'segments separated with a period are replaced correctly' do
|
||||
assert_equal '/projects/1.json', Routes.url_helpers.project_path(1, :json)
|
||||
assert_equal '/projects/1.json', project_path(1, :json)
|
||||
end
|
||||
end
|
||||
|
||||
class TestNamedRouteUrlHelpers < ActionDispatch::IntegrationTest
|
||||
@ -3686,3 +3738,28 @@ def test_redirect_doesnt_match_unnamed_route
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class TestUrlGenerationErrors < ActionDispatch::IntegrationTest
|
||||
Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
|
||||
app.draw do
|
||||
get "/products/:id" => 'products#show', :as => :product
|
||||
end
|
||||
end
|
||||
|
||||
def app; Routes end
|
||||
|
||||
include Routes.url_helpers
|
||||
|
||||
test "url helpers raise a helpful error message whem generation fails" do
|
||||
url, missing = { action: 'show', controller: 'products', id: nil }, [:id]
|
||||
message = "No route matches #{url.inspect} missing required keys: #{missing.inspect}"
|
||||
|
||||
# Optimized url helper
|
||||
error = assert_raises(ActionController::UrlGenerationError){ product_path(nil) }
|
||||
assert_equal message, error.message
|
||||
|
||||
# Non-optimized url helper
|
||||
error = assert_raises(ActionController::UrlGenerationError, message){ product_path(id: nil) }
|
||||
assert_equal message, error.message
|
||||
end
|
||||
end
|
||||
|
@ -137,7 +137,7 @@ def get(path)
|
||||
end
|
||||
|
||||
def with_static_file(file)
|
||||
path = "#{FIXTURE_LOAD_PATH}/public" + file
|
||||
path = "#{FIXTURE_LOAD_PATH}/#{public_path}" + file
|
||||
File.open(path, "wb+") { |f| f.write(file) }
|
||||
yield file
|
||||
ensure
|
||||
@ -149,11 +149,24 @@ class StaticTest < ActiveSupport::TestCase
|
||||
DummyApp = lambda { |env|
|
||||
[200, {"Content-Type" => "text/plain"}, ["Hello, World!"]]
|
||||
}
|
||||
App = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/public", "public, max-age=60")
|
||||
|
||||
def setup
|
||||
@app = App
|
||||
@app = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/public", "public, max-age=60")
|
||||
end
|
||||
|
||||
def public_path
|
||||
"public"
|
||||
end
|
||||
|
||||
include StaticTests
|
||||
end
|
||||
|
||||
class StaticEncodingTest < StaticTest
|
||||
def setup
|
||||
@app = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/公共", "public, max-age=60")
|
||||
end
|
||||
|
||||
def public_path
|
||||
"公共"
|
||||
end
|
||||
end
|
||||
|
@ -66,7 +66,7 @@ def app
|
||||
assert_equal "http://www.example.com:8080/foo", foo_url(host: "www.example.com:8080", protocol: "http://")
|
||||
end
|
||||
|
||||
test "port option overides the host" do
|
||||
test "port option overrides the host" do
|
||||
assert_equal "http://www.example.com:8080/foo", foo_url(host: "www.example.com:8443", protocol: "http://", port: 8080)
|
||||
end
|
||||
|
||||
|
1
actionpack/test/fixtures/localized/hello_world.it.erb
vendored
Normal file
1
actionpack/test/fixtures/localized/hello_world.it.erb
vendored
Normal file
@ -0,0 +1 @@
|
||||
Ciao Mondo
|
1
actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+phablet.erb
vendored
Normal file
1
actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+phablet.erb
vendored
Normal file
@ -0,0 +1 @@
|
||||
phablet
|
1
actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+tablet.erb
vendored
Normal file
1
actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+tablet.erb
vendored
Normal file
@ -0,0 +1 @@
|
||||
tablet
|
1
actionpack/test/fixtures/respond_to/variant_inline_syntax_without_block.html+phone.erb
vendored
Normal file
1
actionpack/test/fixtures/respond_to/variant_inline_syntax_without_block.html+phone.erb
vendored
Normal file
@ -0,0 +1 @@
|
||||
phone
|
1
actionpack/test/fixtures/respond_to/variant_plus_none_for_format.html.erb
vendored
Normal file
1
actionpack/test/fixtures/respond_to/variant_plus_none_for_format.html.erb
vendored
Normal file
@ -0,0 +1 @@
|
||||
none
|
1
actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb
vendored
Normal file
1
actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb
vendored
Normal file
@ -0,0 +1 @@
|
||||
mobile
|
1
actionpack/test/fixtures/公共/foo/bar.html
vendored
Normal file
1
actionpack/test/fixtures/公共/foo/bar.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
/foo/bar.html
|
3
actionpack/test/fixtures/公共/foo/baz.css
vendored
Normal file
3
actionpack/test/fixtures/公共/foo/baz.css
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
body {
|
||||
background: #000;
|
||||
}
|
1
actionpack/test/fixtures/公共/foo/index.html
vendored
Normal file
1
actionpack/test/fixtures/公共/foo/index.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
/foo/index.html
|
1
actionpack/test/fixtures/公共/foo/こんにちは.html
vendored
Normal file
1
actionpack/test/fixtures/公共/foo/こんにちは.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
means hello in Japanese
|
1
actionpack/test/fixtures/公共/index.html
vendored
Normal file
1
actionpack/test/fixtures/公共/index.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
/index.html
|
@ -1,3 +1,73 @@
|
||||
* Use `display:none` instead of `display:inline` for hidden fields
|
||||
|
||||
Fixes #6403
|
||||
|
||||
*Gaelian Ditchburn*
|
||||
|
||||
* The `video_tag` helper accepts a number as `:size`
|
||||
|
||||
The `:size` option of the `video_tag` helper now can be specified
|
||||
with a stringified number. The `width` and `height` attributes of
|
||||
the generated tag will be the same.
|
||||
|
||||
*Kuldeep Aggarwal*
|
||||
|
||||
* A Cycle object should accept an array and cycle through it as it would with a set of
|
||||
comma-separated objects.
|
||||
|
||||
arr = [1,2,3]
|
||||
cycle(arr) # => '1'
|
||||
cycle(arr) # => '2'
|
||||
cycle(arr) # => '3'
|
||||
|
||||
Previously, it would return the array as a string, because it took the array as a
|
||||
single object:
|
||||
|
||||
arr = [1,2,3]
|
||||
cycle(arr) # => '[1,2,3]'
|
||||
cycle(arr) # => '[1,2,3]'
|
||||
cycle(arr) # => '[1,2,3]'
|
||||
|
||||
*Kristian Freeman*
|
||||
|
||||
* Label tags generated by collection helpers only inherit the `:index` and
|
||||
`:namespace` from the input, because only these attributes modifies the
|
||||
`for` attribute of the label. Also, the input attributes don't have
|
||||
precedence over the label attributes anymore.
|
||||
|
||||
Before:
|
||||
|
||||
collection = [[1, true, { class: 'foo' }]]
|
||||
f.collection_check_boxes :options, collection, :second, :first do |b|
|
||||
b.label(class: 'my_custom_class')
|
||||
end
|
||||
|
||||
# => <label class="foo" for="user_active_true">1</label>
|
||||
|
||||
After:
|
||||
|
||||
collection = [[1, true, { class: 'foo' }]]
|
||||
f.collection_check_boxes :options, collection, :second, :first do |b|
|
||||
b.label(class: 'my_custom_class')
|
||||
end
|
||||
|
||||
# => <label class="my_custom_class" for="user_active_true">1</label>
|
||||
|
||||
*Andriel Nuernberg*
|
||||
|
||||
* Fixed a long-standing bug in `json_escape` that causes quotation marks to be stripped.
|
||||
This method also escapes the \u2028 and \u2029 unicode newline characters which are
|
||||
treated as \n in JavaScript. This matches the behaviour of the AS::JSON encoder. (The
|
||||
original change in the encoder was introduced in #10534.)
|
||||
|
||||
*Godfrey Chan*
|
||||
|
||||
* `ActionView::MissingTemplate` includes underscore when raised for a partial.
|
||||
|
||||
Fixes #13002.
|
||||
|
||||
*Yves Senn*
|
||||
|
||||
* Use `set_backtrace` instead of instance variable `@backtrace` in ActionView exceptions
|
||||
|
||||
*Shimpei Makimoto*
|
||||
|
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2004-2013 David Heinemeier Hansson
|
||||
Copyright (c) 2004-2014 David Heinemeier Hansson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
@ -20,10 +20,10 @@
|
||||
s.requirements << 'none'
|
||||
|
||||
s.add_dependency 'activesupport', version
|
||||
s.add_dependency 'activemodel', version
|
||||
|
||||
s.add_dependency 'builder', '~> 3.1.0'
|
||||
s.add_dependency 'builder', '~> 3.1'
|
||||
s.add_dependency 'erubis', '~> 2.7.0'
|
||||
|
||||
s.add_development_dependency 'actionpack', version
|
||||
s.add_development_dependency 'actionpack', version
|
||||
s.add_development_dependency 'activemodel', version
|
||||
end
|
||||
|
@ -1,5 +1,5 @@
|
||||
#--
|
||||
# Copyright (c) 2004-2013 David Heinemeier Hansson
|
||||
# Copyright (c) 2004-2014 David Heinemeier Hansson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
@ -23,6 +23,7 @@
|
||||
|
||||
require 'active_support'
|
||||
require 'active_support/rails'
|
||||
require 'action_view/version'
|
||||
|
||||
module ActionView
|
||||
extend ActiveSupport::Autoload
|
||||
@ -84,6 +85,7 @@ module ActionView
|
||||
|
||||
def self.eager_load!
|
||||
super
|
||||
ActionView::Helpers.eager_load!
|
||||
ActionView::Template.eager_load!
|
||||
HTML.eager_load!
|
||||
end
|
||||
|
@ -1,5 +1,5 @@
|
||||
require 'active_support/core_ext/module/attr_internal'
|
||||
require 'active_support/core_ext/class/attribute_accessors'
|
||||
require 'active_support/core_ext/module/attribute_accessors'
|
||||
require 'active_support/ordered_options'
|
||||
require 'action_view/log_subscriber'
|
||||
require 'action_view/helpers'
|
||||
|
@ -27,6 +27,12 @@ module Helpers #:nodoc:
|
||||
autoload :TextHelper
|
||||
autoload :TranslationHelper
|
||||
autoload :UrlHelper
|
||||
autoload :Tags
|
||||
|
||||
def self.eager_load!
|
||||
super
|
||||
Tags.eager_load!
|
||||
end
|
||||
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
require 'active_support/core_ext/class/attribute_accessors'
|
||||
require 'active_support/core_ext/module/attribute_accessors'
|
||||
require 'active_support/core_ext/enumerable'
|
||||
|
||||
module ActionView
|
||||
|
@ -103,7 +103,7 @@ def stylesheet_link_tag(*sources)
|
||||
}.join("\n").html_safe
|
||||
end
|
||||
|
||||
# Returns a link tag that browsers and news readers can use to auto-detect
|
||||
# Returns a link tag that browsers and feed readers can use to auto-detect
|
||||
# an RSS or Atom feed. The +type+ can either be <tt>:rss</tt> (default) or
|
||||
# <tt>:atom</tt>. Control the link options in url_for format using the
|
||||
# +url_options+. You can modify the LINK tag itself in +tag_options+.
|
||||
@ -176,7 +176,7 @@ def favicon_link_tag(source='favicon.ico', options={})
|
||||
# ==== Options
|
||||
#
|
||||
# You can add HTML attributes using the +options+. The +options+ supports
|
||||
# three additional keys for convenience and conformance:
|
||||
# two additional keys for convenience and conformance:
|
||||
#
|
||||
# * <tt>:alt</tt> - If no alt text is given, the file name part of the
|
||||
# +source+ is used (capitalized and without the extension)
|
||||
@ -207,14 +207,7 @@ def image_tag(source, options={})
|
||||
options[:alt] = options.fetch(:alt){ image_alt(src) }
|
||||
end
|
||||
|
||||
if size = options.delete(:size)
|
||||
if size =~ %r{\A\d+x\d+\z}
|
||||
options[:width], options[:height] = size.split('x')
|
||||
elsif size =~ %r{\A\d+\z}
|
||||
options[:width] = options[:height] = size
|
||||
end
|
||||
end
|
||||
|
||||
options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
|
||||
tag("img", options)
|
||||
end
|
||||
|
||||
@ -251,9 +244,9 @@ def image_alt(src)
|
||||
#
|
||||
# * <tt>:poster</tt> - Set an image (like a screenshot) to be shown
|
||||
# before the video loads. The path is calculated like the +src+ of +image_tag+.
|
||||
# * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
|
||||
# width="30" and height="45". <tt>:size</tt> will be ignored if the
|
||||
# value is not in the correct format.
|
||||
# * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes
|
||||
# width="30" and height="45", and "50" becomes width="50" and height="50".
|
||||
# <tt>:size</tt> will be ignored if the value is not in the correct format.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
@ -267,6 +260,8 @@ def image_alt(src)
|
||||
# # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" />
|
||||
# video_tag("/trailers/hd.avi", size: "16x16")
|
||||
# # => <video src="/trailers/hd.avi" width="16" height="16" />
|
||||
# video_tag("/trailers/hd.avi", size: "16")
|
||||
# # => <video height="16" src="/trailers/hd.avi" width="16" />
|
||||
# video_tag("/trailers/hd.avi", height: '32', width: '32')
|
||||
# # => <video height="32" src="/trailers/hd.avi" width="32" />
|
||||
# video_tag("trailer.ogg", "trailer.flv")
|
||||
@ -278,10 +273,7 @@ def image_alt(src)
|
||||
def video_tag(*sources)
|
||||
multiple_sources_tag('video', sources) do |options|
|
||||
options[:poster] = path_to_image(options[:poster]) if options[:poster]
|
||||
|
||||
if size = options.delete(:size)
|
||||
options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
|
||||
end
|
||||
options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
|
||||
end
|
||||
end
|
||||
|
||||
@ -317,6 +309,14 @@ def multiple_sources_tag(type, sources)
|
||||
content_tag(type, nil, options)
|
||||
end
|
||||
end
|
||||
|
||||
def extract_dimensions(size)
|
||||
if size =~ %r{\A\d+x\d+\z}
|
||||
size.split('x')
|
||||
elsif size =~ %r{\A\d+\z}
|
||||
[size, size]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user