Transform actionpack documentation to Markdown

This commit is contained in:
Rafael Mendonça França 2024-01-25 19:51:51 +00:00
parent 3079e8b0f8
commit d6bf4de7dc
No known key found for this signature in database
GPG Key ID: FC23B6D0F1EEE948
151 changed files with 5573 additions and 5084 deletions

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "action_pack"
require "active_support"
require "active_support/rails"

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module AbstractController
module AssetPaths # :nodoc:
extend ActiveSupport::Concern

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "abstract_controller/error"
require "active_support/configurable"
require "active_support/descendants_tracker"
@ -26,12 +28,12 @@ def corrections # :nodoc:
end
end
# = Abstract Controller \Base
# # Abstract Controller Base
#
# AbstractController::Base is a low-level API. Nobody should be
# using it directly, and subclasses (like ActionController::Base) are
# expected to provide their own +render+ method, since rendering means
# different things depending on the context.
# AbstractController::Base is a low-level API. Nobody should be using it
# directly, and subclasses (like ActionController::Base) are expected to provide
# their own `render` method, since rendering means different things depending on
# the context.
class Base
##
# Returns the body of the HTTP response sent by the controller.
@ -52,27 +54,26 @@ class << self
attr_reader :abstract
alias_method :abstract?, :abstract
# Define a controller as abstract. See internal_methods for more
# details.
# Define a controller as abstract. See internal_methods for more details.
def abstract!
@abstract = true
end
def inherited(klass) # :nodoc:
# Define the abstract ivar on subclasses so that we don't get
# uninitialized ivar warnings
# Define the abstract ivar on subclasses so that we don't get uninitialized ivar
# warnings
unless klass.instance_variable_defined?(:@abstract)
klass.instance_variable_set(:@abstract, false)
end
super
end
# A list of all internal methods for a controller. This finds the first
# abstract superclass of a controller, and gets a list of all public
# instance methods on that abstract class. Public instance methods of
# a controller would normally be considered action methods, so methods
# declared on abstract classes are being removed.
# (ActionController::Metal and ActionController::Base are defined as abstract)
# A list of all internal methods for a controller. This finds the first abstract
# superclass of a controller, and gets a list of all public instance methods on
# that abstract class. Public instance methods of a controller would normally be
# considered action methods, so methods declared on abstract classes are being
# removed. (ActionController::Metal and ActionController::Base are defined as
# abstract)
def internal_methods
controller = self
methods = []
@ -85,18 +86,18 @@ def internal_methods
controller.public_instance_methods(true) - methods
end
# A list of method names that should be considered actions. This
# includes all public instance methods on a controller, less
# any internal methods (see internal_methods), adding back in
# any methods that are internal, but still exist on the class
# itself.
# A list of method names that should be considered actions. This includes all
# public instance methods on a controller, less any internal methods (see
# internal_methods), adding back in any methods that are internal, but still
# exist on the class itself.
#
# #### Returns
# * `Set` - A set of all methods that should be considered actions.
#
# ==== Returns
# * <tt>Set</tt> - A set of all methods that should be considered actions.
def action_methods
@action_methods ||= begin
# All public instance methods of this class, including ancestors
# except for public instance methods of Base and its ancestors.
# All public instance methods of this class, including ancestors except for
# public instance methods of Base and its ancestors.
methods = public_instance_methods(true) - internal_methods
# Be sure to include shadowed public instance methods of this class.
methods.concat(public_instance_methods(false))
@ -105,23 +106,24 @@ def action_methods
end
end
# action_methods are cached and there is sometimes a need to refresh
# them. ::clear_action_methods! allows you to do that, so next time
# you run action_methods, they will be recalculated.
# action_methods are cached and there is sometimes a need to refresh them.
# ::clear_action_methods! allows you to do that, so next time you run
# action_methods, they will be recalculated.
def clear_action_methods!
@action_methods = nil
end
# Returns the full controller name, underscored, without the ending Controller.
#
# class MyApp::MyPostsController < AbstractController::Base
# class MyApp::MyPostsController < AbstractController::Base
#
# end
# end
#
# MyApp::MyPostsController.controller_path # => "my_app/my_posts"
# MyApp::MyPostsController.controller_path # => "my_app/my_posts"
#
# #### Returns
# * `String`
#
# ==== Returns
# * <tt>String</tt>
def controller_path
@controller_path ||= name.delete_suffix("Controller").underscore unless anonymous?
end
@ -142,12 +144,13 @@ def eager_load! # :nodoc:
# Calls the action going through the entire Action Dispatch stack.
#
# The actual method that is called is determined by calling
# #method_for_action. If no method can handle the action, then an
# AbstractController::ActionNotFound error is raised.
# The actual method that is called is determined by calling #method_for_action.
# If no method can handle the action, then an AbstractController::ActionNotFound
# error is raised.
#
# #### Returns
# * `self`
#
# ==== Returns
# * <tt>self</tt>
def process(action, ...)
@_action_name = action.to_s
@ -170,31 +173,30 @@ def action_methods
self.class.action_methods
end
# Returns true if a method for the action is available and
# can be dispatched, false otherwise.
# Returns true if a method for the action is available and can be dispatched,
# false otherwise.
#
# Notice that <tt>action_methods.include?("foo")</tt> may return
# false and <tt>available_action?("foo")</tt> returns true because
# this method considers actions that are also available
# through other means, for example, implicit render ones.
# Notice that `action_methods.include?("foo")` may return false and
# `available_action?("foo")` returns true because this method considers actions
# that are also available through other means, for example, implicit render
# ones.
#
# #### Parameters
# * `action_name` - The name of an action to be tested
#
# ==== Parameters
# * <tt>action_name</tt> - The name of an action to be tested
def available_action?(action_name)
_find_action_name(action_name)
end
# Tests if a response body is set. Used to determine if the
# +process_action+ callback needs to be terminated in
# AbstractController::Callbacks.
# Tests if a response body is set. Used to determine if the `process_action`
# callback needs to be terminated in AbstractController::Callbacks.
def performed?
response_body
end
# Returns true if the given controller is capable of rendering
# a path. A subclass of +AbstractController::Base+
# may return false. An Email controller for example does not
# support paths, only full URLs.
# Returns true if the given controller is capable of rendering a path. A
# subclass of `AbstractController::Base` may return false. An Email controller
# for example does not support paths, only full URLs.
def self.supports_path?
true
end
@ -204,80 +206,83 @@ def inspect # :nodoc:
end
private
# Returns true if the name can be considered an action because
# it has a method defined in the controller.
# Returns true if the name can be considered an action because it has a method
# defined in the controller.
#
# #### Parameters
# * `name` - The name of an action to be tested
#
# ==== Parameters
# * <tt>name</tt> - The name of an action to be tested
def action_method?(name)
self.class.action_methods.include?(name)
end
# Call the action. Override this in a subclass to modify the
# behavior around processing an action. This, and not #process,
# is the intended way to override action dispatching.
# Call the action. Override this in a subclass to modify the behavior around
# processing an action. This, and not #process, is the intended way to override
# action dispatching.
#
# Notice that the first argument is the method to be dispatched
# which is *not* necessarily the same as the action name.
# Notice that the first argument is the method to be dispatched which is **not**
# necessarily the same as the action name.
def process_action(...)
send_action(...)
end
# Actually call the method associated with the action. Override
# this method if you wish to change how action methods are called,
# not to add additional behavior around it. For example, you would
# override #send_action if you want to inject arguments into the
# method.
# Actually call the method associated with the action. Override this method if
# you wish to change how action methods are called, not to add additional
# behavior around it. For example, you would override #send_action if you want
# to inject arguments into the method.
alias send_action send
# If the action name was not found, but a method called "action_missing"
# was found, #method_for_action will return "_handle_action_missing".
# This method calls #action_missing with the current action name.
# If the action name was not found, but a method called "action_missing" was
# found, #method_for_action will return "_handle_action_missing". This method
# calls #action_missing with the current action name.
def _handle_action_missing(*args)
action_missing(@_action_name, *args)
end
# Takes an action name and returns the name of the method that will
# handle the action.
# Takes an action name and returns the name of the method that will handle the
# action.
#
# It checks if the action name is valid and returns false otherwise.
#
# See method_for_action for more information.
#
# ==== Parameters
# * <tt>action_name</tt> - An action name to find a method name for
# #### Parameters
# * `action_name` - An action name to find a method name for
#
# ==== Returns
# * <tt>string</tt> - The name of the method that handles the action
# * false - No valid method name could be found.
# Raise +AbstractController::ActionNotFound+.
#
# #### Returns
# * `string` - The name of the method that handles the action
# * false - No valid method name could be found.
#
# Raise `AbstractController::ActionNotFound`.
def _find_action_name(action_name)
_valid_action_name?(action_name) && method_for_action(action_name)
end
# Takes an action name and returns the name of the method that will
# handle the action. In normal cases, this method returns the same
# name as it receives. By default, if #method_for_action receives
# a name that is not an action, it will look for an #action_missing
# method and return "_handle_action_missing" if one is found.
# Takes an action name and returns the name of the method that will handle the
# action. In normal cases, this method returns the same name as it receives. By
# default, if #method_for_action receives a name that is not an action, it will
# look for an #action_missing method and return "_handle_action_missing" if one
# is found.
#
# Subclasses may override this method to add additional conditions
# that should be considered an action. For instance, an HTTP controller
# with a template matching the action name is considered to exist.
# Subclasses may override this method to add additional conditions that should
# be considered an action. For instance, an HTTP controller with a template
# matching the action name is considered to exist.
#
# If you override this method to handle additional cases, you may
# also provide a method (like +_handle_method_missing+) to handle
# the case.
# If you override this method to handle additional cases, you may also provide a
# method (like `_handle_method_missing`) to handle the case.
#
# If none of these conditions are true, and +method_for_action+
# returns +nil+, an +AbstractController::ActionNotFound+ exception will be raised.
# If none of these conditions are true, and `method_for_action` returns `nil`,
# an `AbstractController::ActionNotFound` exception will be raised.
#
# ==== Parameters
# * <tt>action_name</tt> - An action name to find a method name for
# #### Parameters
# * `action_name` - An action name to find a method name for
#
#
# #### Returns
# * `string` - The name of the method that handles the action
# * `nil` - No method name could be found.
#
# ==== Returns
# * <tt>string</tt> - The name of the method that handles the action
# * <tt>nil</tt> - No method name could be found.
def method_for_action(action_name)
if action_method?(action_name)
action_name

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module AbstractController
module Caching
extend ActiveSupport::Concern

@ -1,22 +1,23 @@
# frozen_string_literal: true
# :markup: markdown
module AbstractController
module Caching
# = Abstract Controller Caching \Fragments
# # Abstract Controller Caching Fragments
#
# Fragment caching is used for caching various blocks within
# views without caching the entire action as a whole. This is
# useful when certain elements of an action change frequently or
# depend on complicated state while other parts rarely change or
# can be shared amongst multiple parties. The caching is done using
# the +cache+ helper available in the Action View. See
# Fragment caching is used for caching various blocks within views without
# caching the entire action as a whole. This is useful when certain elements of
# an action change frequently or depend on complicated state while other parts
# rarely change or can be shared amongst multiple parties. The caching is done
# using the `cache` helper available in the Action View. See
# ActionView::Helpers::CacheHelper for more information.
#
# While it's strongly recommended that you use key-based cache
# expiration (see links in CacheHelper for more information),
# it is also possible to manually expire caches. For example:
# While it's strongly recommended that you use key-based cache expiration (see
# links in CacheHelper for more information), it is also possible to manually
# expire caches. For example:
#
# expire_fragment('name_of_cache')
# expire_fragment('name_of_cache')
module Fragments
extend ActiveSupport::Concern
@ -35,38 +36,35 @@ module Fragments
end
module ClassMethods
# Allows you to specify controller-wide key prefixes for
# cache fragments. Pass either a constant +value+, or a block
# which computes a value each time a cache key is generated.
# Allows you to specify controller-wide key prefixes for cache fragments. Pass
# either a constant `value`, or a block which computes a value each time a cache
# key is generated.
#
# For example, you may want to prefix all fragment cache keys
# with a global version identifier, so you can easily
# invalidate all caches.
# For example, you may want to prefix all fragment cache keys with a global
# version identifier, so you can easily invalidate all caches.
#
# class ApplicationController
# fragment_cache_key "v1"
# end
#
# When it's time to invalidate all fragments, simply change
# the string constant. Or, progressively roll out the cache
# invalidation using a computed value:
#
# class ApplicationController
# fragment_cache_key do
# @account.id.odd? ? "v1" : "v2"
# class ApplicationController
# fragment_cache_key "v1"
# end
#
# When it's time to invalidate all fragments, simply change the string constant.
# Or, progressively roll out the cache invalidation using a computed value:
#
# class ApplicationController
# fragment_cache_key do
# @account.id.odd? ? "v1" : "v2"
# end
# end
# end
def fragment_cache_key(value = nil, &key)
self.fragment_cache_keys += [key || -> { value }]
end
end
# Given a key (as described in +expire_fragment+), returns
# a key array suitable for use in reading, writing, or expiring a
# cached fragment. All keys begin with <tt>:views</tt>,
# followed by <tt>ENV["RAILS_CACHE_ID"]</tt> or <tt>ENV["RAILS_APP_VERSION"]</tt> if set,
# followed by any controller-wide key prefix values, ending
# with the specified +key+ value.
# Given a key (as described in `expire_fragment`), returns a key array suitable
# for use in reading, writing, or expiring a cached fragment. All keys begin
# with `:views`, followed by `ENV["RAILS_CACHE_ID"]` or
# `ENV["RAILS_APP_VERSION"]` if set, followed by any controller-wide key prefix
# values, ending with the specified `key` value.
def combined_fragment_cache_key(key)
head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
@ -77,8 +75,8 @@ def combined_fragment_cache_key(key)
cache_key
end
# Writes +content+ to the location signified by
# +key+ (see +expire_fragment+ for acceptable formats).
# Writes `content` to the location signified by `key` (see `expire_fragment` for
# acceptable formats).
def write_fragment(key, content, options = nil)
return content unless cache_configured?
@ -90,8 +88,8 @@ def write_fragment(key, content, options = nil)
content
end
# Reads a cached fragment from the location signified by +key+
# (see +expire_fragment+ for acceptable formats).
# Reads a cached fragment from the location signified by `key` (see
# `expire_fragment` for acceptable formats).
def read_fragment(key, options = nil)
return unless cache_configured?
@ -102,8 +100,8 @@ def read_fragment(key, options = nil)
end
end
# Check if a cached fragment from the location signified by
# +key+ exists (see +expire_fragment+ for acceptable formats).
# Check if a cached fragment from the location signified by `key` exists (see
# `expire_fragment` for acceptable formats).
def fragment_exist?(key, options = nil)
return unless cache_configured?
key = combined_fragment_cache_key(key)
@ -115,22 +113,21 @@ def fragment_exist?(key, options = nil)
# Removes fragments from the cache.
#
# +key+ can take one of three forms:
# `key` can take one of three forms:
#
# * String - This would normally take the form of a path, like
# <tt>pages/45/notes</tt>.
# * Hash - Treated as an implicit call to +url_for+, like
# <tt>{ controller: 'pages', action: 'notes', id: 45}</tt>
# * Regexp - Will remove any fragment that matches, so
# <tt>%r{pages/\d*/notes}</tt> might remove all notes. Make sure you
# don't use anchors in the regex (<tt>^</tt> or <tt>$</tt>) because
# the actual filename matched looks like
# <tt>./cache/filename/path.cache</tt>. Note: Regexp expiration is
# only supported on caches that can iterate over all keys (unlike
# memcached).
# * String - This would normally take the form of a path, like
# `pages/45/notes`.
# * Hash - Treated as an implicit call to `url_for`, like `{ controller:
# 'pages', action: 'notes', id: 45}`
# * Regexp - Will remove any fragment that matches, so `%r{pages/\d*/notes}`
# might remove all notes. Make sure you don't use anchors in the regex (`^`
# or `$`) because the actual filename matched looks like
# `./cache/filename/path.cache`. Note: Regexp expiration is only supported
# on caches that can iterate over all keys (unlike memcached).
#
# +options+ is passed through to the cache store's +delete+
# method (or <tt>delete_matched</tt>, for Regexp keys).
#
# `options` is passed through to the cache store's `delete` method (or
# `delete_matched`, for Regexp keys).
def expire_fragment(key, options = nil)
return unless cache_configured?
key = combined_fragment_cache_key(key) unless key.is_a?(Regexp)

@ -1,29 +1,32 @@
# frozen_string_literal: true
# :markup: markdown
module AbstractController
# = Abstract Controller \Callbacks
# # Abstract Controller Callbacks
#
# Abstract Controller provides hooks during the life cycle of a controller action.
# Callbacks allow you to trigger logic during this cycle. Available callbacks are:
# Abstract Controller provides hooks during the life cycle of a controller
# action. Callbacks allow you to trigger logic during this cycle. Available
# callbacks are:
#
# * <tt>after_action</tt>
# * <tt>append_after_action</tt>
# * <tt>append_around_action</tt>
# * <tt>append_before_action</tt>
# * <tt>around_action</tt>
# * <tt>before_action</tt>
# * <tt>prepend_after_action</tt>
# * <tt>prepend_around_action</tt>
# * <tt>prepend_before_action</tt>
# * <tt>skip_after_action</tt>
# * <tt>skip_around_action</tt>
# * <tt>skip_before_action</tt>
# * `after_action`
# * `append_after_action`
# * `append_around_action`
# * `append_before_action`
# * `around_action`
# * `before_action`
# * `prepend_after_action`
# * `prepend_around_action`
# * `prepend_before_action`
# * `skip_after_action`
# * `skip_around_action`
# * `skip_before_action`
module Callbacks
extend ActiveSupport::Concern
# Uses ActiveSupport::Callbacks as the base functionality. For
# more details on the whole callback system, read the documentation
# for ActiveSupport::Callbacks.
# Uses ActiveSupport::Callbacks as the base functionality. For more details on
# the whole callback system, read the documentation for
# ActiveSupport::Callbacks.
include ActiveSupport::Callbacks
included do
@ -69,25 +72,24 @@ def match?(controller)
end
module ClassMethods
# If +:only+ or +:except+ are used, convert the options into the
# +:if+ and +:unless+ options of ActiveSupport::Callbacks.
# If `:only` or `:except` are used, convert the options into the `:if` and
# `:unless` options of ActiveSupport::Callbacks.
#
# The basic idea is that <tt>:only => :index</tt> gets converted to
# <tt>:if => proc {|c| c.action_name == "index" }</tt>.
# The basic idea is that `:only => :index` gets converted to `:if => proc {|c|
# c.action_name == "index" }`.
#
# Note that <tt>:only</tt> has priority over <tt>:if</tt> in case they
# are used together.
# Note that `:only` has priority over `:if` in case they are used together.
#
# only: :index, if: -> { true } # the :if option will be ignored.
# only: :index, if: -> { true } # the :if option will be ignored.
#
# Note that <tt>:if</tt> has priority over <tt>:except</tt> in case they
# are used together.
# Note that `:if` has priority over `:except` in case they are used together.
#
# except: :index, if: -> { true } # the :except option will be ignored.
# except: :index, if: -> { true } # the :except option will be ignored.
#
# #### Options
# * `only` - The callback should be run only for this action.
# * `except` - The callback should be run for all actions except this action.
#
# ==== Options
# * <tt>only</tt> - The callback should be run only for this action.
# * <tt>except</tt> - The callback should be run for all actions except this action.
def _normalize_callback_options(options)
_normalize_callback_option(options, :only, :if)
_normalize_callback_option(options, :except, :unless)
@ -101,18 +103,20 @@ def _normalize_callback_option(options, from, to) # :nodoc:
end
end
# Take callback names and an optional callback proc, normalize them,
# then call the block with each callback. This allows us to abstract
# the normalization across several methods that use it.
# Take callback names and an optional callback proc, normalize them, then call
# the block with each callback. This allows us to abstract the normalization
# across several methods that use it.
#
# ==== Parameters
# * <tt>callbacks</tt> - An array of callbacks, with an optional
# options hash as the last parameter.
# * <tt>block</tt> - A proc that should be added to the callbacks.
# #### Parameters
# * `callbacks` - An array of callbacks, with an optional options hash as the
# last parameter.
# * `block` - A proc that should be added to the callbacks.
#
#
# #### Block Parameters
# * `name` - The callback to be added.
# * `options` - A hash of options to be used when adding the callback.
#
# ==== Block Parameters
# * <tt>name</tt> - The callback to be added.
# * <tt>options</tt> - A hash of options to be used when adding the callback.
def _insert_callbacks(callbacks, block = nil)
options = callbacks.extract_options!
callbacks.push(block) if block
@ -131,20 +135,21 @@ def _insert_callbacks(callbacks, block = nil)
#
# Append a callback before actions. See _insert_callbacks for parameter details.
#
# If the callback renders or redirects, the action will not run. If there
# are additional callbacks scheduled to run after that callback, they are
# also cancelled.
# If the callback renders or redirects, the action will not run. If there are
# additional callbacks scheduled to run after that callback, they are also
# cancelled.
##
# :method: prepend_before_action
#
# :call-seq: prepend_before_action(names, block)
#
# Prepend a callback before actions. See _insert_callbacks for parameter details.
# Prepend a callback before actions. See _insert_callbacks for parameter
# details.
#
# If the callback renders or redirects, the action will not run. If there
# are additional callbacks scheduled to run after that callback, they are
# also cancelled.
# If the callback renders or redirects, the action will not run. If there are
# additional callbacks scheduled to run after that callback, they are also
# cancelled.
##
# :method: skip_before_action
@ -160,9 +165,9 @@ def _insert_callbacks(callbacks, block = nil)
#
# Append a callback before actions. See _insert_callbacks for parameter details.
#
# If the callback renders or redirects, the action will not run. If there
# are additional callbacks scheduled to run after that callback, they are
# also cancelled.
# If the callback renders or redirects, the action will not run. If there are
# additional callbacks scheduled to run after that callback, they are also
# cancelled.
##
# :method: after_action
@ -204,7 +209,8 @@ def _insert_callbacks(callbacks, block = nil)
#
# :call-seq: prepend_around_action(names, block)
#
# Prepend a callback around actions. See _insert_callbacks for parameter details.
# Prepend a callback around actions. See _insert_callbacks for parameter
# details.
##
# :method: skip_around_action
@ -219,9 +225,8 @@ def _insert_callbacks(callbacks, block = nil)
# :call-seq: append_around_action(names, block)
#
# Append a callback around actions. See _insert_callbacks for parameter details.
# set up before_action, prepend_before_action, skip_before_action, etc.
# for each of before, after, and around.
# set up before_action, prepend_before_action, skip_before_action, etc. for each
# of before, after, and around.
[:before, :after, :around].each do |callback|
define_method "#{callback}_action" do |*names, &blk|
_insert_callbacks(names, blk) do |name, options|
@ -235,8 +240,8 @@ def _insert_callbacks(callbacks, block = nil)
end
end
# Skip a before, after or around callback. See _insert_callbacks
# for details on the allowed parameters.
# Skip a before, after or around callback. See _insert_callbacks for details on
# the allowed parameters.
define_method "skip_#{callback}_action" do |*names|
_insert_callbacks(names) do |name, options|
skip_callback(:process_action, callback, name, options)
@ -249,8 +254,8 @@ def _insert_callbacks(callbacks, block = nil)
end
private
# Override <tt>AbstractController::Base#process_action</tt> to run the
# <tt>process_action</tt> callbacks around the normal behavior.
# Override `AbstractController::Base#process_action` to run the `process_action`
# callbacks around the normal behavior.
def process_action(...)
run_callbacks(:process_action) do
super

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "action_dispatch/http/mime_type"
module AbstractController

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module AbstractController
def self.deprecator # :nodoc:
@deprecator ||= ActiveSupport::Deprecation.new

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module AbstractController
class Error < StandardError # :nodoc:
end

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "active_support/dependencies"
require "active_support/core_ext/name_error"
@ -10,8 +12,8 @@ module Helpers
included do
class_attribute :_helper_methods, default: Array.new
# This is here so that it is always higher in the inheritance chain than
# the definition in lib/action_view/rendering.rb
# This is here so that it is always higher in the inheritance chain than the
# definition in lib/action_view/rendering.rb
redefine_singleton_method(:_helpers) do
if @_helpers ||= nil
@_helpers
@ -60,9 +62,9 @@ def helper_modules_from_paths(paths)
extend Resolution
module ClassMethods
# When a class is inherited, wrap its helper module in a new module.
# This ensures that the parent class's module can be changed
# independently of the child class's.
# When a class is inherited, wrap its helper module in a new module. This
# ensures that the parent class's module can be changed independently of the
# child class's.
def inherited(klass)
# Inherited from parent by default
klass._helpers = nil
@ -79,49 +81,48 @@ def inherited(klass)
# :method: modules_for_helpers
# :call-seq: modules_for_helpers(modules_or_helper_prefixes)
#
# Given an array of values like the ones accepted by +helper+, this method
# Given an array of values like the ones accepted by `helper`, this method
# returns an array with the corresponding modules, in the same order.
#
# ActionController::Base.modules_for_helpers(["application", "chart", "rubygems"])
# # => [ApplicationHelper, ChartHelper, RubygemsHelper]
# ActionController::Base.modules_for_helpers(["application", "chart", "rubygems"])
# # => [ApplicationHelper, ChartHelper, RubygemsHelper]
#
#--
# Implemented by Resolution#modules_for_helpers.
##
# :method: all_helpers_from_path
# :method: # all_helpers_from_path
# :call-seq: all_helpers_from_path(path)
#
# Returns a list of helper names in a given path.
#
# ActionController::Base.all_helpers_from_path 'app/helpers'
# # => ["application", "chart", "rubygems"]
# ActionController::Base.all_helpers_from_path 'app/helpers'
# # => ["application", "chart", "rubygems"]
#
#--
# Implemented by Resolution#all_helpers_from_path.
# Declare a controller method as a helper. For example, the following
# makes the +current_user+ and +logged_in?+ controller methods available
# makes the `current_user` and `logged_in?` controller methods available
# to the view:
# class ApplicationController < ActionController::Base
# helper_method :current_user, :logged_in?
# class ApplicationController < ActionController::Base
# helper_method :current_user, :logged_in?
#
# private
# def current_user
# @current_user ||= User.find_by(id: session[:user])
# end
# private
# def current_user
# @current_user ||= User.find_by(id: session[:user])
# end
#
# def logged_in?
# current_user != nil
# end
# end
# def logged_in?
# current_user != nil
# end
# end
#
# In a view:
# <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%>
# <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%>
#
# ==== Parameters
# * <tt>method[, method]</tt> - A name or names of a method on the controller
# to be made available on the view.
# #### Parameters
# * `method[, method]` - A name or names of a method on the controller to be
# made available on the view.
def helper_method(*methods)
methods.flatten!
self._helper_methods += methods
@ -131,7 +132,7 @@ def helper_method(*methods)
methods.each do |method|
# def current_user(*args, &block)
# controller.send(:'current_user', *args, &block)
# controller.send(:'current_user', *args, &block)
# end
_helpers_for_modification.class_eval <<~ruby_eval.lines.map(&:strip).join(";"), file, line
def #{method}(...)
@ -143,54 +144,54 @@ def #{method}(...)
# Includes the given modules in the template class.
#
# Modules can be specified in different ways. All of the following calls
# include +FooHelper+:
# Modules can be specified in different ways. All of the following calls include
# `FooHelper`:
#
# # Module, recommended.
# helper FooHelper
# # Module, recommended.
# helper FooHelper
#
# # String/symbol without the "helper" suffix, camel or snake case.
# helper "Foo"
# helper :Foo
# helper "foo"
# helper :foo
# # String/symbol without the "helper" suffix, camel or snake case.
# helper "Foo"
# helper :Foo
# helper "foo"
# helper :foo
#
# The last two assume that <tt>"foo".camelize</tt> returns "Foo".
# The last two assume that `"foo".camelize` returns "Foo".
#
# When strings or symbols are passed, the method finds the actual module
# object using String#constantize. Therefore, if the module has not been
# yet loaded, it has to be autoloadable, which is normally the case.
# When strings or symbols are passed, the method finds the actual module object
# using String#constantize. Therefore, if the module has not been yet loaded, it
# has to be autoloadable, which is normally the case.
#
# Namespaces are supported. The following calls include +Foo::BarHelper+:
# Namespaces are supported. The following calls include `Foo::BarHelper`:
#
# # Module, recommended.
# helper Foo::BarHelper
# # Module, recommended.
# helper Foo::BarHelper
#
# # String/symbol without the "helper" suffix, camel or snake case.
# helper "Foo::Bar"
# helper :"Foo::Bar"
# helper "foo/bar"
# helper :"foo/bar"
# # String/symbol without the "helper" suffix, camel or snake case.
# helper "Foo::Bar"
# helper :"Foo::Bar"
# helper "foo/bar"
# helper :"foo/bar"
#
# The last two assume that <tt>"foo/bar".camelize</tt> returns "Foo::Bar".
# The last two assume that `"foo/bar".camelize` returns "Foo::Bar".
#
# The method accepts a block too. If present, the block is evaluated in
# the context of the controller helper module. This simple call makes the
# +wadus+ method available in templates of the enclosing controller:
# The method accepts a block too. If present, the block is evaluated in the
# context of the controller helper module. This simple call makes the `wadus`
# method available in templates of the enclosing controller:
#
# helper do
# def wadus
# "wadus"
# helper do
# def wadus
# "wadus"
# end
# end
# end
#
# Furthermore, all the above styles can be mixed together:
#
# helper FooHelper, "woo", "bar/baz" do
# def wadus
# "wadus"
# helper FooHelper, "woo", "bar/baz" do
# def wadus
# "wadus"
# end
# end
# end
#
def helper(*args, &block)
modules_for_helpers(args).each do |mod|
@ -201,8 +202,8 @@ def helper(*args, &block)
_helpers_for_modification.module_eval(&block) if block_given?
end
# Clears up all existing helpers in this class, only keeping the helper
# with the same name as this class.
# Clears up all existing helpers in this class, only keeping the helper with the
# same name as this class.
def clear_helpers
inherited_helper_methods = _helper_methods
self._helpers = Module.new
@ -221,8 +222,8 @@ def _helpers_for_modification
private
def define_helpers_module(klass, helpers = nil)
# In some tests inherited is called explicitly. In that case, just
# return the module from the first time it was defined
# In some tests inherited is called explicitly. In that case, just return the
# module from the first time it was defined
return klass.const_get(:HelperMethods) if klass.const_defined?(:HelperMethods, false)
mod = Module.new

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "active_support/benchmarkable"
module AbstractController

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "active_support/core_ext/module/introspection"
module AbstractController

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "abstract_controller/error"
require "action_view"
require "action_view/view_paths"
@ -19,9 +21,9 @@ module Rendering
include ActionView::ViewPaths
# Normalizes arguments and options, and then delegates to render_to_body and
# sticks the result in <tt>self.response_body</tt>.
# sticks the result in `self.response_body`.
#
# Supported options depend on the underlying +render_to_body+ implementation.
# Supported options depend on the underlying `render_to_body` implementation.
def render(*args, &block)
options = _normalize_render(*args, &block)
rendered_body = render_to_body(options)
@ -35,11 +37,11 @@ def render(*args, &block)
end
# Similar to #render, but only returns the rendered template as a string,
# instead of setting +self.response_body+.
# instead of setting `self.response_body`.
#
# If a component extends the semantics of +response_body+ (as ActionController
# extends it to be anything that responds to the method each), this method
# needs to be overridden in order to still return a string.
# If a component extends the semantics of `response_body` (as ActionController
# extends it to be anything that responds to the method each), this method needs
# to be overridden in order to still return a string.
def render_to_string(*args, &block)
options = _normalize_render(*args, &block)
render_to_body(options)
@ -49,15 +51,15 @@ def render_to_string(*args, &block)
def render_to_body(options = {})
end
# Returns +Content-Type+ of rendered content.
# Returns `Content-Type` of rendered content.
def rendered_format
Mime[:text]
end
DEFAULT_PROTECTED_INSTANCE_VARIABLES = %i(@_action_name @_response_body @_formats @_prefixes)
# This method should return a hash with assigns.
# You can overwrite this configuration per controller.
# This method should return a hash with assigns. You can overwrite this
# configuration per controller.
def view_assigns
variables = instance_variables - _protected_ivars
@ -67,9 +69,8 @@ def view_assigns
end
private
# Normalize args by converting <tt>render "foo"</tt> to
# <tt>render action: "foo"</tt> and <tt>render "foo/bar"</tt> to
# <tt>render file: "foo/bar"</tt>.
# Normalize args by converting `render "foo"` to `render action: "foo"` and
# `render "foo/bar"` to `render file: "foo/bar"`.
def _normalize_args(action = nil, options = {}) # :doc:
if action.respond_to?(:permitted?)
if action.permitted?

@ -1,17 +1,19 @@
# frozen_string_literal: true
# :markup: markdown
require "active_support/html_safe_translation"
module AbstractController
module Translation
# Delegates to <tt>I18n.translate</tt>.
# Delegates to `I18n.translate`.
#
# When the given key starts with a period, it will be scoped by the current
# controller and action. So if you call <tt>translate(".foo")</tt> from
# <tt>PeopleController#index</tt>, it will convert the call to
# <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive
# to translate many keys within the same controller / action and gives you a
# simple framework for scoping them consistently.
# controller and action. So if you call `translate(".foo")` from
# `PeopleController#index`, it will convert the call to
# `I18n.translate("people.index.foo")`. This makes it less repetitive to
# translate many keys within the same controller / action and gives you a simple
# framework for scoping them consistently.
def translate(key, **options)
if key&.start_with?(".")
path = controller_path.tr("/", ".")
@ -25,7 +27,7 @@ def translate(key, **options)
end
alias :t :translate
# Delegates to <tt>I18n.localize</tt>.
# Delegates to `I18n.localize`.
def localize(object, **options)
I18n.localize(object, **options)
end

@ -1,14 +1,16 @@
# frozen_string_literal: true
# :markup: markdown
module AbstractController
# = URL For
# # URL For
#
# Includes +url_for+ into the host class (e.g. an abstract controller or mailer). The class
# has to provide a +RouteSet+ by implementing the <tt>_routes</tt> methods. Otherwise, an
# exception will be raised.
# Includes `url_for` into the host class (e.g. an abstract controller or
# mailer). The class has to provide a `RouteSet` by implementing the `_routes`
# methods. Otherwise, an exception will be raised.
#
# Note that this module is completely decoupled from HTTP - the only requirement is a valid
# <tt>_routes</tt> implementation.
# Note that this module is completely decoupled from HTTP - the only requirement
# is a valid `_routes` implementation.
module UrlFor
extend ActiveSupport::Concern
include ActionDispatch::Routing::UrlFor

@ -1,12 +1,14 @@
# frozen_string_literal: true
# :markup: markdown
require "abstract_controller"
require "action_dispatch"
require "action_controller/deprecator"
require "action_controller/metal/strong_parameters"
require "action_controller/metal/exceptions"
# = Action Controller
# # Action Controller
#
# Action Controller is a module of Action Pack.
#

@ -1,107 +1,108 @@
# frozen_string_literal: true
# :markup: markdown
require "action_view"
require "action_controller"
require "action_controller/log_subscriber"
module ActionController
# = Action Controller \API
# # Action Controller API
#
# API Controller is a lightweight version of ActionController::Base,
# created for applications that don't require all functionalities that a complete
# \Rails controller provides, allowing you to create controllers with just the
# features that you need for API only applications.
# API Controller is a lightweight version of ActionController::Base, created for
# applications that don't require all functionalities that a complete Rails
# controller provides, allowing you to create controllers with just the features
# that you need for API only applications.
#
# An API Controller is different from a normal controller in the sense that
# by default it doesn't include a number of features that are usually required
# by browser access only: layouts and templates rendering,
# flash, assets, and so on. This makes the entire controller stack thinner,
# suitable for API applications. It doesn't mean you won't have such
# features if you need them: they're all available for you to include in
# your application, they're just not part of the default API controller stack.
# An API Controller is different from a normal controller in the sense that by
# default it doesn't include a number of features that are usually required by
# browser access only: layouts and templates rendering, flash, assets, and so
# on. This makes the entire controller stack thinner, suitable for API
# applications. It doesn't mean you won't have such features if you need them:
# they're all available for you to include in your application, they're just not
# part of the default API controller stack.
#
# Normally, +ApplicationController+ is the only controller that inherits from
# +ActionController::API+. All other controllers in turn inherit from
# +ApplicationController+.
# Normally, `ApplicationController` is the only controller that inherits from
# `ActionController::API`. All other controllers in turn inherit from
# `ApplicationController`.
#
# A sample controller could look like this:
#
# class PostsController < ApplicationController
# def index
# posts = Post.all
# render json: posts
# class PostsController < ApplicationController
# def index
# posts = Post.all
# render json: posts
# end
# end
# end
#
# Request, response, and parameters objects all work the exact same way as
# ActionController::Base.
#
# == Renders
# ## Renders
#
# The default API Controller stack includes all renderers, which means you
# can use <tt>render :json</tt> and siblings freely in your controllers. Keep
# in mind that templates are not going to be rendered, so you need to ensure
# your controller is calling either <tt>render</tt> or <tt>redirect_to</tt> in
# all actions, otherwise it will return <tt>204 No Content</tt>.
# The default API Controller stack includes all renderers, which means you can
# use `render :json` and siblings freely in your controllers. Keep in mind that
# templates are not going to be rendered, so you need to ensure your controller
# is calling either `render` or `redirect_to` in all actions, otherwise it will
# return `204 No Content`.
#
# def show
# post = Post.find(params[:id])
# render json: post
# end
# def show
# post = Post.find(params[:id])
# render json: post
# end
#
# == Redirects
# ## Redirects
#
# Redirects are used to move from one action to another. You can use the
# <tt>redirect_to</tt> method in your controllers in the same way as in
# `redirect_to` method in your controllers in the same way as in
# ActionController::Base. For example:
#
# def create
# redirect_to root_url and return if not_authorized?
# # do stuff here
# end
# def create
# redirect_to root_url and return if not_authorized?
# # do stuff here
# end
#
# == Adding New Behavior
# ## Adding New Behavior
#
# In some scenarios you may want to add back some functionality provided by
# ActionController::Base that is not present by default in
# +ActionController::API+, for instance <tt>MimeResponds</tt>. This
# module gives you the <tt>respond_to</tt> method. Adding it is quite simple,
# you just need to include the module in a specific controller or in
# +ApplicationController+ in case you want it available in your entire
# application:
# `ActionController::API`, for instance `MimeResponds`. This module gives you
# the `respond_to` method. Adding it is quite simple, you just need to include
# the module in a specific controller or in `ApplicationController` in case you
# want it available in your entire application:
#
# class ApplicationController < ActionController::API
# include ActionController::MimeResponds
# end
# class ApplicationController < ActionController::API
# include ActionController::MimeResponds
# end
#
# class PostsController < ApplicationController
# def index
# posts = Post.all
# class PostsController < ApplicationController
# def index
# posts = Post.all
#
# respond_to do |format|
# format.json { render json: posts }
# format.xml { render xml: posts }
# respond_to do |format|
# format.json { render json: posts }
# format.xml { render xml: posts }
# end
# end
# end
# end
#
# Make sure to check the modules included in ActionController::Base
# if you want to use any other functionality that is not provided
# by +ActionController::API+ out of the box.
# Make sure to check the modules included in ActionController::Base if you want
# to use any other functionality that is not provided by `ActionController::API`
# out of the box.
class API < Metal
abstract!
# Shortcut helper that returns all the ActionController::API modules except
# the ones passed as arguments:
# Shortcut helper that returns all the ActionController::API modules except the
# ones passed as arguments:
#
# class MyAPIBaseController < ActionController::Metal
# ActionController::API.without_modules(:UrlFor).each do |left|
# include left
# class MyAPIBaseController < ActionController::Metal
# ActionController::API.without_modules(:UrlFor).each do |left|
# include left
# end
# end
# end
#
# This gives better control over what you want to exclude and makes it easier
# to create an API controller class, instead of listing the modules required
# This gives better control over what you want to exclude and makes it easier to
# create an API controller class, instead of listing the modules required
# manually.
def self.without_modules(*modules)
modules = modules.map do |m|
@ -127,19 +128,19 @@ def self.without_modules(*modules)
DefaultHeaders,
Logging,
# Before callbacks should also be executed as early as possible, so
# also include them at the bottom.
# Before callbacks should also be executed as early as possible, so also include
# them at the bottom.
AbstractController::Callbacks,
# Append rescue at the bottom to wrap as much as possible.
Rescue,
# Add instrumentations hooks at the bottom, to ensure they instrument
# all the methods properly.
# Add instrumentations hooks at the bottom, to ensure they instrument all the
# methods properly.
Instrumentation,
# Params wrapper should come before instrumentation so they are
# properly showed in logs
# Params wrapper should come before instrumentation so they are properly showed
# in logs
ParamsWrapper
]

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
module ApiRendering
extend ActiveSupport::Concern

@ -1,170 +1,205 @@
# frozen_string_literal: true
# :markup: markdown
require "action_view"
require "action_controller/log_subscriber"
require "action_controller/metal/params_wrapper"
module ActionController
# = Action Controller \Base
# # Action Controller Base
#
# 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.
# 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.
#
# By default, only the ApplicationController in a \Rails application inherits from +ActionController::Base+. All other
# controllers inherit from ApplicationController. This gives you one class to configure things such as
# By default, only the ApplicationController in a Rails application inherits
# from `ActionController::Base`. All other controllers inherit from
# ApplicationController. This gives you one class to configure things such as
# request forgery protection and filtering of sensitive request parameters.
#
# A sample controller could look like this:
#
# class PostsController < ApplicationController
# def index
# @posts = Post.all
# class PostsController < ApplicationController
# def index
# @posts = Post.all
# end
#
# def create
# @post = Post.create params[:post]
# redirect_to posts_path
# end
# end
#
# def create
# @post = Post.create params[:post]
# redirect_to posts_path
# Actions, by default, render a template in the `app/views` directory
# corresponding to the name of the controller and action after executing code in
# the action. For example, the `index` action of the PostsController would
# render the template `app/views/posts/index.html.erb` by default after
# populating the `@posts` instance variable.
#
# Unlike index, the create action will not render a template. After performing
# its main purpose (creating a new post), it initiates a redirect instead. This
# redirect works by returning an external `302 Moved` HTTP response that takes
# the user to the index action.
#
# These two methods represent the two basic action archetypes used in Action
# Controllers: Get-and-show and do-and-redirect. Most actions are variations on
# these themes.
#
# ## Requests
#
# For every request, the router determines the value of the `controller` and
# `action` keys. These determine which controller and action are called. The
# remaining request parameters, the session (if one is available), and the full
# request with all the HTTP headers are made available to the action through
# accessor methods. Then the action is performed.
#
# The full request object is available via the request accessor and is primarily
# used to query for HTTP headers:
#
# def server_ip
# location = request.env["REMOTE_ADDR"]
# render plain: "This server hosted at #{location}"
# end
# end
#
# Actions, by default, render a template in the <tt>app/views</tt> directory corresponding to the name of the controller and action
# after executing code in the action. For example, the +index+ action of the PostsController would render the
# template <tt>app/views/posts/index.html.erb</tt> by default after populating the <tt>@posts</tt> instance variable.
# ## Parameters
#
# Unlike index, the create action will not render a template. After performing its main purpose (creating a
# new post), it initiates a redirect instead. This redirect works by returning an external
# <tt>302 Moved</tt> HTTP response that takes the user to the index action.
# All request parameters, whether they come from a query string in the URL or
# form data submitted through a POST request are available through the `params`
# method which returns a hash. For example, an action that was performed through
# `/posts?category=All&limit=5` will include `{ "category" => "All", "limit" =>
# "5" }` in `params`.
#
# These two methods represent the two basic action archetypes used in Action Controllers: Get-and-show and do-and-redirect.
# Most actions are variations on these themes.
# It's also possible to construct multi-dimensional parameter hashes by
# specifying keys using brackets, such as:
#
# == Requests
# <input type="text" name="post[name]" value="david">
# <input type="text" name="post[address]" value="hyacintvej">
#
# For every request, the router determines the value of the +controller+ and +action+ keys. These determine which controller
# and action are called. The remaining request parameters, the session (if one is available), and the full request with
# all the HTTP headers are made available to the action through accessor methods. Then the action is performed.
# A request coming from a form holding these inputs will include `{ "post" => {
# "name" => "david", "address" => "hyacintvej" } }`. If the address input had
# been named `post[address][street]`, the `params` would have included `{ "post"
# => { "address" => { "street" => "hyacintvej" } } }`. There's no limit to the
# depth of the nesting.
#
# The full request object is available via the request accessor and is primarily used to query for HTTP headers:
# ## Sessions
#
# def server_ip
# location = request.env["REMOTE_ADDR"]
# render plain: "This server hosted at #{location}"
# end
# Sessions allow you to store objects in between requests. This is useful for
# objects that are not yet ready to be persisted, such as a Signup object
# constructed in a multi-paged process, or objects that don't change much and
# are needed all the time, such as a User object for a system that requires
# login. The session should not be used, however, as a cache for objects where
# it's likely they could be changed unknowingly. It's usually too much work to
# keep it all synchronized -- something databases already excel at.
#
# == Parameters
# You can place objects in the session by using the `session` method, which
# accesses a hash:
#
# All request parameters, whether they come from a query string in the URL or form data submitted through a POST request are
# available through the <tt>params</tt> method which returns a hash. For example, an action that was performed through
# <tt>/posts?category=All&limit=5</tt> will include <tt>{ "category" => "All", "limit" => "5" }</tt> in <tt>params</tt>.
#
# It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as:
#
# <input type="text" name="post[name]" value="david">
# <input type="text" name="post[address]" value="hyacintvej">
#
# A request coming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
# If the address input had been named <tt>post[address][street]</tt>, the <tt>params</tt> would have included
# <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting.
#
# == Sessions
#
# Sessions allow you to store objects in between requests. This is useful for objects that are not yet ready to be persisted,
# such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such
# as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely
# they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at.
#
# You can place objects in the session by using the <tt>session</tt> method, which accesses a hash:
#
# session[:person] = Person.authenticate(user_name, password)
# session[:person] = Person.authenticate(user_name, password)
#
# You can retrieve it again through the same hash:
#
# "Hello #{session[:person]}"
# "Hello #{session[:person]}"
#
# For removing objects from the session, you can either assign a single key to +nil+:
# For removing objects from the session, you can either assign a single key to
# `nil`:
#
# # removes :person from session
# session[:person] = nil
# # removes :person from session
# session[:person] = nil
#
# or you can remove the entire session with +reset_session+.
# or you can remove the entire session with `reset_session`.
#
# By default, sessions are stored in an encrypted browser cookie (see
# ActionDispatch::Session::CookieStore). Thus the user will not be able to
# read or edit the session data. However, the user can keep a copy of the
# cookie even after it has expired, so you should avoid storing sensitive
# information in cookie-based sessions.
# ActionDispatch::Session::CookieStore). Thus the user will not be able to read
# or edit the session data. However, the user can keep a copy of the cookie even
# after it has expired, so you should avoid storing sensitive information in
# cookie-based sessions.
#
# == Responses
# ## Responses
#
# Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response
# object is generated automatically through the use of renders and redirects and requires no user intervention.
# Each action results in a response, which holds the headers and document to be
# sent to the user's browser. The actual response object is generated
# automatically through the use of renders and redirects and requires no user
# intervention.
#
# == Renders
# ## Renders
#
# Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering
# of a template. Included in the Action Pack is the Action View, which enables rendering of ERB templates. It's automatically configured.
# The controller passes objects to the view by assigning instance variables:
# Action Controller sends content to the user by using one of five rendering
# methods. The most versatile and common is the rendering of a template.
# Included in the Action Pack is the Action View, which enables rendering of ERB
# templates. It's automatically configured. The controller passes objects to the
# view by assigning instance variables:
#
# def show
# @post = Post.find(params[:id])
# end
# def show
# @post = Post.find(params[:id])
# end
#
# Which are then automatically available to the view:
#
# Title: <%= @post.title %>
# Title: <%= @post.title %>
#
# You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates
# will use the manual rendering methods:
# You don't have to rely on the automated rendering. For example, actions that
# could result in the rendering of different templates will use the manual
# rendering methods:
#
# def search
# @results = Search.find(params[:query])
# case @results.count
# when 0 then render action: "no_results"
# when 1 then render action: "show"
# when 2..10 then render action: "show_many"
# def search
# @results = Search.find(params[:query])
# case @results.count
# when 0 then render action: "no_results"
# when 1 then render action: "show"
# when 2..10 then render action: "show_many"
# end
# end
# end
#
# Read more about writing ERB and Builder templates in ActionView::Base.
#
# == Redirects
# ## Redirects
#
# Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to the
# database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're
# going to reuse (and redirect to) a <tt>show</tt> action that we'll assume has already been created. The code might look like this:
# Redirects are used to move from one action to another. For example, after a
# `create` action, which stores a blog entry to the database, we might like to
# show the user the new entry. Because we're following good DRY principles
# (Don't Repeat Yourself), we're going to reuse (and redirect to) a `show`
# action that we'll assume has already been created. The code might look like
# this:
#
# def create
# @entry = Entry.new(params[:entry])
# if @entry.save
# # The entry was saved correctly, redirect to show
# redirect_to action: 'show', id: @entry.id
# else
# # things didn't go so well, do something else
# def create
# @entry = Entry.new(params[:entry])
# if @entry.save
# # The entry was saved correctly, redirect to show
# redirect_to action: 'show', id: @entry.id
# else
# # things didn't go so well, do something else
# end
# end
# end
#
# In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method, which is then executed.
# Note that this is an external HTTP-level redirection which will cause the browser to make a second request (a GET to the show action),
# and not some internal re-routing which calls both "create" and then "show" within one request.
# In this case, after saving our new entry to the database, the user is
# redirected to the `show` method, which is then executed. Note that this is an
# external HTTP-level redirection which will cause the browser to make a second
# request (a GET to the show action), and not some internal re-routing which
# calls both "create" and then "show" within one request.
#
# Learn more about <tt>redirect_to</tt> and what options you have in ActionController::Redirecting.
# Learn more about `redirect_to` and what options you have in
# ActionController::Redirecting.
#
# == Calling multiple redirects or renders
# ## Calling multiple redirects or renders
#
# An action may contain only a single render or a single redirect. Attempting to try to do either again will result in a DoubleRenderError:
# An action may contain only a single render or a single redirect. Attempting to
# try to do either again will result in a DoubleRenderError:
#
# def do_something
# redirect_to action: "elsewhere"
# render action: "overthere" # raises DoubleRenderError
# end
# def do_something
# redirect_to action: "elsewhere"
# render action: "overthere" # raises DoubleRenderError
# end
#
# If you need to redirect on the condition of something, then be sure to add "and return" to halt execution.
# If you need to redirect on the condition of something, then be sure to add
# "and return" to halt execution.
#
# def do_something
# redirect_to(action: "elsewhere") and return if monkeys.nil?
# render action: "overthere" # won't be called if monkeys is nil
# end
# def do_something
# redirect_to(action: "elsewhere") and return if monkeys.nil?
# render action: "overthere" # won't be called if monkeys is nil
# end
#
class Base < Metal
abstract!
@ -172,15 +207,15 @@ class Base < Metal
# Shortcut helper that returns all the modules included in
# ActionController::Base except the ones passed as arguments:
#
# class MyBaseController < ActionController::Metal
# ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
# include left
# class MyBaseController < ActionController::Metal
# ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
# include left
# end
# end
# end
#
# This gives better control over what you want to exclude and makes it
# easier to create a bare controller class, instead of listing the modules
# required manually.
# This gives better control over what you want to exclude and makes it easier to
# create a bare controller class, instead of listing the modules required
# manually.
def self.without_modules(*modules)
modules = modules.map do |m|
m.is_a?(Symbol) ? ActionController.const_get(m) : m
@ -224,19 +259,19 @@ def self.without_modules(*modules)
DefaultHeaders,
Logging,
# Before callbacks should also be executed as early as possible, so
# also include them at the bottom.
# Before callbacks should also be executed as early as possible, so also include
# them at the bottom.
AbstractController::Callbacks,
# Append rescue at the bottom to wrap as much as possible.
Rescue,
# Add instrumentations hooks at the bottom, to ensure they instrument
# all the methods properly.
# Add instrumentations hooks at the bottom, to ensure they instrument all the
# methods properly.
Instrumentation,
# Params wrapper should come before instrumentation so they are
# properly showed in logs
# Params wrapper should come before instrumentation so they are properly showed
# in logs
ParamsWrapper
]

@ -1,28 +1,31 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
# = Action Controller \Caching
# # Action Controller Caching
#
# \Caching is a cheap way of speeding up slow applications by keeping the result of
# calculations, renderings, and database calls around for subsequent requests.
# Caching is a cheap way of speeding up slow applications by keeping the result
# of calculations, renderings, and database calls around for subsequent
# requests.
#
# You can read more about each approach by clicking the modules below.
#
# Note: To turn off all caching provided by Action Controller, set
# config.action_controller.perform_caching = false
# config.action_controller.perform_caching = false
#
# == \Caching stores
# ## Caching stores
#
# All the caching stores from ActiveSupport::Cache are available to be used as backends
# for Action Controller caching.
# All the caching stores from ActiveSupport::Cache are available to be used as
# backends for Action Controller caching.
#
# Configuration examples (FileStore is the default):
#
# config.action_controller.cache_store = :memory_store
# config.action_controller.cache_store = :file_store, '/path/to/cache/directory'
# config.action_controller.cache_store = :mem_cache_store, 'localhost'
# config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211')
# config.action_controller.cache_store = MyOwnStore.new('parameter')
# config.action_controller.cache_store = :memory_store
# config.action_controller.cache_store = :file_store, '/path/to/cache/directory'
# config.action_controller.cache_store = :mem_cache_store, 'localhost'
# config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211')
# config.action_controller.cache_store = MyOwnStore.new('parameter')
module Caching
extend ActiveSupport::Concern

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
def self.deprecator # :nodoc:
AbstractController.deprecator

@ -1,31 +1,33 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
# = Action Controller Form Builder
# # Action Controller Form Builder
#
# Override the default form builder for all views rendered by this
# controller and any of its descendants. Accepts a subclass of
# Override the default form builder for all views rendered by this controller
# and any of its descendants. Accepts a subclass of
# ActionView::Helpers::FormBuilder.
#
# For example, given a form builder:
#
# class AdminFormBuilder < ActionView::Helpers::FormBuilder
# def special_field(name)
# class AdminFormBuilder < ActionView::Helpers::FormBuilder
# def special_field(name)
# end
# end
# end
#
# The controller specifies a form builder as its default:
#
# class AdminAreaController < ApplicationController
# default_form_builder AdminFormBuilder
# end
# class AdminAreaController < ApplicationController
# default_form_builder AdminFormBuilder
# end
#
# Then in the view any form using +form_for+ will be an instance of the
# Then in the view any form using `form_for` will be an instance of the
# specified form builder:
#
# <%= form_for(@instance) do |builder| %>
# <%= builder.special_field(:name) %>
# <% end %>
# <%= form_for(@instance) do |builder| %>
# <%= builder.special_field(:name) %>
# <% end %>
module FormBuilder
extend ActiveSupport::Concern
@ -34,11 +36,12 @@ module FormBuilder
end
module ClassMethods
# Set the form builder to be used as the default for all forms
# in the views rendered by this controller and its subclasses.
# Set the form builder to be used as the default for all forms in the views
# rendered by this controller and its subclasses.
#
# ==== Parameters
# * <tt>builder</tt> - Default form builder, an instance of ActionView::Helpers::FormBuilder
# #### Parameters
# * `builder` - Default form builder, an instance of
# ActionView::Helpers::FormBuilder
def default_form_builder(builder)
self._default_form_builder = builder
end

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
class LogSubscriber < ActiveSupport::LogSubscriber
INTERNAL_PARAMS = %w(controller action format _method only_path)

@ -1,17 +1,19 @@
# frozen_string_literal: true
# :markup: markdown
require "active_support/core_ext/array/extract_options"
require "action_dispatch/middleware/stack"
module ActionController
# = Action Controller \MiddlewareStack
# # Action Controller MiddlewareStack
#
# Extend ActionDispatch middleware stack to make it aware of options
# allowing the following syntax in controllers:
# Extend ActionDispatch middleware stack to make it aware of options allowing
# the following syntax in controllers:
#
# class PostsController < ApplicationController
# use AuthenticationMiddleware, except: [:index, :show]
# end
# class PostsController < ApplicationController
# use AuthenticationMiddleware, except: [:index, :show]
# end
#
class MiddlewareStack < ActionDispatch::MiddlewareStack # :nodoc:
class Middleware < ActionDispatch::MiddlewareStack::Middleware # :nodoc:
@ -60,73 +62,71 @@ def build_middleware(klass, args, block)
end
end
# = Action Controller \Metal
# # Action Controller Metal
#
# +ActionController::Metal+ is the simplest possible controller, providing a
# `ActionController::Metal` is the simplest possible controller, providing a
# valid Rack interface without the additional niceties provided by
# ActionController::Base.
#
# A sample metal controller might look like this:
#
# class HelloController < ActionController::Metal
# def index
# self.response_body = "Hello World!"
# class HelloController < ActionController::Metal
# def index
# self.response_body = "Hello World!"
# end
# end
# end
#
# And then to route requests to your metal controller, you would add
# something like this to <tt>config/routes.rb</tt>:
# And then to route requests to your metal controller, you would add something
# like this to `config/routes.rb`:
#
# get 'hello', to: HelloController.action(:index)
# get 'hello', to: HelloController.action(:index)
#
# The +action+ method returns a valid Rack application for the \Rails
# router to dispatch to.
# The `action` method returns a valid Rack application for the Rails router to
# dispatch to.
#
# == \Rendering \Helpers
# ## Rendering Helpers
#
# +ActionController::Metal+ by default provides no utilities for rendering
# `ActionController::Metal` by default provides no utilities for rendering
# views, partials, or other responses aside from explicitly calling of
# <tt>response_body=</tt>, <tt>content_type=</tt>, and <tt>status=</tt>. To
# add the render helpers you're used to having in a normal controller, you
# can do the following:
# `response_body=`, `content_type=`, and `status=`. To add the render helpers
# you're used to having in a normal controller, you can do the following:
#
# class HelloController < ActionController::Metal
# include AbstractController::Rendering
# include ActionView::Layouts
# append_view_path "#{Rails.root}/app/views"
# class HelloController < ActionController::Metal
# include AbstractController::Rendering
# include ActionView::Layouts
# append_view_path "#{Rails.root}/app/views"
#
# def index
# render "hello/index"
# def index
# render "hello/index"
# end
# end
# end
#
# == Redirection \Helpers
# ## Redirection Helpers
#
# To add redirection helpers to your metal controller, do the following:
#
# class HelloController < ActionController::Metal
# include ActionController::Redirecting
# include Rails.application.routes.url_helpers
# class HelloController < ActionController::Metal
# include ActionController::Redirecting
# include Rails.application.routes.url_helpers
#
# def index
# redirect_to root_url
# def index
# redirect_to root_url
# end
# end
# end
#
# == Other \Helpers
#
# You can refer to the modules included in ActionController::Base to see
# other features you can bring into your metal controller.
# ## Other Helpers
#
# You can refer to the modules included in ActionController::Base to see other
# features you can bring into your metal controller.
class Metal < AbstractController::Base
abstract!
# Returns the last part of the controller's name, underscored, without the ending
# <tt>Controller</tt>. For instance, +PostsController+ returns <tt>posts</tt>.
# Namespaces are left out, so +Admin::PostsController+ returns <tt>posts</tt> as well.
# Returns the last part of the controller's name, underscored, without the
# ending `Controller`. For instance, `PostsController` returns `posts`.
# Namespaces are left out, so `Admin::PostsController` returns `posts` as well.
#
# ==== Returns
# * <tt>string</tt>
# #### Returns
# * `string`
def self.controller_name
@controller_name ||= (name.demodulize.delete_suffix("Controller").underscore unless anonymous?)
end
@ -172,7 +172,7 @@ def controller_name
##
# The ActionDispatch::Request::Session instance for the current request.
# See further details in the
# {Active Controller Session guide}[https://guides.rubyonrails.org/action_controller_overview.html#session].
# [Active Controller Session guide](https://guides.rubyonrails.org/action_controller_overview.html#session).
delegate :session, to: "@_request"
##
@ -201,7 +201,7 @@ def params=(val)
alias :response_code :status # :nodoc:
# Basic \url_for that can be overridden for more robust functionality.
# Basic url_for that can be overridden for more robust functionality.
def url_for(string)
string
end
@ -238,7 +238,8 @@ def set_response!(response) # :nodoc:
@_response = response
end
# Assign the response and mark it as committed. No further processing will occur.
# Assign the response and mark it as committed. No further processing will
# occur.
def response=(response)
set_response!(response)
@ -271,15 +272,15 @@ def use(...)
# The middleware stack used by this controller.
#
# By default uses a variation of ActionDispatch::MiddlewareStack which
# allows for the following syntax:
# By default uses a variation of ActionDispatch::MiddlewareStack which allows
# for the following syntax:
#
# class PostsController < ApplicationController
# use AuthenticationMiddleware, except: [:index, :show]
# end
# class PostsController < ApplicationController
# use AuthenticationMiddleware, except: [:index, :show]
# end
#
# Read more about {Rails middleware
# stack}[https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack]
# Read more about [Rails middleware stack]
# (https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack)
# in the guides.
def self.middleware
middleware_stack
@ -300,8 +301,8 @@ def self.action(name)
end
end
# Direct dispatch to the controller. Instantiates the controller, then
# executes the action named +name+.
# Direct dispatch to the controller. Instantiates the controller, then executes
# the action named `name`.
def self.dispatch(name, req, res)
if middleware_stack.any?
middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env

@ -1,41 +1,48 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController # :nodoc:
module AllowBrowser
extend ActiveSupport::Concern
module ClassMethods
# Specify the browser versions that will be allowed to access all actions (or some, as limited by <tt>only:</tt> or <tt>except:</tt>).
# Only browsers matched in the hash or named set passed to <tt>versions:</tt> will be blocked if they're below the versions specified.
# This means that all other browsers, as well as agents that aren't reporting a user-agent header, will be allowed access.
# Specify the browser versions that will be allowed to access all actions (or
# some, as limited by `only:` or `except:`). Only browsers matched in the hash
# or named set passed to `versions:` will be blocked if they're below the
# versions specified. This means that all other browsers, as well as agents that
# aren't reporting a user-agent header, will be allowed access.
#
# A browser that's blocked will by default be served the file in public/426.html with a HTTP status code of "426 Upgrade Required".
# A browser that's blocked will by default be served the file in public/426.html
# with a HTTP status code of "426 Upgrade Required".
#
# In addition to specifically named browser versions, you can also pass <tt>:modern</tt> as the set to restrict support to browsers
# natively supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
# This includes Safari 17.2+, Chrome 119+, Firefox 121+, Opera 104+.
# In addition to specifically named browser versions, you can also pass
# `:modern` as the set to restrict support to browsers natively supporting webp
# images, web push, badges, import maps, CSS nesting, and CSS :has. This
# includes Safari 17.2+, Chrome 119+, Firefox 121+, Opera 104+.
#
# You can use https://caniuse.com to check for browser versions supporting the features you use.
# You can use https://caniuse.com to check for browser versions supporting the
# features you use.
#
# You can use +ActiveSupport::Notifications+ to subscribe to events of browsers being blocked using the +browser_block.action_controller+
# event name.
# You can use `ActiveSupport::Notifications` to subscribe to events of browsers
# being blocked using the `browser_block.action_controller` event name.
#
# Examples:
#
# class ApplicationController < ActionController::Base
# # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting + :has
# allow_browser versions: :modern
# end
# class ApplicationController < ActionController::Base
# # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting + :has
# allow_browser versions: :modern
# end
#
# class ApplicationController < ActionController::Base
# # All versions of Chrome and Opera will be allowed, but no versions of "internet explorer" (ie). Safari needs to be 16.4+ and Firefox 121+.
# allow_browser versions: { safari: 16.4, firefox: 121, ie: false }
# end
# class ApplicationController < ActionController::Base
# # All versions of Chrome and Opera will be allowed, but no versions of "internet explorer" (ie). Safari needs to be 16.4+ and Firefox 121+.
# allow_browser versions: { safari: 16.4, firefox: 121, ie: false }
# end
#
# class MessagesController < ApplicationController
# # In addition to the browsers blocked by ApplicationController, also block Opera below 104 and Chrome below 119 for the show action.
# allow_browser versions: { opera: 104, chrome: 119 }, only: :show
# end
# class MessagesController < ApplicationController
# # In addition to the browsers blocked by ApplicationController, also block Opera below 104 and Chrome below 119 for the show action.
# allow_browser versions: { opera: 104, chrome: 119 }, only: :show
# end
def allow_browser(versions:, block: -> { render file: Rails.root.join("public/426.html"), layout: false, status: :upgrade_required }, **options)
before_action -> { allow_browser(versions: versions, block: block) }, **options
end

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
module BasicImplicitRender # :nodoc:
def send_action(method, *args)

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "active_support/core_ext/object/try"
require "active_support/core_ext/integer/time"
@ -14,116 +16,118 @@ module ConditionalGet
end
module ClassMethods
# Allows you to consider additional controller-wide information when generating an ETag.
# For example, if you serve pages tailored depending on who's logged in at the moment, you
# may want to add the current user id to be part of the ETag to prevent unauthorized displaying
# of cached pages.
# Allows you to consider additional controller-wide information when generating
# an ETag. For example, if you serve pages tailored depending on who's logged in
# at the moment, you may want to add the current user id to be part of the ETag
# to prevent unauthorized displaying of cached pages.
#
# class InvoicesController < ApplicationController
# etag { current_user&.id }
# class InvoicesController < ApplicationController
# etag { current_user&.id }
#
# def show
# # Etag will differ even for the same invoice when it's viewed by a different current_user
# @invoice = Invoice.find(params[:id])
# fresh_when etag: @invoice
# def show
# # Etag will differ even for the same invoice when it's viewed by a different current_user
# @invoice = Invoice.find(params[:id])
# fresh_when etag: @invoice
# end
# end
# end
def etag(&etagger)
self.etaggers += [etagger]
end
end
# Sets the +etag+, +last_modified+, or both on the response, and renders a
# <tt>304 Not Modified</tt> response if the request is already fresh.
# Sets the `etag`, `last_modified`, or both on the response, and renders a `304
# Not Modified` response if the request is already fresh.
#
# ==== Options
# #### Options
#
# [+:etag+]
# Sets a "weak" ETag validator on the response. See the +:weak_etag+ option.
# [+:weak_etag+]
# Sets a "weak" ETag validator on the response. Requests that specify an
# +If-None-Match+ header may receive a <tt>304 Not Modified</tt> response
# if the ETag matches exactly.
# `:etag`
# : Sets a "weak" ETag validator on the response. See the `:weak_etag` option.
# `:weak_etag`
# : Sets a "weak" ETag validator on the response. Requests that specify an
# `If-None-Match` header may receive a `304 Not Modified` response if the
# ETag matches exactly.
#
# A weak ETag indicates semantic equivalence, not byte-for-byte equality,
# so they're good for caching HTML pages in browser caches. They can't be
# used for responses that must be byte-identical, like serving +Range+
# requests within a PDF file.
# [+:strong_etag+]
# Sets a "strong" ETag validator on the response. Requests that specify an
# +If-None-Match+ header may receive a <tt>304 Not Modified</tt> response
# if the ETag matches exactly.
# A weak ETag indicates semantic equivalence, not byte-for-byte equality, so
# they're good for caching HTML pages in browser caches. They can't be used
# for responses that must be byte-identical, like serving `Range` requests
# within a PDF file.
# `:strong_etag`
# : Sets a "strong" ETag validator on the response. Requests that specify an
# `If-None-Match` header may receive a `304 Not Modified` response if the
# ETag matches exactly.
#
# A strong ETag implies exact equality -- the response must match byte for
# byte. This is necessary for serving +Range+ requests within a large
# video or PDF file, for example, or for compatibility with some CDNs that
# don't support weak ETags.
# [+:last_modified+]
# Sets a "weak" last-update validator on the response. Subsequent requests
# that specify an +If-Modified-Since+ header may receive a <tt>304 Not Modified</tt>
# response if +last_modified+ <= +If-Modified-Since+.
# [+:public+]
# By default the +Cache-Control+ header is private. Set this option to
# +true+ if you want your application to be cacheable by other devices,
# such as proxy caches.
# [+:cache_control+]
# When given, will overwrite an existing +Cache-Control+ header. For a
# list of +Cache-Control+ directives, see the {article on
# MDN}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control].
# [+:template+]
# By default, the template digest for the current controller/action is
# included in ETags. If the action renders a different template, you can
# include its digest instead. If the action doesn't render a template at
# all, you can pass <tt>template: false</tt> to skip any attempt to check
# for a template digest.
# A strong ETag implies exact equality -- the response must match byte for
# byte. This is necessary for serving `Range` requests within a large video
# or PDF file, for example, or for compatibility with some CDNs that don't
# support weak ETags.
# `:last_modified`
# : Sets a "weak" last-update validator on the response. Subsequent requests
# that specify an `If-Modified-Since` header may receive a `304 Not
# Modified` response if `last_modified` <= `If-Modified-Since`.
# `:public`
# : By default the `Cache-Control` header is private. Set this option to
# `true` if you want your application to be cacheable by other devices, such
# as proxy caches.
# `:cache_control`
# : When given, will overwrite an existing `Cache-Control` header. For a list
# of `Cache-Control` directives, see the [article on
# MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Contr
# ol).
# `:template`
# : By default, the template digest for the current controller/action is
# included in ETags. If the action renders a different template, you can
# include its digest instead. If the action doesn't render a template at
# all, you can pass `template: false` to skip any attempt to check for a
# template digest.
#
# ==== Examples
#
# def show
# @article = Article.find(params[:id])
# fresh_when(etag: @article, last_modified: @article.updated_at, public: true)
# end
# #### Examples
#
# This will send a <tt>304 Not Modified</tt> response if the request
# specifies a matching ETag and +If-Modified-Since+ header. Otherwise, it
# will render the +show+ template.
# def show
# @article = Article.find(params[:id])
# fresh_when(etag: @article, last_modified: @article.updated_at, public: true)
# end
#
# This will send a `304 Not Modified` response if the request specifies a
# matching ETag and `If-Modified-Since` header. Otherwise, it will render the
# `show` template.
#
# You can also just pass a record:
#
# def show
# @article = Article.find(params[:id])
# fresh_when(@article)
# end
# def show
# @article = Article.find(params[:id])
# fresh_when(@article)
# end
#
# +etag+ will be set to the record, and +last_modified+ will be set to the
# record's +updated_at+.
# `etag` will be set to the record, and `last_modified` will be set to the
# record's `updated_at`.
#
# You can also pass an object that responds to +maximum+, such as a
# collection of records:
# You can also pass an object that responds to `maximum`, such as a collection
# of records:
#
# def index
# @articles = Article.all
# fresh_when(@articles)
# end
# def index
# @articles = Article.all
# fresh_when(@articles)
# end
#
# In this case, +etag+ will be set to the collection, and +last_modified+
# will be set to <tt>maximum(:updated_at)</tt> (the timestamp of the most
# recently updated record).
# In this case, `etag` will be set to the collection, and `last_modified` will
# be set to `maximum(:updated_at)` (the timestamp of the most recently updated
# record).
#
# When passing a record or a collection, you can still specify other
# options, such as +:public+ and +:cache_control+:
# When passing a record or a collection, you can still specify other options,
# such as `:public` and `:cache_control`:
#
# def show
# @article = Article.find(params[:id])
# fresh_when(@article, public: true, cache_control: { no_cache: true })
# end
# def show
# @article = Article.find(params[:id])
# fresh_when(@article, public: true, cache_control: { no_cache: true })
# end
#
# The above will set <tt>Cache-Control: public, no-cache</tt> in the response.
# The above will set `Cache-Control: public, no-cache` in the response.
#
# When rendering a different template than the controller/action's default
# template, you can indicate which digest to include in the ETag:
#
# before_action { fresh_when @article, template: "widgets/show" }
# before_action { fresh_when @article, template: "widgets/show" }
#
def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, cache_control: {}, template: nil)
response.cache_control.delete(:no_store)
@ -145,131 +149,132 @@ def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_m
head :not_modified if request.fresh?(response)
end
# Sets the +etag+ and/or +last_modified+ on the response and checks them
# against the request. If the request doesn't match the provided options, it
# is considered stale, and the response should be rendered from scratch.
# Otherwise, it is fresh, and a <tt>304 Not Modified</tt> is sent.
# Sets the `etag` and/or `last_modified` on the response and checks them against
# the request. If the request doesn't match the provided options, it is
# considered stale, and the response should be rendered from scratch. Otherwise,
# it is fresh, and a `304 Not Modified` is sent.
#
# ==== Options
# #### Options
#
# See #fresh_when for supported options.
#
# ==== Examples
# #### Examples
#
# def show
# @article = Article.find(params[:id])
# def show
# @article = Article.find(params[:id])
#
# if stale?(etag: @article, last_modified: @article.updated_at)
# @statistics = @article.really_expensive_call
# respond_to do |format|
# # all the supported formats
# if stale?(etag: @article, last_modified: @article.updated_at)
# @statistics = @article.really_expensive_call
# respond_to do |format|
# # all the supported formats
# end
# end
# end
# end
#
# You can also just pass a record:
#
# def show
# @article = Article.find(params[:id])
# def show
# @article = Article.find(params[:id])
#
# if stale?(@article)
# @statistics = @article.really_expensive_call
# respond_to do |format|
# # all the supported formats
# if stale?(@article)
# @statistics = @article.really_expensive_call
# respond_to do |format|
# # all the supported formats
# end
# end
# end
# end
#
# +etag+ will be set to the record, and +last_modified+ will be set to the
# record's +updated_at+.
# `etag` will be set to the record, and `last_modified` will be set to the
# record's `updated_at`.
#
# You can also pass an object that responds to +maximum+, such as a
# collection of records:
# You can also pass an object that responds to `maximum`, such as a collection
# of records:
#
# def index
# @articles = Article.all
# def index
# @articles = Article.all
#
# if stale?(@articles)
# @statistics = @articles.really_expensive_call
# respond_to do |format|
# # all the supported formats
# if stale?(@articles)
# @statistics = @articles.really_expensive_call
# respond_to do |format|
# # all the supported formats
# end
# end
# end
# end
#
# In this case, +etag+ will be set to the collection, and +last_modified+
# will be set to <tt>maximum(:updated_at)</tt> (the timestamp of the most
# recently updated record).
# In this case, `etag` will be set to the collection, and `last_modified` will
# be set to `maximum(:updated_at)` (the timestamp of the most recently updated
# record).
#
# When passing a record or a collection, you can still specify other
# options, such as +:public+ and +:cache_control+:
# When passing a record or a collection, you can still specify other options,
# such as `:public` and `:cache_control`:
#
# def show
# @article = Article.find(params[:id])
# def show
# @article = Article.find(params[:id])
#
# if stale?(@article, public: true, cache_control: { no_cache: true })
# @statistics = @articles.really_expensive_call
# respond_to do |format|
# # all the supported formats
# if stale?(@article, public: true, cache_control: { no_cache: true })
# @statistics = @articles.really_expensive_call
# respond_to do |format|
# # all the supported formats
# end
# end
# end
# end
#
# The above will set <tt>Cache-Control: public, no-cache</tt> in the response.
# The above will set `Cache-Control: public, no-cache` in the response.
#
# When rendering a different template than the controller/action's default
# template, you can indicate which digest to include in the ETag:
#
# def show
# super if stale?(@article, template: "widgets/show")
# end
# def show
# super if stale?(@article, template: "widgets/show")
# end
#
def stale?(object = nil, **freshness_kwargs)
fresh_when(object, **freshness_kwargs)
!request.fresh?(response)
end
# Sets the +Cache-Control+ header, overwriting existing directives. This
# method will also ensure an HTTP +Date+ header for client compatibility.
# Sets the `Cache-Control` header, overwriting existing directives. This method
# will also ensure an HTTP `Date` header for client compatibility.
#
# Defaults to issuing the +private+ directive, so that intermediate caches
# must not cache the response.
# Defaults to issuing the `private` directive, so that intermediate caches must
# not cache the response.
#
# ==== Options
# #### Options
#
# [+:public+]
# If true, replaces the default +private+ directive with the +public+
# directive.
# [+:must_revalidate+]
# If true, adds the +must-revalidate+ directive.
# [+:stale_while_revalidate+]
# Sets the value of the +stale-while-revalidate+ directive.
# [+:stale_if_error+]
# Sets the value of the +stale-if-error+ directive.
# `:public`
# : If true, replaces the default `private` directive with the `public`
# directive.
# `:must_revalidate`
# : If true, adds the `must-revalidate` directive.
# `:stale_while_revalidate`
# : Sets the value of the `stale-while-revalidate` directive.
# `:stale_if_error`
# : Sets the value of the `stale-if-error` directive.
#
# Any additional key-value pairs are concatenated as directives. For a list
# of supported +Cache-Control+ directives, see the {article on
# MDN}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control].
#
# ==== Examples
# Any additional key-value pairs are concatenated as directives. For a list of
# supported `Cache-Control` directives, see the [article on
# MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control).
#
# expires_in 10.minutes
# # => Cache-Control: max-age=600, private
# #### Examples
#
# expires_in 10.minutes, public: true
# # => Cache-Control: max-age=600, public
# expires_in 10.minutes
# # => Cache-Control: max-age=600, private
#
# expires_in 10.minutes, public: true, must_revalidate: true
# # => Cache-Control: max-age=600, public, must-revalidate
# expires_in 10.minutes, public: true
# # => Cache-Control: max-age=600, public
#
# expires_in 1.hour, stale_while_revalidate: 60.seconds
# # => Cache-Control: max-age=3600, private, stale-while-revalidate=60
# expires_in 10.minutes, public: true, must_revalidate: true
# # => Cache-Control: max-age=600, public, must-revalidate
#
# expires_in 1.hour, stale_if_error: 5.minutes
# # => Cache-Control: max-age=3600, private, stale-if-error=300
# expires_in 1.hour, stale_while_revalidate: 60.seconds
# # => Cache-Control: max-age=3600, private, stale-while-revalidate=60
#
# expires_in 1.hour, public: true, "s-maxage": 3.hours, "no-transform": true
# # => Cache-Control: max-age=3600, public, s-maxage=10800, no-transform=true
# expires_in 1.hour, stale_if_error: 5.minutes
# # => Cache-Control: max-age=3600, private, stale-if-error=300
#
# expires_in 1.hour, public: true, "s-maxage": 3.hours, "no-transform": true
# # => Cache-Control: max-age=3600, public, s-maxage=10800, no-transform=true
#
def expires_in(seconds, options = {})
response.cache_control.delete(:no_store)
@ -286,8 +291,8 @@ def expires_in(seconds, options = {})
response.date = Time.now unless response.date?
end
# Sets an HTTP 1.1 +Cache-Control+ header of <tt>no-cache</tt>. This means the
# resource will be marked as stale, so clients must always revalidate.
# Sets an HTTP 1.1 `Cache-Control` header of `no-cache`. This means the resource
# will be marked as stale, so clients must always revalidate.
# Intermediate/browser caches may still store the asset.
def expires_now
response.cache_control.replace(no_cache: true)
@ -295,12 +300,13 @@ def expires_now
# Cache or yield the block. The cache is supposed to never expire.
#
# You can use this method when you have an HTTP response that never changes,
# and the browser and proxies should cache it indefinitely.
# You can use this method when you have an HTTP response that never changes, and
# the browser and proxies should cache it indefinitely.
#
# * `public`: By default, HTTP responses are private, cached only on the
# user's web browser. To allow proxies to cache the response, set `true` to
# indicate that they can serve the cached response to all users.
#
# * +public+: By default, HTTP responses are private, cached only on the
# user's web browser. To allow proxies to cache the response, set +true+ to
# indicate that they can serve the cached response to all users.
def http_cache_forever(public: false)
expires_in 100.years, public: public
@ -309,8 +315,8 @@ def http_cache_forever(public: false)
public: public)
end
# Sets an HTTP 1.1 +Cache-Control+ header of <tt>no-store</tt>. This means the
# resource may not be stored in any cache.
# Sets an HTTP 1.1 `Cache-Control` header of `no-store`. This means the resource
# may not be stored in any cache.
def no_store
response.cache_control.replace(no_store: true)
end

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController # :nodoc:
module ContentSecurityPolicy
extend ActiveSupport::Concern
@ -13,29 +15,28 @@ module ContentSecurityPolicy
end
module ClassMethods
# Overrides parts of the globally configured +Content-Security-Policy+
# header:
# Overrides parts of the globally configured `Content-Security-Policy` header:
#
# class PostsController < ApplicationController
# content_security_policy do |policy|
# policy.base_uri "https://www.example.com"
# class PostsController < ApplicationController
# content_security_policy do |policy|
# policy.base_uri "https://www.example.com"
# end
# end
# end
#
# Options can be passed similar to +before_action+. For example, pass
# <tt>only: :index</tt> to override the header on the index action only:
# Options can be passed similar to `before_action`. For example, pass `only:
# :index` to override the header on the index action only:
#
# class PostsController < ApplicationController
# content_security_policy(only: :index) do |policy|
# policy.default_src :self, :https
# class PostsController < ApplicationController
# content_security_policy(only: :index) do |policy|
# policy.default_src :self, :https
# end
# end
# end
#
# Pass +false+ to remove the +Content-Security-Policy+ header:
# Pass `false` to remove the `Content-Security-Policy` header:
#
# class PostsController < ApplicationController
# content_security_policy false, only: :index
# end
# class PostsController < ApplicationController
# content_security_policy false, only: :index
# end
def content_security_policy(enabled = true, **options, &block)
before_action(options) do
if block_given?
@ -50,18 +51,18 @@ def content_security_policy(enabled = true, **options, &block)
end
end
# Overrides the globally configured +Content-Security-Policy-Report-Only+
# Overrides the globally configured `Content-Security-Policy-Report-Only`
# header:
#
# class PostsController < ApplicationController
# content_security_policy_report_only only: :index
# end
# class PostsController < ApplicationController
# content_security_policy_report_only only: :index
# end
#
# Pass +false+ to remove the +Content-Security-Policy-Report-Only+ header:
# Pass `false` to remove the `Content-Security-Policy-Report-Only` header:
#
# class PostsController < ApplicationController
# content_security_policy_report_only false, only: :index
# end
# class PostsController < ApplicationController
# content_security_policy_report_only false, only: :index
# end
def content_security_policy_report_only(report_only = true, **options)
before_action(options) do
request.content_security_policy_report_only = report_only

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController # :nodoc:
module Cookies
extend ActiveSupport::Concern
@ -9,8 +11,8 @@ module Cookies
end
private
# The cookies for the current request. See ActionDispatch::Cookies for
# more information.
# The cookies for the current request. See ActionDispatch::Cookies for more
# information.
def cookies # :doc:
request.cookie_jar
end

@ -1,10 +1,12 @@
# frozen_string_literal: true
# :markup: markdown
require "action_controller/metal/exceptions"
require "action_dispatch/http/content_disposition"
module ActionController # :nodoc:
# = Action Controller Data \Streaming
# # Action Controller Data Streaming
#
# Methods for sending arbitrary data and for streaming files to the browser,
# instead of rendering.
@ -17,57 +19,60 @@ module DataStreaming
DEFAULT_SEND_FILE_DISPOSITION = "attachment" # :nodoc:
private
# Sends the file. This uses a server-appropriate method (such as +X-Sendfile+)
# via the +Rack::Sendfile+ middleware. The header to use is set via
# +config.action_dispatch.x_sendfile_header+.
# Your server can also configure this for you by setting the +X-Sendfile-Type+ header.
# Sends the file. This uses a server-appropriate method (such as `X-Sendfile`)
# via the `Rack::Sendfile` middleware. The header to use is set via
# `config.action_dispatch.x_sendfile_header`. Your server can also configure
# this for you by setting the `X-Sendfile-Type` header.
#
# Be careful to sanitize the path parameter if it is coming from a web
# page. <tt>send_file(params[:path])</tt> allows a malicious user to
# download any file on your server.
# Be careful to sanitize the path parameter if it is coming from a web page.
# `send_file(params[:path])` allows a malicious user to download any file on
# your server.
#
# Options:
# * <tt>:filename</tt> - suggests a filename for the browser to use.
# Defaults to <tt>File.basename(path)</tt>.
# * <tt>:type</tt> - specifies an HTTP content type.
# You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example +:json+.
# If omitted, the type will be inferred from the file extension specified in <tt>:filename</tt>.
# If no content type is registered for the extension, the default type +application/octet-stream+ will be used.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are <tt>"inline"</tt> and <tt>"attachment"</tt> (default).
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
# * <tt>:url_based_filename</tt> - set to +true+ if you want the browser to guess the filename from
# the URL, which is necessary for i18n filenames on certain browsers
# (setting <tt>:filename</tt> overrides this option).
# * `:filename` - suggests a filename for the browser to use. Defaults to
# `File.basename(path)`.
# * `:type` - specifies an HTTP content type. You can specify either a string
# or a symbol for a registered type with `Mime::Type.register`, for example
# `:json`. If omitted, the type will be inferred from the file extension
# specified in `:filename`. If no content type is registered for the
# extension, the default type `application/octet-stream` will be used.
# * `:disposition` - specifies whether the file will be shown inline or
# downloaded. Valid values are `"inline"` and `"attachment"` (default).
# * `:status` - specifies the status code to send with the response. Defaults
# to 200.
# * `:url_based_filename` - set to `true` if you want the browser to guess the
# filename from the URL, which is necessary for i18n filenames on certain
# browsers (setting `:filename` overrides this option).
#
# The default +Content-Type+ and +Content-Disposition+ headers are
# set to download arbitrary binary files in as many browsers as
# possible. IE versions 4, 5, 5.5, and 6 are all known to have
# a variety of quirks (especially when downloading over SSL).
#
# The default `Content-Type` and `Content-Disposition` headers are set to
# download arbitrary binary files in as many browsers as possible. IE versions
# 4, 5, 5.5, and 6 are all known to have a variety of quirks (especially when
# downloading over SSL).
#
# Simple download:
#
# send_file '/path/to.zip'
# send_file '/path/to.zip'
#
# Show a JPEG in the browser:
#
# send_file '/path/to.jpeg', type: 'image/jpeg', disposition: 'inline'
# send_file '/path/to.jpeg', type: 'image/jpeg', disposition: 'inline'
#
# Show a 404 page in the browser:
#
# send_file '/path/to/404.html', type: 'text/html; charset=utf-8', disposition: 'inline', status: 404
# send_file '/path/to/404.html', type: 'text/html; charset=utf-8', disposition: 'inline', status: 404
#
# You can use other <tt>Content-*</tt> HTTP headers to provide additional
# information to the client. See MDN for a
# {list of HTTP headers}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers].
# You can use other `Content-*` HTTP headers to provide additional information
# to the client. See MDN for a [list of HTTP
# headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers).
#
# Also be aware that the document may be cached by proxies and browsers.
# The +Pragma+ and +Cache-Control+ headers declare how the file may be cached
# by intermediaries. They default to require clients to validate with
# the server before releasing cached responses. See
# https://www.mnot.net/cache_docs/ for an overview of web caching and
# {RFC 9111}[https://www.rfc-editor.org/rfc/rfc9111.html#name-cache-control]
# for the +Cache-Control+ header spec.
# Also be aware that the document may be cached by proxies and browsers. The
# `Pragma` and `Cache-Control` headers declare how the file may be cached by
# intermediaries. They default to require clients to validate with the server
# before releasing cached responses. See https://www.mnot.net/cache_docs/ for an
# overview of web caching and [RFC
# 9111](https://www.rfc-editor.org/rfc/rfc9111.html#name-cache-control) for the
# `Cache-Control` header spec.
def send_file(path, options = {}) # :doc:
raise MissingFile, "Cannot read file #{path}" unless File.file?(path) && File.readable?(path)
@ -79,35 +84,39 @@ def send_file(path, options = {}) # :doc:
response.send_file path
end
# Sends the given binary data to the browser. This method is similar to
# <tt>render plain: data</tt>, but also allows you to specify whether
# the browser should display the response as a file attachment (i.e. in a
# download dialog) or as inline data. You may also set the content type,
# the file name, and other things.
# Sends the given binary data to the browser. This method is similar to `render
# plain: data`, but also allows you to specify whether the browser should
# display the response as a file attachment (i.e. in a download dialog) or as
# inline data. You may also set the content type, the file name, and other
# things.
#
# Options:
# * <tt>:filename</tt> - suggests a filename for the browser to use.
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to +application/octet-stream+.
# You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example +:json+.
# If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>.
# If no content type is registered for the extension, the default type +application/octet-stream+ will be used.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are <tt>"inline"</tt> and <tt>"attachment"</tt> (default).
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
# * `:filename` - suggests a filename for the browser to use.
# * `:type` - specifies an HTTP content type. Defaults to
# `application/octet-stream`. You can specify either a string or a symbol
# for a registered type with `Mime::Type.register`, for example `:json`. If
# omitted, type will be inferred from the file extension specified in
# `:filename`. If no content type is registered for the extension, the
# default type `application/octet-stream` will be used.
# * `:disposition` - specifies whether the file will be shown inline or
# downloaded. Valid values are `"inline"` and `"attachment"` (default).
# * `:status` - specifies the status code to send with the response. Defaults
# to 200.
#
#
# Generic data download:
#
# send_data buffer
# send_data buffer
#
# Download a dynamically-generated tarball:
#
# send_data generate_tgz('dir'), filename: 'dir.tgz'
# send_data generate_tgz('dir'), filename: 'dir.tgz'
#
# Display an image Active Record in the browser:
#
# send_data image.data, type: image.content_type, disposition: 'inline'
# send_data image.data, type: image.content_type, disposition: 'inline'
#
# See +send_file+ for more information on HTTP <tt>Content-*</tt> headers and caching.
# See `send_file` for more information on HTTP `Content-*` headers and caching.
def send_data(data, options = {}) # :doc:
send_file_headers! options
render options.slice(:status, :content_type).merge(body: data)

@ -1,10 +1,12 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
# = Action Controller Default Headers
# # Action Controller Default Headers
#
# Allows configuring default headers that will be automatically merged into
# each response.
# Allows configuring default headers that will be automatically merged into each
# response.
module DefaultHeaders
extend ActiveSupport::Concern

@ -1,7 +1,9 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
# = Action Controller Etag With \Flash
# # Action Controller Etag With Flash
#
# When you're using the flash, it's generally used as a conditional on the view.
# This means the content of the view depends on the flash. Which in turn means

@ -1,24 +1,26 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
# = Action Controller Etag With Template \Digest
# # Action Controller Etag With Template Digest
#
# When our views change, they should bubble up into HTTP cache freshness
# and bust browser caches. So the template digest for the current action
# is automatically included in the ETag.
# When our views change, they should bubble up into HTTP cache freshness and
# bust browser caches. So the template digest for the current action is
# automatically included in the ETag.
#
# Enabled by default for apps that use Action View. Disable by setting
#
# config.action_controller.etag_with_template_digest = false
# config.action_controller.etag_with_template_digest = false
#
# Override the template to digest by passing +:template+ to +fresh_when+
# and +stale?+ calls. For example:
# Override the template to digest by passing `:template` to `fresh_when` and
# `stale?` calls. For example:
#
# # We're going to render widgets/show, not posts/show
# fresh_when @post, template: 'widgets/show'
# # We're going to render widgets/show, not posts/show
# fresh_when @post, template: 'widgets/show'
#
# # We're not going to render a template, so omit it from the ETag.
# fresh_when @post, template: false
# # We're not going to render a template, so omit it from the ETag.
# fresh_when @post, template: false
#
module EtagWithTemplateDigest
extend ActiveSupport::Concern
@ -40,10 +42,10 @@ def determine_template_etag(options)
end
end
# Pick the template digest to include in the ETag. If the +:template+ option
# is present, use the named template. If +:template+ is +nil+ or absent, use
# the default controller/action template. If +:template+ is false, omit the
# template digest from the ETag.
# Pick the template digest to include in the ETag. If the `:template` option is
# present, use the named template. If `:template` is `nil` or absent, use the
# default controller/action template. If `:template` is false, omit the template
# digest from the ETag.
def pick_template_for_etag(options)
unless options[:template] == false
options[:template] || lookup_context.find_all(action_name, _prefixes).first&.virtual_path

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
class ActionControllerError < StandardError # :nodoc:
end
@ -73,16 +75,16 @@ class UnknownHttpMethod < ActionControllerError # :nodoc:
class UnknownFormat < ActionControllerError # :nodoc:
end
# Raised when a nested respond_to is triggered and the content types of each
# are incompatible. For example:
# Raised when a nested respond_to is triggered and the content types of each are
# incompatible. For example:
#
# respond_to do |outer_type|
# outer_type.js do
# respond_to do |inner_type|
# inner_type.html { render body: "HTML" }
# end
# end
# end
# respond_to do |outer_type|
# outer_type.js do
# respond_to do |inner_type|
# inner_type.html { render body: "HTML" }
# end
# end
# end
class RespondToMismatchError < ActionControllerError
DEFAULT_MESSAGE = "respond_to was called multiple times and matched with conflicting formats in this action. Please note that you may only call respond_to and match on a single format per action."

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController # :nodoc:
module Flash
extend ActiveSupport::Concern
@ -13,19 +15,19 @@ module Flash
module ClassMethods
# Creates new flash types. You can pass as many types as you want to create
# flash types other than the default <tt>alert</tt> and <tt>notice</tt> in
# your controllers and views. For instance:
# flash types other than the default `alert` and `notice` in your controllers
# and views. For instance:
#
# # in application_controller.rb
# class ApplicationController < ActionController::Base
# add_flash_types :warning
# end
# # in application_controller.rb
# class ApplicationController < ActionController::Base
# add_flash_types :warning
# end
#
# # in your controller
# redirect_to user_path(@user), warning: "Incomplete profile"
# # in your controller
# redirect_to user_path(@user), warning: "Incomplete profile"
#
# # in your view
# <%= warning %>
# # in your view
# <%= warning %>
#
# This method will automatically define a new method for each of the given
# names, and it will be available in your views.

@ -1,23 +1,25 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
module Head
# 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:
# 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:
#
# head :created, location: person_path(@person)
# head :created, location: person_path(@person)
#
# head :created, location: @person
# head :created, location: @person
#
# It can also be used to return exceptional conditions:
#
# return head(:method_not_allowed) unless request.post?
# return head(:bad_request) unless valid_request?
# render
# return head(:method_not_allowed) unless request.post?
# return head(:bad_request) unless valid_request?
# render
#
# See +Rack::Utils::SYMBOL_TO_STATUS_CODE+ for a full list of valid +status+ symbols.
# See `Rack::Utils::SYMBOL_TO_STATUS_CODE` for a full list of valid `status`
# symbols.
def head(status, options = nil)
if status.is_a?(Hash)
raise ArgumentError, "#{status.inspect} is not a valid value for `status`."

@ -1,59 +1,64 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
# = Action Controller \Helpers
# # Action Controller Helpers
#
# The \Rails framework provides a large number of helpers for working with assets, dates, forms,
# numbers and model objects, to name a few. These helpers are available to all templates
# by default.
# The Rails framework provides a large number of helpers for working with
# assets, dates, forms, numbers and model objects, to name a few. These helpers
# are available to all templates by default.
#
# In addition to using the standard template helpers provided, creating custom helpers to
# extract complicated logic or reusable functionality is strongly encouraged. By default, each controller
# will include all helpers. These helpers are only accessible on the controller through <tt>#helpers</tt>
# In addition to using the standard template helpers provided, creating custom
# helpers to extract complicated logic or reusable functionality is strongly
# encouraged. By default, each controller will include all helpers. These
# helpers are only accessible on the controller through `#helpers`
#
# In previous versions of \Rails the controller will include a helper which
# matches the name of the controller, e.g., <tt>MyController</tt> will automatically
# include <tt>MyHelper</tt>. You can revert to the old behavior with the following:
# In previous versions of Rails the controller will include a helper which
# matches the name of the controller, e.g., `MyController` will automatically
# include `MyHelper`. You can revert to the old behavior with the following:
#
# # config/application.rb
# class Application < Rails::Application
# config.action_controller.include_all_helpers = false
# end
#
# Additional helpers can be specified using the +helper+ class method in ActionController::Base or any
# controller which inherits from it.
#
# The +to_s+ method from the \Time class can be wrapped in a helper method to display a custom message if
# a \Time object is blank:
#
# module FormattedTimeHelper
# def format_time(time, format=:long, blank_message="&nbsp;")
# time.blank? ? blank_message : time.to_fs(format)
# # config/application.rb
# class Application < Rails::Application
# config.action_controller.include_all_helpers = false
# end
# end
#
# FormattedTimeHelper can now be included in a controller, using the +helper+ class method:
# Additional helpers can be specified using the `helper` class method in
# ActionController::Base or any controller which inherits from it.
#
# class EventsController < ActionController::Base
# helper FormattedTimeHelper
# def index
# @events = Event.all
# The `to_s` method from the Time class can be wrapped in a helper method to
# display a custom message if a Time object is blank:
#
# module FormattedTimeHelper
# def format_time(time, format=:long, blank_message="&nbsp;")
# time.blank? ? blank_message : time.to_fs(format)
# end
# end
# end
#
# Then, in any view rendered by <tt>EventsController</tt>, the <tt>format_time</tt> method can be called:
# FormattedTimeHelper can now be included in a controller, using the `helper`
# class method:
#
# <% @events.each do |event| -%>
# <p>
# <%= format_time(event.time, :short, "N/A") %> | <%= event.name %>
# </p>
# <% end -%>
# class EventsController < ActionController::Base
# helper FormattedTimeHelper
# def index
# @events = Event.all
# end
# end
#
# Finally, assuming we have two event instances, one which has a time and one which does not,
# the output might look like this:
# Then, in any view rendered by `EventsController`, the `format_time` method can
# be called:
#
# 23 Aug 11:30 | Carolina Railhawks Soccer Match
# N/A | Carolina Railhawks Training Workshop
# <% @events.each do |event| -%>
# <p>
# <%= format_time(event.time, :short, "N/A") %> | <%= event.name %>
# </p>
# <% end -%>
#
# Finally, assuming we have two event instances, one which has a time and one
# which does not, the output might look like this:
#
# 23 Aug 11:30 | Carolina Railhawks Soccer Match
# N/A | Carolina Railhawks Training Workshop
#
module Helpers
extend ActiveSupport::Concern
@ -68,23 +73,24 @@ class << self; attr_accessor :helpers_path; end
module ClassMethods
# Declares helper accessors for controller attributes. For example, the
# following adds new +name+ and <tt>name=</tt> instance methods to a
# controller and makes them available to the view:
# attr_accessor :name
# helper_attr :name
# following adds new `name` and `name=` instance methods to a controller and
# makes them available to the view:
# attr_accessor :name
# helper_attr :name
#
# #### Parameters
# * `attrs` - Names of attributes to be converted into helpers.
#
# ==== Parameters
# * <tt>attrs</tt> - Names of attributes to be converted into helpers.
def helper_attr(*attrs)
attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
end
# Provides a proxy to access helper methods from outside the view.
#
# Note that the proxy is rendered under a different view context.
# This may cause incorrect behavior with capture methods. Consider
# using {helper}[rdoc-ref:AbstractController::Helpers::ClassMethods#helper]
# instead when using +capture+.
# Note that the proxy is rendered under a different view context. This may cause
# incorrect behavior with capture methods. Consider using
# [helper](rdoc-ref:AbstractController::Helpers::ClassMethods#helper) instead
# when using `capture`.
def helpers
@helper_proxy ||= begin
proxy = ActionView::Base.empty
@ -93,21 +99,23 @@ def helpers
end
end
# Override modules_for_helpers to accept +:all+ as argument, which loads
# all helpers in helpers_path.
# Override modules_for_helpers to accept `:all` as argument, which loads all
# helpers in helpers_path.
#
# ==== Parameters
# * <tt>args</tt> - A list of helpers
# #### Parameters
# * `args` - A list of helpers
#
#
# #### Returns
# * `array` - A normalized list of modules for the list of helpers provided.
#
# ==== Returns
# * <tt>array</tt> - A normalized list of modules for the list of helpers provided.
def modules_for_helpers(args)
args += all_application_helpers if args.delete(:all)
super(args)
end
private
# Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt>
# Extract helper names from files in `app/helpers/***/**_helper.rb`
def all_application_helpers
all_helpers_from_path(helpers_path)
end

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "base64"
require "active_support/security_utils"
require "active_support/core_ext/array/access"
@ -7,62 +9,63 @@
module ActionController
# HTTP Basic, Digest, and Token authentication.
module HttpAuthentication
# = HTTP \Basic authentication
# # HTTP Basic authentication
#
# === Simple \Basic example
# ### Simple Basic example
#
# class PostsController < ApplicationController
# http_basic_authenticate_with name: "dhh", password: "secret", except: :index
# class PostsController < ApplicationController
# http_basic_authenticate_with name: "dhh", password: "secret", except: :index
#
# def index
# render plain: "Everyone can see me!"
# end
#
# def edit
# render plain: "I'm only accessible if you know the password"
# end
# end
#
# === Advanced \Basic example
#
# Here is a more advanced \Basic example where only Atom feeds and the XML API are protected by HTTP authentication.
# The regular HTML interface is protected by a session approach:
#
# class ApplicationController < ActionController::Base
# before_action :set_account, :authenticate
#
# private
# def set_account
# @account = Account.find_by(url_name: request.subdomains.first)
# def index
# render plain: "Everyone can see me!"
# end
#
# def authenticate
# case request.format
# when Mime[:xml], Mime[:atom]
# if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
# @current_user = user
# def edit
# render plain: "I'm only accessible if you know the password"
# end
# end
#
# ### Advanced Basic example
#
# Here is a more advanced Basic example where only Atom feeds and the XML API
# are protected by HTTP authentication. The regular HTML interface is protected
# by a session approach:
#
# class ApplicationController < ActionController::Base
# before_action :set_account, :authenticate
#
# private
# def set_account
# @account = Account.find_by(url_name: request.subdomains.first)
# end
#
# def authenticate
# case request.format
# when Mime[:xml], Mime[:atom]
# if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
# @current_user = user
# else
# request_http_basic_authentication
# end
# else
# request_http_basic_authentication
# end
# else
# if session_authenticated?
# @current_user = @account.users.find(session[:authenticated][:user_id])
# else
# redirect_to(login_url) and return false
# if session_authenticated?
# @current_user = @account.users.find(session[:authenticated][:user_id])
# else
# redirect_to(login_url) and return false
# end
# end
# end
# end
# end
# end
#
# In your integration tests, you can do something like this:
#
# def test_access_granted_from_xml
# authorization = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
# def test_access_granted_from_xml
# authorization = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
#
# get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
# get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
#
# assert_equal 200, status
# end
# assert_equal 200, status
# end
module Basic
extend self
@ -70,7 +73,7 @@ module ControllerMethods
extend ActiveSupport::Concern
module ClassMethods
# Enables HTTP \Basic authentication.
# Enables HTTP Basic authentication.
#
# See ActionController::HttpAuthentication::Basic for example usage.
def http_basic_authenticate_with(name:, password:, realm: nil, **options)
@ -82,8 +85,8 @@ def http_basic_authenticate_with(name:, password:, realm: nil, **options)
def http_basic_authenticate_or_request_with(name:, password:, realm: nil, message: nil)
authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password|
# This comparison uses & so that it doesn't short circuit and
# uses `secure_compare` so that length information isn't leaked.
# This comparison uses & so that it doesn't short circuit and uses
# `secure_compare` so that length information isn't leaked.
ActiveSupport::SecurityUtils.secure_compare(given_name.to_s, name) &
ActiveSupport::SecurityUtils.secure_compare(given_password.to_s, password)
end
@ -140,67 +143,68 @@ def authentication_request(controller, realm, message)
end
end
# = HTTP \Digest authentication
# # HTTP Digest authentication
#
# === Simple \Digest example
# ### Simple Digest example
#
# require "openssl"
# class PostsController < ApplicationController
# REALM = "SuperSecret"
# USERS = {"dhh" => "secret", #plain text password
# "dap" => OpenSSL::Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
# require "openssl"
# class PostsController < ApplicationController
# REALM = "SuperSecret"
# USERS = {"dhh" => "secret", #plain text password
# "dap" => OpenSSL::Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
#
# before_action :authenticate, except: [:index]
# before_action :authenticate, except: [:index]
#
# def index
# render plain: "Everyone can see me!"
# end
#
# def edit
# render plain: "I'm only accessible if you know the password"
# end
#
# private
# def authenticate
# authenticate_or_request_with_http_digest(REALM) do |username|
# USERS[username]
# end
# def index
# render plain: "Everyone can see me!"
# end
# end
#
# === Notes
# def edit
# render plain: "I'm only accessible if you know the password"
# end
#
# The +authenticate_or_request_with_http_digest+ block must return the user's password
# or the ha1 digest hash so the framework can appropriately hash to check the user's
# credentials. Returning +nil+ will cause authentication to fail.
# private
# def authenticate
# authenticate_or_request_with_http_digest(REALM) do |username|
# USERS[username]
# end
# end
# end
#
# Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If
# the password file or database is compromised, the attacker would be able to use the ha1 hash to
# authenticate as the user at this +realm+, but would not have the user's password to try using at
# other sites.
# ### Notes
#
# In rare instances, web servers or front proxies strip authorization headers before
# they reach your application. You can debug this situation by logging all environment
# variables, and check for HTTP_AUTHORIZATION, amongst others.
# The `authenticate_or_request_with_http_digest` block must return the user's
# password or the ha1 digest hash so the framework can appropriately hash to
# check the user's credentials. Returning `nil` will cause authentication to
# fail.
#
# Storing the ha1 hash: MD5(username:realm:password), is better than storing a
# plain password. If the password file or database is compromised, the attacker
# would be able to use the ha1 hash to authenticate as the user at this `realm`,
# but would not have the user's password to try using at other sites.
#
# In rare instances, web servers or front proxies strip authorization headers
# before they reach your application. You can debug this situation by logging
# all environment variables, and check for HTTP_AUTHORIZATION, amongst others.
module Digest
extend self
module ControllerMethods
# Authenticate using an HTTP \Digest, or otherwise render an HTTP header
# requesting the client to send a \Digest.
# Authenticate using an HTTP Digest, or otherwise render an HTTP header
# requesting the client to send a Digest.
#
# See ActionController::HttpAuthentication::Digest for example usage.
def authenticate_or_request_with_http_digest(realm = "Application", message = nil, &password_procedure)
authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm, message)
end
# Authenticate using an HTTP \Digest. Returns true if authentication is
# Authenticate using an HTTP Digest. Returns true if authentication is
# successful, false otherwise.
def authenticate_with_http_digest(realm = "Application", &password_procedure)
HttpAuthentication::Digest.authenticate(request, realm, &password_procedure)
end
# Render an HTTP header requesting the client to send a \Digest for
# Render an HTTP header requesting the client to send a Digest for
# authentication.
def request_http_digest_authentication(realm = "Application", message = nil)
HttpAuthentication::Digest.authentication_request(self, realm, message)
@ -212,9 +216,9 @@ def authenticate(request, realm, &password_procedure)
request.authorization && validate_digest_response(request, realm, &password_procedure)
end
# Returns false unless the request credentials response value matches the expected value.
# First try the password as a ha1 digest password. If this fails, then try it as a plain
# text password.
# Returns false unless the request credentials response value matches the
# expected value. First try the password as a ha1 digest password. If this
# fails, then try it as a plain text password.
def validate_digest_response(request, realm, &password_procedure)
secret_key = secret_token(request)
credentials = decode_credentials_header(request)
@ -237,9 +241,10 @@ def validate_digest_response(request, realm, &password_procedure)
end
end
# Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
# Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead
# of a plain-text password.
# Returns the expected response for a request of `http_method` to `uri` with the
# decoded `credentials` and the expected `password` Optional parameter
# `password_is_ha1` is set to `true` by default, since best practice is to store
# ha1 digest instead of a plain-text password.
def expected_response(http_method, uri, credentials, password, password_is_ha1 = true)
ha1 = password_is_ha1 ? password : ha1(credentials, password)
ha2 = OpenSSL::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
@ -288,36 +293,40 @@ def secret_token(request)
# Uses an MD5 digest based on time to generate a value to be used only once.
#
# A server-specified data string which should be uniquely generated each time a 401 response is made.
# It is recommended that this string be base64 or hexadecimal data.
# Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed.
# A server-specified data string which should be uniquely generated each time a
# 401 response is made. It is recommended that this string be base64 or
# hexadecimal data. Specifically, since the string is passed in the header lines
# as a quoted string, the double-quote character is not allowed.
#
# The contents of the nonce are implementation dependent.
# The quality of the implementation depends on a good choice.
# A nonce might, for example, be constructed as the base 64 encoding of
# The contents of the nonce are implementation dependent. The quality of the
# implementation depends on a good choice. A nonce might, for example, be
# constructed as the base 64 encoding of
#
# time-stamp H(time-stamp ":" ETag ":" private-key)
# time-stamp H(time-stamp ":" ETag ":" private-key)
#
# where time-stamp is a server-generated time or other non-repeating value,
# ETag is the value of the HTTP ETag header associated with the requested entity,
# and private-key is data known only to the server.
# With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and
# reject the request if it did not match the nonce from that header or
# if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity.
# The inclusion of the ETag prevents a replay request for an updated version of the resource.
# (Note: including the IP address of the client in the nonce would appear to offer the server the ability
# to limit the reuse of the nonce to the same client that originally got it.
# However, that would break proxy farms, where requests from a single user often go through different proxies in the farm.
# Also, IP address spoofing is not that hard.)
# where time-stamp is a server-generated time or other non-repeating value, ETag
# is the value of the HTTP ETag header associated with the requested entity, and
# private-key is data known only to the server. With a nonce of this form a
# server would recalculate the hash portion after receiving the client
# authentication header and reject the request if it did not match the nonce
# from that header or if the time-stamp value is not recent enough. In this way
# the server can limit the time of the nonce's validity. The inclusion of the
# ETag prevents a replay request for an updated version of the resource. (Note:
# including the IP address of the client in the nonce would appear to offer the
# server the ability to limit the reuse of the nonce to the same client that
# originally got it. However, that would break proxy farms, where requests from
# a single user often go through different proxies in the farm. Also, IP address
# spoofing is not that hard.)
#
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
# POST, PUT, or PATCH requests, and a time-stamp for GET requests. For more details on the issues involved see Section 4
# of this document.
# An implementation might choose not to accept a previously used nonce or a
# previously used digest, in order to protect against a replay attack. Or, an
# implementation might choose to use one-time nonces or digests for POST, PUT,
# or PATCH requests, and a time-stamp for GET requests. For more details on the
# issues involved see Section 4 of this document.
#
# The nonce is opaque to the client. Composed of Time, and hash of Time with secret
# key from the \Rails session secret generated upon creation of project. Ensures
# the time cannot be modified by client.
# The nonce is opaque to the client. Composed of Time, and hash of Time with
# secret key from the Rails session secret generated upon creation of project.
# Ensures the time cannot be modified by client.
def nonce(secret_key, time = Time.now)
t = time.to_i
hashed = [t, secret_key]
@ -325,11 +334,10 @@ def nonce(secret_key, time = Time.now)
::Base64.strict_encode64("#{t}:#{digest}")
end
# Might want a shorter timeout depending on whether the request
# is a PATCH, PUT, or POST, and if the client is a browser or web service.
# Can be much shorter if the Stale directive is implemented. This would
# allow a user to use new nonce without prompting the user again for their
# username and password.
# Might want a shorter timeout depending on whether the request is a PATCH, PUT,
# or POST, and if the client is a browser or web service. Can be much shorter if
# the Stale directive is implemented. This would allow a user to use new nonce
# without prompting the user again for their username and password.
def validate_nonce(secret_key, request, value, seconds_to_timeout = 5 * 60)
return false if value.nil?
t = ::Base64.decode64(value).split(":").first.to_i
@ -342,80 +350,78 @@ def opaque(secret_key)
end
end
# = HTTP \Token authentication
# # HTTP Token authentication
#
# === Simple \Token example
# ### Simple Token example
#
# class PostsController < ApplicationController
# TOKEN = "secret"
# class PostsController < ApplicationController
# TOKEN = "secret"
#
# before_action :authenticate, except: [ :index ]
# before_action :authenticate, except: [ :index ]
#
# def index
# render plain: "Everyone can see me!"
# end
#
# def edit
# render plain: "I'm only accessible if you know the password"
# end
#
# private
# def authenticate
# authenticate_or_request_with_http_token do |token, options|
# # Compare the tokens in a time-constant manner, to mitigate
# # timing attacks.
# ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
# end
# end
# end
#
#
# Here is a more advanced Token example where only Atom feeds and the XML API are protected by HTTP token authentication.
# The regular HTML interface is protected by a session approach:
#
# class ApplicationController < ActionController::Base
# before_action :set_account, :authenticate
#
# private
# def set_account
# @account = Account.find_by(url_name: request.subdomains.first)
# def index
# render plain: "Everyone can see me!"
# end
#
# def authenticate
# case request.format
# when Mime[:xml], Mime[:atom]
# if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) }
# @current_user = user
# else
# request_http_token_authentication
# end
# else
# if session_authenticated?
# @current_user = @account.users.find(session[:authenticated][:user_id])
# else
# redirect_to(login_url) and return false
# def edit
# render plain: "I'm only accessible if you know the password"
# end
#
# private
# def authenticate
# authenticate_or_request_with_http_token do |token, options|
# # Compare the tokens in a time-constant manner, to mitigate
# # timing attacks.
# ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
# end
# end
# end
# end
# end
#
# Here is a more advanced Token example where only Atom feeds and the XML API
# are protected by HTTP token authentication. The regular HTML interface is
# protected by a session approach:
#
# class ApplicationController < ActionController::Base
# before_action :set_account, :authenticate
#
# private
# def set_account
# @account = Account.find_by(url_name: request.subdomains.first)
# end
#
# def authenticate
# case request.format
# when Mime[:xml], Mime[:atom]
# if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) }
# @current_user = user
# else
# request_http_token_authentication
# end
# else
# if session_authenticated?
# @current_user = @account.users.find(session[:authenticated][:user_id])
# else
# redirect_to(login_url) and return false
# end
# end
# end
# end
#
# In your integration tests, you can do something like this:
#
# def test_access_granted_from_xml
# authorization = ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
# def test_access_granted_from_xml
# authorization = ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
#
# get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
# get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
#
# assert_equal 200, status
# end
# assert_equal 200, status
# end
#
#
# On shared hosts, Apache sometimes doesn't pass authentication headers to
# FCGI instances. If your environment matches this description and you cannot
# On shared hosts, Apache sometimes doesn't pass authentication headers to FCGI
# instances. If your environment matches this description and you cannot
# authenticate, try this rule in your Apache setup:
#
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
module Token
TOKEN_KEY = "token="
TOKEN_REGEX = /^(Token|Bearer)\s+/
@ -423,19 +429,18 @@ module Token
extend self
module ControllerMethods
# Authenticate using an HTTP Bearer token, or otherwise render an HTTP
# header requesting the client to send a Bearer token. For the authentication
# to be considered successful, +login_procedure+ should return a non-nil
# value. Typically, the authenticated user is returned.
# Authenticate using an HTTP Bearer token, or otherwise render an HTTP header
# requesting the client to send a Bearer token. For the authentication to be
# considered successful, `login_procedure` should return a non-nil value.
# Typically, the authenticated user is returned.
#
# See ActionController::HttpAuthentication::Token for example usage.
def authenticate_or_request_with_http_token(realm = "Application", message = nil, &login_procedure)
authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm, message)
end
# Authenticate using an HTTP Bearer token.
# Returns the return value of +login_procedure+ if a
# token is found. Returns +nil+ if no token is found.
# Authenticate using an HTTP Bearer token. Returns the return value of
# `login_procedure` if a token is found. Returns `nil` if no token is found.
#
# See ActionController::HttpAuthentication::Token for example usage.
def authenticate_with_http_token(&login_procedure)
@ -449,19 +454,20 @@ def request_http_token_authentication(realm = "Application", message = nil)
end
end
# If token Authorization header is present, call the login
# procedure with the present token and options.
# If token Authorization header is present, call the login procedure with the
# present token and options.
#
# Returns the return value of +login_procedure+ if a
# token is found. Returns +nil+ if no token is found.
# Returns the return value of `login_procedure` if a token is found. Returns
# `nil` if no token is found.
#
# ==== Parameters
# #### Parameters
#
# * +controller+ - ActionController::Base instance for the current request.
# * +login_procedure+ - Proc to call if a token is present. The Proc
# should take two arguments:
# * `controller` - ActionController::Base instance for the current request.
# * `login_procedure` - Proc to call if a token is present. The Proc should
# take two arguments:
#
# authenticate(controller) { |token, options| ... }
#
# authenticate(controller) { |token, options| ... }
#
def authenticate(controller, &login_procedure)
token, options = token_and_options(controller.request)
@ -470,21 +476,21 @@ def authenticate(controller, &login_procedure)
end
end
# Parses the token and options out of the token Authorization header.
# The value for the Authorization header is expected to have the prefix
# <tt>"Token"</tt> or <tt>"Bearer"</tt>. If the header looks like this:
# Parses the token and options out of the token Authorization header. The value
# for the Authorization header is expected to have the prefix `"Token"` or
# `"Bearer"`. If the header looks like this:
#
# Authorization: Token token="abc", nonce="def"
# Authorization: Token token="abc", nonce="def"
#
# Then the returned token is <tt>"abc"</tt>, and the options are
# <tt>{nonce: "def"}</tt>.
# Then the returned token is `"abc"`, and the options are `{nonce: "def"}`.
#
# Returns an +Array+ of <tt>[String, Hash]</tt> if a token is present.
# Returns +nil+ if no token is found.
# Returns an `Array` of `[String, Hash]` if a token is present. Returns `nil` if
# no token is found.
#
# ==== Parameters
# #### Parameters
#
# * `request` - ActionDispatch::Request instance with the current headers.
#
# * +request+ - ActionDispatch::Request instance with the current headers.
def token_and_options(request)
authorization_request = request.authorization.to_s
if authorization_request[TOKEN_REGEX]
@ -497,12 +503,12 @@ def token_params_from(auth)
rewrite_param_values params_array_from raw_params auth
end
# Takes +raw_params+ and turns it into an array of parameters.
# Takes `raw_params` and turns it into an array of parameters.
def params_array_from(raw_params)
raw_params.map { |param| param.split %r/=(.+)?/ }
end
# This removes the <tt>"</tt> characters wrapping the value.
# This removes the `"` characters wrapping the value.
def rewrite_param_values(array_params)
array_params.each { |param| (param[1] || +"").gsub! %r/^"|"$/, "" }
end
@ -510,9 +516,9 @@ def rewrite_param_values(array_params)
WHITESPACED_AUTHN_PAIR_DELIMITERS = /\s*#{AUTHN_PAIR_DELIMITERS}\s*/
private_constant :WHITESPACED_AUTHN_PAIR_DELIMITERS
# This method takes an authorization body and splits up the key-value
# pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt>
# delimiters defined in +AUTHN_PAIR_DELIMITERS+.
# This method takes an authorization body and splits up the key-value pairs by
# the standardized `:`, `;`, or `\t` delimiters defined in
# `AUTHN_PAIR_DELIMITERS`.
def raw_params(auth)
_raw_params = auth.sub(TOKEN_REGEX, "").split(WHITESPACED_AUTHN_PAIR_DELIMITERS)
_raw_params.reject!(&:empty?)
@ -528,10 +534,11 @@ def raw_params(auth)
#
# Returns String.
#
# ==== Parameters
# #### Parameters
#
# * `token` - String token.
# * `options` - Optional Hash of the options.
#
# * +token+ - String token.
# * +options+ - Optional Hash of the options.
def encode_credentials(token, options = {})
values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value|
"#{key}=#{value.to_s.inspect}"
@ -543,10 +550,11 @@ def encode_credentials(token, options = {})
#
# Returns nothing.
#
# ==== Parameters
# #### Parameters
#
# * `controller` - ActionController::Base instance for the outgoing response.
# * `realm` - String realm to use in the header.
#
# * +controller+ - ActionController::Base instance for the outgoing response.
# * +realm+ - String realm to use in the header.
def authentication_request(controller, realm, message = nil)
message ||= "HTTP Token: Access denied.\n"
controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"', "")}")

@ -1,33 +1,35 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
# = Action Controller Implicit Render
# # Action Controller Implicit Render
#
# Handles implicit rendering for a controller action that does not
# explicitly respond with +render+, +respond_to+, +redirect+, or +head+.
# Handles implicit rendering for a controller action that does not explicitly
# respond with `render`, `respond_to`, `redirect`, or `head`.
#
# For API controllers, the implicit response is always <tt>204 No Content</tt>.
# For API controllers, the implicit response is always `204 No Content`.
#
# For all other controllers, we use these heuristics to decide whether to
# render a template, raise an error for a missing template, or respond with
# <tt>204 No Content</tt>:
# For all other controllers, we use these heuristics to decide whether to render
# a template, raise an error for a missing template, or respond with `204 No
# Content`:
#
# First, if we DO find a template, it's rendered. Template lookup accounts
# for the action name, locales, format, variant, template handlers, and more
# (see +render+ for details).
# First, if we DO find a template, it's rendered. Template lookup accounts for
# the action name, locales, format, variant, template handlers, and more (see
# `render` for details).
#
# Second, if we DON'T find a template but the controller action does have
# templates for other formats, variants, etc., then we trust that you meant
# to provide a template for this response, too, and we raise
# templates for other formats, variants, etc., then we trust that you meant to
# provide a template for this response, too, and we raise
# ActionController::UnknownFormat with an explanation.
#
# Third, if we DON'T find a template AND the request is a page load in a web
# browser (technically, a non-XHR GET request for an HTML response) where
# you reasonably expect to have rendered a template, then we raise
# browser (technically, a non-XHR GET request for an HTML response) where you
# reasonably expect to have rendered a template, then we raise
# ActionController::MissingExactTemplate with an explanation.
#
# Finally, if we DON'T find a template AND the request isn't a browser page
# load, then we implicitly respond with <tt>204 No Content</tt>.
# load, then we implicitly respond with `204 No Content`.
module ImplicitRender
# :stopdoc:
include BasicImplicitRender

@ -1,14 +1,17 @@
# frozen_string_literal: true
# :markup: markdown
require "benchmark"
require "abstract_controller/logger"
module ActionController
# = Action Controller \Instrumentation
# # Action Controller Instrumentation
#
# Adds instrumentation to several ends in ActionController::Base. It also provides
# some hooks related with process_action. This allows an ORM like Active Record
# and/or DataMapper to plug in ActionController and show related information.
# Adds instrumentation to several ends in ActionController::Base. It also
# provides some hooks related with process_action. This allows an ORM like
# Active Record and/or DataMapper to plug in ActionController and show related
# information.
#
# Check ActiveRecord::Railties::ControllerRuntime for an example.
module Instrumentation
@ -91,23 +94,23 @@ def halted_callback_hook(filter, _)
# A hook which allows you to clean up any time, wrongly taken into account in
# views, like database querying time.
#
# def cleanup_view_runtime
# super - time_taken_in_something_expensive
# end
# def cleanup_view_runtime
# super - time_taken_in_something_expensive
# end
def cleanup_view_runtime # :doc:
yield
end
# Every time after an action is processed, this method is invoked
# with the payload, so you can add more information.
# Every time after an action is processed, this method is invoked with the
# payload, so you can add more information.
def append_info_to_payload(payload) # :doc:
payload[:view_runtime] = view_runtime
end
module ClassMethods
# A hook which allows other frameworks to log what happened during
# controller process action. This method should return an array
# with the messages to be added.
# A hook which allows other frameworks to log what happened during controller
# process action. This method should return an array with the messages to be
# added.
def log_process_action(payload) # :nodoc:
messages, view_runtime = [], payload[:view_runtime]
messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime

@ -1,56 +1,58 @@
# frozen_string_literal: true
# :markup: markdown
require "action_dispatch/http/response"
require "delegate"
require "active_support/json"
module ActionController
# = Action Controller \Live
# # Action Controller Live
#
# Mix this module into your controller, and all actions in that controller
# will be able to stream data to the client as it's written.
# Mix this module into your controller, and all actions in that controller will
# be able to stream data to the client as it's written.
#
# class MyController < ActionController::Base
# include ActionController::Live
# class MyController < ActionController::Base
# include ActionController::Live
#
# def stream
# response.headers['Content-Type'] = 'text/event-stream'
# 100.times {
# response.stream.write "hello world\n"
# sleep 1
# }
# ensure
# response.stream.close
# def stream
# response.headers['Content-Type'] = 'text/event-stream'
# 100.times {
# response.stream.write "hello world\n"
# sleep 1
# }
# ensure
# response.stream.close
# end
# end
# end
#
# There are a few caveats with this module. You *cannot* write headers after the
# response has been committed (Response#committed? will return truthy).
# Calling +write+ or +close+ on the response stream will cause the response
# object to be committed. Make sure all headers are set before calling write
# or close on your stream.
# There are a few caveats with this module. You **cannot** write headers after
# the response has been committed (Response#committed? will return truthy).
# Calling `write` or `close` on the response stream will cause the response
# object to be committed. Make sure all headers are set before calling write or
# close on your stream.
#
# You *must* call close on your stream when you're finished, otherwise the
# You **must** call close on your stream when you're finished, otherwise the
# socket may be left open forever.
#
# The final caveat is that your actions are executed in a separate thread than
# the main thread. Make sure your actions are thread safe, and this shouldn't
# be a problem (don't share state across threads, etc).
# the main thread. Make sure your actions are thread safe, and this shouldn't be
# a problem (don't share state across threads, etc).
#
# Note that \Rails includes +Rack::ETag+ by default, which will buffer your
# Note that Rails includes `Rack::ETag` by default, which will buffer your
# response. As a result, streaming responses may not work properly with Rack
# 2.2.x, and you may need to implement workarounds in your application.
# You can either set the +ETag+ or +Last-Modified+ response headers or remove
# +Rack::ETag+ from the middleware stack to address this issue.
# 2.2.x, and you may need to implement workarounds in your application. You can
# either set the `ETag` or `Last-Modified` response headers or remove
# `Rack::ETag` from the middleware stack to address this issue.
#
# Here's an example of how you can set the +Last-Modified+ header if your Rack
# Here's an example of how you can set the `Last-Modified` header if your Rack
# version is 2.2.x:
#
# def stream
# response.headers["Content-Type"] = "text/event-stream"
# response.headers["Last-Modified"] = Time.now.httpdate # Add this line if your Rack version is 2.2.x
# ...
# end
# def stream
# response.headers["Content-Type"] = "text/event-stream"
# response.headers["Last-Modified"] = Time.now.httpdate # Add this line if your Rack version is 2.2.x
# ...
# end
module Live
extend ActiveSupport::Concern
@ -66,44 +68,44 @@ def make_response!(request)
end
end
# = Action Controller \Live Server Sent Events
# # Action Controller Live Server Sent Events
#
# This class provides the ability to write an SSE (Server Sent Event)
# to an IO stream. The class is initialized with a stream and can be used
# to either write a JSON string or an object which can be converted to JSON.
# This class provides the ability to write an SSE (Server Sent Event) to an IO
# stream. The class is initialized with a stream and can be used to either write
# a JSON string or an object which can be converted to JSON.
#
# Writing an object will convert it into standard SSE format with whatever
# options you have configured. You may choose to set the following options:
#
# 1) Event. If specified, an event with this name will be dispatched on
# the browser.
# 2) Retry. The reconnection time in milliseconds used when attempting
# to send the event.
# 3) Id. If the connection dies while sending an SSE to the browser, then
# the server will receive a +Last-Event-ID+ header with value equal to +id+.
# 1) Event. If specified, an event with this name will be dispatched on
# the browser.
# 2) Retry. The reconnection time in milliseconds used when attempting
# to send the event.
# 3) Id. If the connection dies while sending an SSE to the browser, then
# 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 across the stream will use those options unless overridden.
# After setting an option in the constructor of the SSE object, all future SSEs
# sent across the stream will use those options unless overridden.
#
# Example Usage:
#
# class MyController < ActionController::Base
# include ActionController::Live
# class MyController < ActionController::Base
# include ActionController::Live
#
# def index
# response.headers['Content-Type'] = 'text/event-stream'
# sse = SSE.new(response.stream, retry: 300, event: "event-name")
# sse.write({ name: 'John'})
# sse.write({ name: 'John'}, id: 10)
# sse.write({ name: 'John'}, id: 10, event: "other-event")
# sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500)
# ensure
# sse.close
# def index
# response.headers['Content-Type'] = 'text/event-stream'
# sse = SSE.new(response.stream, retry: 300, event: "event-name")
# sse.write({ name: 'John'})
# sse.write({ name: 'John'}, id: 10)
# sse.write({ name: 'John'}, id: 10, event: "other-event")
# sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500)
# ensure
# sse.close
# end
# end
# end
#
# Note: SSEs are not currently supported by IE. However, they are supported
# by Chrome, Firefox, Opera, and Safari.
# Note: SSEs are not currently supported by IE. However, they are supported by
# Chrome, Firefox, Opera, and Safari.
class SSE
PERMITTED_OPTIONS = %w( retry event id )
@ -153,10 +155,9 @@ class << self
# Ignore that the client has disconnected.
#
# If this value is `true`, calling `write` after the client
# disconnects will result in the written content being silently
# discarded. If this value is `false` (the default), a
# ClientDisconnected exception will be raised.
# If this value is `true`, calling `write` after the client disconnects will
# result in the written content being silently discarded. If this value is
# `false` (the default), a ClientDisconnected exception will be raised.
attr_accessor :ignore_disconnect
def initialize(response)
@ -167,9 +168,10 @@ def initialize(response)
@ignore_disconnect = false
end
# ActionDispatch::Response delegates #to_ary to the internal ActionDispatch::Response::Buffer,
# defining #to_ary is an indicator that the response body can be buffered and/or cached by
# Rack middlewares, this is not the case for Live responses so we undefine it for this Buffer subclass.
# ActionDispatch::Response delegates #to_ary to the internal
# ActionDispatch::Response::Buffer, defining #to_ary is an indicator that the
# response body can be buffered and/or cached by Rack middlewares, this is not
# the case for Live responses so we undefine it for this Buffer subclass.
undef_method :to_ary
def write(string)
@ -184,21 +186,20 @@ def write(string)
@buf.clear
unless @ignore_disconnect
# Raise ClientDisconnected, which is a RuntimeError (not an
# IOError), because that's more appropriate for something beyond
# the developer's control.
# Raise ClientDisconnected, which is a RuntimeError (not an IOError), because
# that's more appropriate for something beyond the developer's control.
raise ClientDisconnected, "client disconnected"
end
end
end
# Same as +write+ but automatically include a newline at the end of the string.
# Same as `write` but automatically include a newline at the end of the string.
def writeln(string)
write string.end_with?("\n") ? string : "#{string}\n"
end
# Write a 'close' event to the buffer; the producer/writing thread
# uses this to notify us that it's finished supplying content.
# Write a 'close' event to the buffer; the producer/writing thread uses this to
# notify us that it's finished supplying content.
#
# See also #abort.
def close
@ -209,9 +210,8 @@ def close
end
end
# Inform the producer/writing thread that the client has
# disconnected; the reading thread is no longer interested in
# anything that's being written.
# Inform the producer/writing thread that the client has disconnected; the
# reading thread is no longer interested in anything that's being written.
#
# See also #close.
def abort
@ -223,8 +223,8 @@ def abort
# Is the client still connected and waiting for content?
#
# The result of calling `write` when this is `false` is determined
# by `ignore_disconnect`.
# The result of calling `write` when this is `false` is determined by
# `ignore_disconnect`.
def connected?
!@aborted
end
@ -275,15 +275,15 @@ def process(name)
locals = t1.keys.map { |key| [key, t1[key]] }
error = nil
# This processes the action in a child thread. It lets us return the
# response code and headers back up the Rack stack, and still process
# the body in parallel with sending data to the client.
# This processes the action in a child thread. It lets us return the response
# code and headers back up the Rack stack, and still process the body in
# parallel with sending data to the client.
new_controller_thread {
ActiveSupport::Dependencies.interlock.running do
t2 = Thread.current
# Since we're processing the view in a different thread, copy the
# thread locals from the main thread to the child thread. :'(
# Since we're processing the view in a different thread, copy the thread locals
# from the main thread to the child thread. :'(
locals.each { |k, v| t2[k] = v }
ActiveSupport::IsolatedExecutionState.share_with(t1)
@ -321,27 +321,30 @@ def response_body=(body)
response.close if response
end
# Sends a stream to the browser, which is helpful when you're generating exports or other running data where you
# don't want the entire file buffered in memory first. Similar to send_data, but where the data is generated live.
# Sends a stream to the browser, which is helpful when you're generating exports
# or other running data where you don't want the entire file buffered in memory
# first. Similar to send_data, but where the data is generated live.
#
# Options:
# * <tt>:filename</tt> - suggests a filename for the browser to use.
# * <tt>:type</tt> - specifies an HTTP content type.
# You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
# If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>.
# If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default).
# * `:filename` - suggests a filename for the browser to use.
# * `:type` - specifies an HTTP content type. You can specify either a string
# or a symbol for a registered type with `Mime::Type.register`, for example
# :json. If omitted, type will be inferred from the file extension specified
# in `:filename`. If no content type is registered for the extension, the
# default type 'application/octet-stream' will be used.
# * `:disposition` - specifies whether the file will be shown inline or
# downloaded. Valid values are 'inline' and 'attachment' (default).
#
#
# Example of generating a csv export:
#
# send_stream(filename: "subscribers.csv") do |stream|
# stream.write "email_address,updated_at\n"
# send_stream(filename: "subscribers.csv") do |stream|
# stream.write "email_address,updated_at\n"
#
# @subscribers.find_each do |subscriber|
# stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"
# end
# end
# @subscribers.find_each do |subscriber|
# stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"
# end
# end
def send_stream(filename:, disposition: "attachment", type: nil)
payload = { filename: filename, disposition: disposition, type: type }
ActiveSupport::Notifications.instrument("send_stream.action_controller", payload) do
@ -360,10 +363,10 @@ def send_stream(filename:, disposition: "attachment", type: nil)
end
private
# Spawn a new thread to serve up the controller in. This is to get
# around the fact that Rack isn't based around IOs and we need to use
# a thread to stream data from the response bodies. Nobody should call
# this method except in Rails internals. Seriously!
# Spawn a new thread to serve up the controller in. This is to get around the
# fact that Rack isn't based around IOs and we need to use a thread to stream
# data from the response bodies. Nobody should call this method except in Rails
# internals. Seriously!
def new_controller_thread # :nodoc:
Thread.new {
t2 = Thread.current

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
module Logging
extend ActiveSupport::Concern
@ -7,10 +9,10 @@ module Logging
module ClassMethods
# Set a different log level per request.
#
# # Use the debug log level if a particular cookie is set.
# class ApplicationController < ActionController::Base
# log_at :debug, if: -> { cookies[:debug] }
# end
# # Use the debug log level if a particular cookie is set.
# class ApplicationController < ActionController::Base
# log_at :debug, if: -> { cookies[:debug] }
# end
#
def log_at(level, **options)
around_action ->(_, action) { logger.log_at(level, &action) }, **options

@ -1,203 +1,213 @@
# frozen_string_literal: true
# :markup: markdown
require "abstract_controller/collector"
module ActionController # :nodoc:
module MimeResponds
# Without web-service support, an action which collects the data for displaying a list of people
# might look something like this:
# Without web-service support, an action which collects the data for displaying
# a list of people might look something like this:
#
# def index
# @people = Person.all
# end
# def index
# @people = Person.all
# end
#
# That action implicitly responds to all formats, but formats can also be explicitly enumerated:
# That action implicitly responds to all formats, but formats can also be
# explicitly enumerated:
#
# def index
# @people = Person.all
# respond_to :html, :js
# end
# def index
# @people = Person.all
# respond_to :html, :js
# end
#
# Here's the same action, with web-service support baked in:
#
# def index
# @people = Person.all
# def index
# @people = Person.all
#
# respond_to do |format|
# format.html
# format.js
# format.xml { render xml: @people }
# respond_to do |format|
# format.html
# format.js
# format.xml { render xml: @people }
# end
# end
# end
#
# What that says is, "if the client wants HTML or JS in response to this action, just respond as we
# would have before, but if the client wants XML, return them the list of people in XML format."
# (\Rails determines the desired response format from the HTTP Accept header submitted by the client.)
# What that says is, "if the client wants HTML or JS in response to this action,
# just respond as we would have before, but if the client wants XML, return them
# the list of people in XML format." (Rails determines the desired response
# format from the HTTP Accept header submitted by the client.)
#
# Supposing you have an action that adds a new person, optionally creating their company
# (by name) if it does not already exist, without web-services, it might look like this:
# Supposing you have an action that adds a new person, optionally creating their
# company (by name) if it does not already exist, without web-services, it might
# look like this:
#
# def create
# @company = Company.find_or_create_by(name: params[:company][:name])
# @person = @company.people.create(params[:person])
# def create
# @company = Company.find_or_create_by(name: params[:company][:name])
# @person = @company.people.create(params[:person])
#
# redirect_to(person_list_url)
# end
# redirect_to(person_list_url)
# end
#
# Here's the same action, with web-service support baked in:
#
# def create
# company = params[:person].delete(:company)
# @company = Company.find_or_create_by(name: company[:name])
# @person = @company.people.create(params[:person])
# def create
# company = params[:person].delete(:company)
# @company = Company.find_or_create_by(name: company[:name])
# @person = @company.people.create(params[:person])
#
# respond_to do |format|
# format.html { redirect_to(person_list_url) }
# format.js
# format.xml { render xml: @person.to_xml(include: @company) }
# respond_to do |format|
# format.html { redirect_to(person_list_url) }
# format.js
# format.xml { render xml: @person.to_xml(include: @company) }
# end
# end
# end
#
# If the client wants HTML, we just redirect them back to the person list. If they want JavaScript,
# then it is an Ajax request and we render the JavaScript template associated with this action.
# Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
# include the person's company in the rendered XML, so you get something like this:
# If the client wants HTML, we just redirect them back to the person list. If
# they want JavaScript, then it is an Ajax request and we render the JavaScript
# template associated with this action. Lastly, if the client wants XML, we
# render the created person as XML, but with a twist: we also include the
# person's company in the rendered XML, so you get something like this:
#
# <person>
# <id>...</id>
# ...
# <company>
# <person>
# <id>...</id>
# <name>...</name>
# ...
# </company>
# </person>
# <company>
# <id>...</id>
# <name>...</name>
# ...
# </company>
# </person>
#
# Note, however, the extra bit at the top of that action:
#
# company = params[:person].delete(:company)
# @company = Company.find_or_create_by(name: company[:name])
# company = params[:person].delete(:company)
# @company = Company.find_or_create_by(name: company[:name])
#
# This is because the incoming XML document (if a web-service request is in process) can only contain a
# single root-node. So, we have to rearrange things so that the request looks like this (url-encoded):
# This is because the incoming XML document (if a web-service request is in
# process) can only contain a single root-node. So, we have to rearrange things
# so that the request looks like this (url-encoded):
#
# person[name]=...&person[company][name]=...&...
# person[name]=...&person[company][name]=...&...
#
# And, like this (xml-encoded):
#
# <person>
# <name>...</name>
# <company>
# <person>
# <name>...</name>
# </company>
# </person>
# <company>
# <name>...</name>
# </company>
# </person>
#
# In other words, we make the request so that it operates on a single entity's person. Then, in the action,
# we extract the company data from the request, find or create the company, and then create the new person
# with the remaining data.
# In other words, we make the request so that it operates on a single entity's
# person. Then, in the action, we extract the company data from the request,
# find or create the company, and then create the new person with the remaining
# data.
#
# Note that you can define your own XML parameter parser which would allow you to describe multiple entities
# in a single request (i.e., by wrapping them all in a single root node), but if you just go with the flow
# and accept \Rails' defaults, life will be much easier.
# Note that you can define your own XML parameter parser which would allow you
# to describe multiple entities in a single request (i.e., by wrapping them all
# in a single root node), but if you just go with the flow and accept Rails'
# defaults, life will be much easier.
#
# If you need to use a MIME type which isn't supported by default, you can register your own handlers in
# +config/initializers/mime_types.rb+ as follows.
# If you need to use a MIME type which isn't supported by default, you can
# register your own handlers in `config/initializers/mime_types.rb` as follows.
#
# Mime::Type.register "image/jpeg", :jpg
# Mime::Type.register "image/jpeg", :jpg
#
# +respond_to+ also allows you to specify a common block for different formats by using +any+:
# `respond_to` also allows you to specify a common block for different formats
# by using `any`:
#
# def index
# @people = Person.all
# def index
# @people = Person.all
#
# respond_to do |format|
# format.html
# format.any(:xml, :json) { render request.format.to_sym => @people }
# respond_to do |format|
# format.html
# format.any(:xml, :json) { render request.format.to_sym => @people }
# end
# end
# end
#
# In the example above, if the format is xml, it will render:
#
# render xml: @people
# render xml: @people
#
# Or if the format is json:
#
# render json: @people
# render json: @people
#
# +any+ can also be used with no arguments, in which case it will be used for any format requested by
# the user:
# `any` can also be used with no arguments, in which case it will be used for
# any format requested by the user:
#
# respond_to do |format|
# format.html
# format.any { redirect_to support_path }
# end
# respond_to do |format|
# format.html
# format.any { redirect_to support_path }
# 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>.
# The request variant is a specialization of the request format, like `:tablet`,
# `:phone`, or `:desktop`.
#
# We often want to render different html/json/xml templates for phones,
# tablets, and desktop browsers. Variants make it easy.
# 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+:
# You can set the variant in a `before_action`:
#
# request.variant = :tablet if /iPad/.match?(request.user_agent)
# request.variant = :tablet if /iPad/.match?(request.user_agent)
#
# 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
# 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
# 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
# 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:
# 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
# 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.
# Variants also support common `any`/`all` block that formats have.
#
# It works for both inline:
#
# respond_to do |format|
# format.html.any { render html: "any" }
# format.html.phone { render html: "phone" }
# end
# respond_to do |format|
# format.html.any { render html: "any" }
# format.html.phone { render html: "phone" }
# end
#
# and block syntax:
#
# respond_to do |format|
# format.html do |variant|
# variant.any(:tablet, :phablet){ render html: "any" }
# variant.phone { render html: "phone" }
# respond_to do |format|
# format.html do |variant|
# variant.any(:tablet, :phablet){ render html: "any" }
# variant.phone { render html: "phone" }
# end
# end
# end
#
# You can also set an array of variants:
#
# request.variant = [:tablet, :phone]
# request.variant = [:tablet, :phone]
#
# This will work similarly to formats and MIME types negotiation. If there
# is no +:tablet+ variant declared, the +:phone+ variant will be used:
# This will work similarly to formats and MIME types negotiation. If there is no
# `:tablet` variant declared, the `:phone` variant will be used:
#
# respond_to do |format|
# format.html.none
# format.html.phone # this gets rendered
# end
# respond_to do |format|
# format.html.none
# format.html.phone # this gets rendered
# end
def respond_to(*mimes)
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
@ -217,27 +227,26 @@ def respond_to(*mimes)
end
end
# A container for responses available from the current controller for
# requests for different mime-types sent to a particular action.
# A container for responses available from the current controller for requests
# for different mime-types sent to a particular action.
#
# The public controller methods +respond_to+ may be called with a block
# that is used to define responses to different mime-types, e.g.
# for +respond_to+ :
# The public controller methods `respond_to` may be called with a block that is
# used to define responses to different mime-types, e.g. for `respond_to` :
#
# respond_to do |format|
# format.html
# format.xml { render xml: @people }
# end
# respond_to do |format|
# format.html
# format.xml { render xml: @people }
# end
#
# In this usage, the argument passed to the block (+format+ above) is an
# instance of the ActionController::MimeResponds::Collector class. This
# object serves as a container in which available responses can be stored by
# calling any of the dynamically generated, mime-type-specific methods such
# as +html+, +xml+ etc on the Collector. Each response is represented by a
# corresponding block if present.
# In this usage, the argument passed to the block (`format` above) is an
# instance of the ActionController::MimeResponds::Collector class. This object
# serves as a container in which available responses can be stored by calling
# any of the dynamically generated, mime-type-specific methods such as `html`,
# `xml` etc on the Collector. Each response is represented by a corresponding
# block if present.
#
# A subsequent call to #negotiate_format(request) will enable the Collector
# to determine which specific mime-type it should respond with for the current
# A subsequent call to #negotiate_format(request) will enable the Collector to
# determine which specific mime-type it should respond with for the current
# request, with this response then being accessible by calling #response.
class Collector
include AbstractController::Collector

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
# Specify binary encoding for parameters for a given action.
module ParameterEncoding
@ -21,59 +23,59 @@ def action_encoding_template(action) # :nodoc:
end
end
# Specify that a given action's parameters should all be encoded as
# ASCII-8BIT (it "skips" the encoding default of UTF-8).
# Specify that a given action's parameters should all be encoded as ASCII-8BIT
# (it "skips" the encoding default of UTF-8).
#
# For example, a controller would use it like this:
#
# class RepositoryController < ActionController::Base
# skip_parameter_encoding :show
# class RepositoryController < ActionController::Base
# skip_parameter_encoding :show
#
# def show
# @repo = Repository.find_by_filesystem_path params[:file_path]
# def show
# @repo = Repository.find_by_filesystem_path params[:file_path]
#
# # `repo_name` is guaranteed to be UTF-8, but was ASCII-8BIT, so
# # tag it as such
# @repo_name = params[:repo_name].force_encoding 'UTF-8'
# # `repo_name` is guaranteed to be UTF-8, but was ASCII-8BIT, so
# # tag it as such
# @repo_name = params[:repo_name].force_encoding 'UTF-8'
# end
#
# def index
# @repositories = Repository.all
# end
# end
#
# def index
# @repositories = Repository.all
# end
# end
#
# The show action in the above controller would have all parameter values
# encoded as ASCII-8BIT. This is useful in the case where an application
# must handle data but encoding of the data is unknown, like file system data.
# encoded as ASCII-8BIT. This is useful in the case where an application must
# handle data but encoding of the data is unknown, like file system data.
def skip_parameter_encoding(action)
@_parameter_encodings[action.to_s] = Hash.new { Encoding::ASCII_8BIT }
end
# Specify the encoding for a parameter on an action.
# If not specified the default is UTF-8.
# Specify the encoding for a parameter on an action. If not specified the
# default is UTF-8.
#
# You can specify a binary (ASCII_8BIT) parameter with:
#
# class RepositoryController < ActionController::Base
# # This specifies that file_path is not UTF-8 and is instead ASCII_8BIT
# param_encoding :show, :file_path, Encoding::ASCII_8BIT
# class RepositoryController < ActionController::Base
# # This specifies that file_path is not UTF-8 and is instead ASCII_8BIT
# param_encoding :show, :file_path, Encoding::ASCII_8BIT
#
# def show
# @repo = Repository.find_by_filesystem_path params[:file_path]
# def show
# @repo = Repository.find_by_filesystem_path params[:file_path]
#
# # params[:repo_name] remains UTF-8 encoded
# @repo_name = params[:repo_name]
# # params[:repo_name] remains UTF-8 encoded
# @repo_name = params[:repo_name]
# end
#
# def index
# @repositories = Repository.all
# end
# end
#
# def index
# @repositories = Repository.all
# end
# end
#
# The file_path parameter on the show action would be encoded as ASCII-8BIT,
# but all other arguments will remain UTF-8 encoded.
# This is useful in the case where an application must handle data
# but encoding of the data is unknown, like file system data.
# The file_path parameter on the show action would be encoded as ASCII-8BIT, but
# all other arguments will remain UTF-8 encoded. This is useful in the case
# where an application must handle data but encoding of the data is unknown,
# like file system data.
def param_encoding(action, param, encoding)
@_parameter_encodings[action.to_s][param.to_s] = encoding
end

@ -1,12 +1,14 @@
# frozen_string_literal: true
# :markup: markdown
require "active_support/core_ext/hash/slice"
require "active_support/core_ext/hash/except"
require "active_support/core_ext/module/anonymous"
require "action_dispatch/http/mime_type"
module ActionController
# = Action Controller Params Wrapper
# # Action Controller Params Wrapper
#
# Wraps the parameters hash into a nested hash. This will allow clients to
# submit requests without having to specify any root elements.
@ -24,8 +26,8 @@ module ActionController
# wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]
# end
#
# If you enable +ParamsWrapper+ for +:json+ format, instead of having to
# send JSON parameters like this:
# If you enable `ParamsWrapper` for `:json` format, instead of having to send
# JSON parameters like this:
#
# {"user": {"name": "Konata"}}
#
@ -34,45 +36,44 @@ module ActionController
# {"name": "Konata"}
#
# And it will be wrapped into a nested hash with the key name matching the
# controller's name. For example, if you're posting to +UsersController+,
# your new +params+ hash will look like this:
# controller's name. For example, if you're posting to `UsersController`, your
# new `params` hash will look like this:
#
# {"name" => "Konata", "user" => {"name" => "Konata"}}
#
# You can also specify the key in which the parameters should be wrapped to,
# and also the list of attributes it should wrap by using either +:include+ or
# +:exclude+ options like this:
# You can also specify the key in which the parameters should be wrapped to, and
# also the list of attributes it should wrap by using either `:include` or
# `:exclude` options like this:
#
# class UsersController < ApplicationController
# wrap_parameters :person, include: [:username, :password]
# end
#
# On Active Record models with no +:include+ or +:exclude+ option set,
# it will only wrap the parameters returned by the class method
# <tt>attribute_names</tt>.
# On Active Record models with no `:include` or `:exclude` option set, it will
# only wrap the parameters returned by the class method `attribute_names`.
#
# If you're going to pass the parameters to an +ActiveModel+ object (such as
# <tt>User.new(params[:user])</tt>), you might consider passing the model class to
# the method instead. The +ParamsWrapper+ will actually try to determine the
# list of attribute names from the model and only wrap those attributes:
# If you're going to pass the parameters to an `ActiveModel` object (such as
# `User.new(params[:user])`), you might consider passing the model class to the
# method instead. The `ParamsWrapper` will actually try to determine the list of
# attribute names from the model and only wrap those attributes:
#
# class UsersController < ApplicationController
# wrap_parameters Person
# end
#
# You still could pass +:include+ and +:exclude+ to set the list of attributes
# You still could pass `:include` and `:exclude` to set the list of attributes
# you want to wrap.
#
# By default, if you don't specify the key in which the parameters would be
# wrapped to, +ParamsWrapper+ will actually try to determine if there's
# a model related to it or not. This controller, for example:
# wrapped to, `ParamsWrapper` will actually try to determine if there's a model
# related to it or not. This controller, for example:
#
# class Admin::UsersController < ApplicationController
# end
#
# will try to check if +Admin::User+ or +User+ model exists, and use it to
# determine the wrapper key respectively. If both models don't exist,
# it will then fall back to use +user+ as the key.
# will try to check if `Admin::User` or `User` model exists, and use it to
# determine the wrapper key respectively. If both models don't exist, it will
# then fall back to use `user` as the key.
#
# To disable this functionality for a controller:
#
@ -154,13 +155,13 @@ def name
end
private
# Determine the wrapper model from the controller's name. By convention,
# this could be done by trying to find the defined model that has the
# same singular name as the controller. For example, +UsersController+
# will try to find if the +User+ model exists.
# Determine the wrapper model from the controller's name. By convention, this
# could be done by trying to find the defined model that has the same singular
# name as the controller. For example, `UsersController` will try to find if the
# `User` model exists.
#
# This method also does namespace lookup. Foo::Bar::UsersController will
# try to find Foo::Bar::User, Foo::User and finally User.
# This method also does namespace lookup. Foo::Bar::UsersController will try to
# find Foo::Bar::User, Foo::User and finally User.
def _default_wrap_model
return nil if klass.anonymous?
model_name = klass.name.delete_suffix("Controller").classify
@ -189,33 +190,34 @@ def _set_wrapper_options(options)
self._wrapper_options = Options.from_hash(options)
end
# Sets the name of the wrapper key, or the model which +ParamsWrapper+
# would use to determine the attribute names from.
# Sets the name of the wrapper key, or the model which `ParamsWrapper` would use
# to determine the attribute names from.
#
# ==== Examples
# wrap_parameters format: :xml
# # enables the parameter wrapper for XML format
# #### Examples
# wrap_parameters format: :xml
# # enables the parameter wrapper for XML format
#
# wrap_parameters :person
# # wraps parameters into +params[:person]+ hash
# wrap_parameters :person
# # wraps parameters into +params[:person]+ hash
#
# wrap_parameters Person
# # wraps parameters by determining the wrapper key from Person class
# # (+person+, in this case) and the list of attribute names
# wrap_parameters Person
# # wraps parameters by determining the wrapper key from Person class
# # (+person+, in this case) and the list of attribute names
#
# wrap_parameters include: [:username, :title]
# # wraps only +:username+ and +:title+ attributes from parameters.
# wrap_parameters include: [:username, :title]
# # wraps only +:username+ and +:title+ attributes from parameters.
#
# wrap_parameters false
# # disables parameters wrapping for this controller altogether.
# wrap_parameters false
# # disables parameters wrapping for this controller altogether.
#
# #### Options
# * `:format` - The list of formats in which the parameters wrapper will be
# enabled.
# * `:include` - The list of attribute names which parameters wrapper will
# wrap into a nested hash.
# * `:exclude` - The list of attribute names which parameters wrapper will
# exclude from a nested hash.
#
# ==== Options
# * <tt>:format</tt> - The list of formats in which the parameters wrapper
# will be enabled.
# * <tt>:include</tt> - The list of attribute names which parameters wrapper
# will wrap into a nested hash.
# * <tt>:exclude</tt> - The list of attribute names which parameters wrapper
# will exclude from a nested hash.
def wrap_parameters(name_or_model_or_options, options = {})
model = nil
@ -237,9 +239,8 @@ def wrap_parameters(name_or_model_or_options, options = {})
self._wrapper_options = opts
end
# Sets the default wrapper key or model which will be used to determine
# wrapper key and attribute names. Called automatically when the
# module is inherited.
# Sets the default wrapper key or model which will be used to determine wrapper
# key and attribute names. Called automatically when the module is inherited.
def inherited(klass)
if klass._wrapper_options.format.any?
params = klass._wrapper_options.dup
@ -251,8 +252,8 @@ def inherited(klass)
end
private
# Performs parameters wrapping upon the request. Called automatically
# by the metal call stack.
# Performs parameters wrapping upon the request. Called automatically by the
# metal call stack.
def process_action(*)
_perform_parameter_wrapping if _wrapper_enabled?
super

@ -1,27 +1,28 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController # :nodoc:
module PermissionsPolicy
extend ActiveSupport::Concern
module ClassMethods
# Overrides parts of the globally configured +Feature-Policy+
# header:
# Overrides parts of the globally configured `Feature-Policy` header:
#
# class PagesController < ApplicationController
# permissions_policy do |policy|
# policy.geolocation "https://example.com"
# class PagesController < ApplicationController
# permissions_policy do |policy|
# policy.geolocation "https://example.com"
# end
# end
# end
#
# Options can be passed similar to +before_action+. For example, pass
# <tt>only: :index</tt> to override the header on the index action only:
# Options can be passed similar to `before_action`. For example, pass `only:
# :index` to override the header on the index action only:
#
# class PagesController < ApplicationController
# permissions_policy(only: :index) do |policy|
# policy.camera :self
# class PagesController < ApplicationController
# permissions_policy(only: :index) do |policy|
# policy.camera :self
# end
# end
# end
#
def permissions_policy(**options, &block)
before_action(options) do

@ -1,39 +1,49 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController # :nodoc:
module RateLimiting
extend ActiveSupport::Concern
module ClassMethods
# Applies a rate limit to all actions or those specified by the normal <tt>before_action</tt> filters with <tt>only:</tt> and <tt>except:</tt>.
# Applies a rate limit to all actions or those specified by the normal
# `before_action` filters with `only:` and `except:`.
#
# The maximum number of requests allowed is specified <tt>to:</tt> and constrained to the window of time given by <tt>within:</tt>.
# The maximum number of requests allowed is specified `to:` and constrained to
# the window of time given by `within:`.
#
# Rate limits are by default unique to the ip address making the request, but you can provide your own identity function by passing a callable
# in the <tt>by:</tt> parameter. It's evaluated within the context of the controller processing the request.
# Rate limits are by default unique to the ip address making the request, but
# you can provide your own identity function by passing a callable in the `by:`
# parameter. It's evaluated within the context of the controller processing the
# request.
#
# Requests that exceed the rate limit are refused with a <tt>429 Too Many Requests</tt> response. You can specialize this by passing a callable
# in the <tt>with:</tt> parameter. It's evaluated within the context of the controller processing the request.
# Requests that exceed the rate limit are refused with a `429 Too Many Requests`
# response. You can specialize this by passing a callable in the `with:`
# parameter. It's evaluated within the context of the controller processing the
# request.
#
# Rate limiting relies on a backing <tt>ActiveSupport::Cache</tt> store and defaults to <tt>config.action_controller.cache_store</tt>, which
# itself defaults to the global <tt>config.cache_store</tt>. If you don't want to store rate limits in the same datastore as your general caches,
# you can pass a custom store in the <tt>store</tt> parameter.
# Rate limiting relies on a backing `ActiveSupport::Cache` store and defaults to
# `config.action_controller.cache_store`, which itself defaults to the global
# `config.cache_store`. If you don't want to store rate limits in the same
# datastore as your general caches, you can pass a custom store in the `store`
# parameter.
#
# Examples:
#
# class SessionsController < ApplicationController
# rate_limit to: 10, within: 3.minutes, only: :create
# end
# class SessionsController < ApplicationController
# rate_limit to: 10, within: 3.minutes, only: :create
# end
#
# class SignupsController < ApplicationController
# rate_limit to: 1000, within: 10.seconds,
# by: -> { request.domain }, with: -> { redirect_to busy_controller_url, alert: "Too many signups on domain!" }, only: :new
# end
# class SignupsController < ApplicationController
# rate_limit to: 1000, within: 10.seconds,
# by: -> { request.domain }, with: -> { redirect_to busy_controller_url, alert: "Too many signups on domain!" }, only: :new
# end
#
# class APIController < ApplicationController
# RATE_LIMIT_STORE = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"])
# rate_limit to: 10, within: 3.minutes, store: RATE_LIMIT_STORE
# end
# class APIController < ApplicationController
# RATE_LIMIT_STORE = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"])
# rate_limit to: 10, within: 3.minutes, store: RATE_LIMIT_STORE
# end
def rate_limit(to:, within:, by: -> { request.remote_ip }, with: -> { head :too_many_requests }, store: cache_store, **options)
before_action -> { rate_limiting(to: to, within: within, by: by, with: with, store: store) }, **options
end

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
module Redirecting
extend ActiveSupport::Concern
@ -15,72 +17,89 @@ class UnsafeRedirectError < StandardError; end
mattr_accessor :raise_on_open_redirects, default: false
end
# Redirects the browser to the target specified in +options+. This parameter can be any one of:
# Redirects the browser to the target specified in `options`. This parameter can
# be any one of:
#
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
# * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
# * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) or a protocol relative reference (like <tt>//</tt>) - Is passed straight through as the target for redirection.
# * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string.
# * <tt>Proc</tt> - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+.
# * `Hash` - The URL will be generated by calling url_for with the `options`.
# * `Record` - The URL will be generated by calling url_for with the
# `options`, which will reference a named URL for that record.
# * `String` starting with `protocol://` (like `http://`) or a protocol
# relative reference (like `//`) - Is passed straight through as the target
# for redirection.
# * `String` not containing a protocol - The current protocol and host is
# prepended to the string.
# * `Proc` - A block that will be executed in the controller's context. Should
# return any option accepted by `redirect_to`.
#
# === Examples
#
# redirect_to action: "show", id: 5
# redirect_to @post
# redirect_to "http://www.rubyonrails.org"
# redirect_to "/images/screenshot.jpg"
# redirect_to posts_url
# redirect_to proc { edit_post_url(@post) }
# ### Examples
#
# The redirection happens as a <tt>302 Found</tt> header unless otherwise specified using the <tt>:status</tt> option:
# redirect_to action: "show", id: 5
# redirect_to @post
# redirect_to "http://www.rubyonrails.org"
# redirect_to "/images/screenshot.jpg"
# redirect_to posts_url
# redirect_to proc { edit_post_url(@post) }
#
# redirect_to post_url(@post), status: :found
# redirect_to action: 'atom', status: :moved_permanently
# redirect_to post_url(@post), status: 301
# redirect_to action: 'atom', status: 302
# The redirection happens as a `302 Found` header unless otherwise specified
# using the `:status` option:
#
# The status code can either be a standard {HTTP Status code}[https://www.iana.org/assignments/http-status-codes] as an
# integer, or a symbol representing the downcased, underscored and symbolized description.
# Note that the status code must be a 3xx HTTP code, or redirection will not occur.
# redirect_to post_url(@post), status: :found
# redirect_to action: 'atom', status: :moved_permanently
# redirect_to post_url(@post), status: 301
# redirect_to action: 'atom', status: 302
#
# The status code can either be a standard [HTTP Status
# code](https://www.iana.org/assignments/http-status-codes) as an integer, or a
# symbol representing the downcased, underscored and symbolized description.
# Note that the status code must be a 3xx HTTP code, or redirection will not
# occur.
#
# If you are using XHR requests other than GET or POST and redirecting after the
# request then some browsers will follow the redirect using the original request
# method. This may lead to undesirable behavior such as a double DELETE. To work
# around this you can return a <tt>303 See Other</tt> status code which will be
# around this you can return a `303 See Other` status code which will be
# followed using a GET request.
#
# redirect_to posts_url, status: :see_other
# redirect_to action: 'index', status: 303
# redirect_to posts_url, status: :see_other
# redirect_to action: 'index', status: 303
#
# It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names
# +alert+ and +notice+ as well as a general purpose +flash+ bucket.
# It is also possible to assign a flash message as part of the redirection.
# There are two special accessors for the commonly used flash names `alert` and
# `notice` as well as a general purpose `flash` bucket.
#
# 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 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")
#
# Statements after +redirect_to+ in our controller get executed, so +redirect_to+ doesn't stop the execution of the function.
# To terminate the execution of the function immediately after the +redirect_to+, use return.
# Statements after `redirect_to` in our controller get executed, so
# `redirect_to` doesn't stop the execution of the function. To terminate the
# execution of the function immediately after the `redirect_to`, use return.
#
# redirect_to post_url(@post) and return
# redirect_to post_url(@post) and return
#
# === Open Redirect protection
# ### Open Redirect protection
#
# By default, \Rails protects against redirecting to external hosts for your app's safety, so called open redirects.
# Note: this was a new default in \Rails 7.0, after upgrading opt-in by uncommenting the line with +raise_on_open_redirects+ in <tt>config/initializers/new_framework_defaults_7_0.rb</tt>
# By default, Rails protects against redirecting to external hosts for your
# app's safety, so called open redirects. Note: this was a new default in Rails
# 7.0, after upgrading opt-in by uncommenting the line with
# `raise_on_open_redirects` in
# `config/initializers/new_framework_defaults_7_0.rb`
#
# Here #redirect_to automatically validates the potentially-unsafe URL:
#
# redirect_to params[:redirect_url]
# redirect_to params[:redirect_url]
#
# Raises UnsafeRedirectError in the case of an unsafe redirect.
#
# To allow any external redirects pass <tt>allow_other_host: true</tt>, though using a user-provided param in that case is unsafe.
# To allow any external redirects pass `allow_other_host: true`, though using a
# user-provided param in that case is unsafe.
#
# redirect_to "https://rubyonrails.org", allow_other_host: true
# redirect_to "https://rubyonrails.org", allow_other_host: true
#
# See #url_from for more information on what an internal and safe URL is, or how to fall back to an alternate redirect URL in the unsafe case.
# See #url_from for more information on what an internal and safe URL is, or how
# to fall back to an alternate redirect URL in the unsafe case.
def redirect_to(options = {}, response_options = {})
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
@ -96,50 +115,53 @@ def redirect_to(options = {}, response_options = {})
self.response_body = ""
end
# Soft deprecated alias for #redirect_back_or_to where the +fallback_location+ location is supplied as a keyword argument instead
# of the first positional argument.
# Soft deprecated alias for #redirect_back_or_to where the `fallback_location`
# location is supplied as a keyword argument instead of the first positional
# argument.
def redirect_back(fallback_location:, allow_other_host: _allow_other_host, **args)
redirect_back_or_to fallback_location, allow_other_host: allow_other_host, **args
end
# Redirects the browser to the page that issued the request (the referrer)
# if possible, otherwise redirects to the provided default fallback
# location.
# Redirects the browser to the page that issued the request (the referrer) if
# possible, otherwise redirects to the provided default fallback location.
#
# The referrer information is pulled from the HTTP +Referer+ (sic) header on
# the request. This is an optional header and its presence on the request is
# subject to browser security settings and user preferences. If the request
# is missing this header, the <tt>fallback_location</tt> will be used.
# The referrer information is pulled from the HTTP `Referer` (sic) header on the
# request. This is an optional header and its presence on the request is subject
# to browser security settings and user preferences. If the request is missing
# this header, the `fallback_location` will be used.
#
# redirect_back_or_to({ action: "show", id: 5 })
# redirect_back_or_to @post
# redirect_back_or_to "http://www.rubyonrails.org"
# redirect_back_or_to "/images/screenshot.jpg"
# redirect_back_or_to posts_url
# redirect_back_or_to proc { edit_post_url(@post) }
# redirect_back_or_to '/', allow_other_host: false
# redirect_back_or_to({ action: "show", id: 5 })
# redirect_back_or_to @post
# redirect_back_or_to "http://www.rubyonrails.org"
# redirect_back_or_to "/images/screenshot.jpg"
# redirect_back_or_to posts_url
# redirect_back_or_to proc { edit_post_url(@post) }
# redirect_back_or_to '/', allow_other_host: false
#
# ==== Options
# * <tt>:allow_other_host</tt> - Allow or disallow redirection to the host that is different to the current host, defaults to true.
# #### Options
# * `:allow_other_host` - Allow or disallow redirection to the host that is
# different to the current host, defaults to true.
#
# All other options that can be passed to #redirect_to are accepted as
# options, and the behavior is identical.
#
# All other options that can be passed to #redirect_to are accepted as options,
# and the behavior is identical.
def redirect_back_or_to(fallback_location, allow_other_host: _allow_other_host, **options)
if request.referer && (allow_other_host || _url_host_allowed?(request.referer))
redirect_to request.referer, allow_other_host: allow_other_host, **options
else
# The method level `allow_other_host` doesn't apply in the fallback case, omit and let the `redirect_to` handling take over.
# The method level `allow_other_host` doesn't apply in the fallback case, omit
# and let the `redirect_to` handling take over.
redirect_to fallback_location, **options
end
end
def _compute_redirect_to_location(request, options) # :nodoc:
case options
# The scheme name consist of a letter followed by any combination of
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
# characters; and is terminated by a colon (":").
# See https://tools.ietf.org/html/rfc3986#section-3.1
# The protocol relative scheme starts with a double slash "//".
# The scheme name consist of a letter followed by any combination of letters,
# digits, and the plus ("+"), period ("."), or hyphen ("-") characters; and is
# terminated by a colon (":"). See
# https://tools.ietf.org/html/rfc3986#section-3.1 The protocol relative scheme
# starts with a double slash "//".
when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i
options.to_str
when String
@ -153,25 +175,30 @@ def _compute_redirect_to_location(request, options) # :nodoc:
module_function :_compute_redirect_to_location
public :_compute_redirect_to_location
# Verifies the passed +location+ is an internal URL that's safe to redirect to and returns it, or nil if not.
# Useful to wrap a params provided redirect URL and fall back to an alternate URL to redirect to:
# Verifies the passed `location` is an internal URL that's safe to redirect to
# and returns it, or nil if not. Useful to wrap a params provided redirect URL
# and fall back to an alternate URL to redirect to:
#
# redirect_to url_from(params[:redirect_url]) || root_url
# redirect_to url_from(params[:redirect_url]) || root_url
#
# The +location+ is considered internal, and safe, if it's on the same host as <tt>request.host</tt>:
# The `location` is considered internal, and safe, if it's on the same host as
# `request.host`:
#
# # If request.host is example.com:
# url_from("https://example.com/profile") # => "https://example.com/profile"
# url_from("http://example.com/profile") # => "http://example.com/profile"
# url_from("http://evil.com/profile") # => nil
# # If request.host is example.com:
# url_from("https://example.com/profile") # => "https://example.com/profile"
# url_from("http://example.com/profile") # => "http://example.com/profile"
# url_from("http://evil.com/profile") # => nil
#
# Subdomains are considered part of the host:
#
# # If request.host is on https://example.com or https://app.example.com, you'd get:
# url_from("https://dev.example.com/profile") # => nil
# # If request.host is on https://example.com or https://app.example.com, you'd get:
# url_from("https://dev.example.com/profile") # => nil
#
# NOTE: there's a similarity with {url_for}[rdoc-ref:ActionDispatch::Routing::UrlFor#url_for], which generates an internal URL from various options from within the app, e.g. <tt>url_for(@post)</tt>.
# However, #url_from is meant to take an external parameter to verify as in <tt>url_from(params[:redirect_url])</tt>.
# NOTE: there's a similarity with
# [url_for](rdoc-ref:ActionDispatch::Routing::UrlFor#url_for), which generates
# an internal URL from various options from within the app, e.g.
# `url_for(@post)`. However, #url_from is meant to take an external parameter to
# verify as in `url_from(params[:redirect_url])`.
def url_from(location)
location = location.presence
location if location && _url_host_allowed?(location)
@ -212,9 +239,8 @@ def _url_host_allowed?(url)
end
def _ensure_url_is_http_header_safe(url)
# Attempt to comply with the set of valid token characters
# defined for an HTTP header value in
# https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
# Attempt to comply with the set of valid token characters defined for an HTTP
# header value in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
if url.match?(ILLEGAL_HEADER_VALUE_REGEX)
msg = "The redirect URL #{url} contains one or more illegal HTTP header field character. " \
"Set of legal characters defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6"

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "set"
module ActionController
@ -13,7 +15,7 @@ def self.remove_renderer(key)
Renderers.remove(key)
end
# See <tt>Responder#api_behavior</tt>
# See `Responder#api_behavior`
class MissingRenderer < LoadError
def initialize(format)
super "No renderer defined for format: #{format}"
@ -24,7 +26,7 @@ module Renderers
extend ActiveSupport::Concern
# A Set containing renderer names that correspond to available renderer procs.
# Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
# Default values are `:json`, `:js`, `:xml`.
RENDERERS = Set.new
included do
@ -42,35 +44,34 @@ module All
end
end
# Adds a new renderer to call within controller actions.
# A renderer is invoked by passing its name as an option to
# AbstractController::Rendering#render. To create a renderer
# pass it a name and a block. The block takes two arguments, the first
# is the value paired with its key and the second is the remaining
# hash of options passed to +render+.
# Adds a new renderer to call within controller actions. A renderer is invoked
# by passing its name as an option to AbstractController::Rendering#render. To
# create a renderer pass it a name and a block. The block takes two arguments,
# the first is the value paired with its key and the second is the remaining
# hash of options passed to `render`.
#
# Create a csv renderer:
#
# ActionController::Renderers.add :csv do |obj, options|
# filename = options[:filename] || 'data'
# str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
# send_data str, type: Mime[:csv],
# disposition: "attachment; filename=#{filename}.csv"
# end
# ActionController::Renderers.add :csv do |obj, options|
# filename = options[:filename] || 'data'
# str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
# send_data str, type: Mime[:csv],
# disposition: "attachment; filename=#{filename}.csv"
# end
#
# Note that we used Mime[:csv] for the csv mime type as it comes with \Rails.
# Note that we used [Mime](:csv) for the csv mime type as it comes with Rails.
# For a custom renderer, you'll need to register a mime type with
# <tt>Mime::Type.register</tt>.
# `Mime::Type.register`.
#
# To use the csv renderer in a controller action:
#
# def show
# @csvable = Csvable.find(params[:id])
# respond_to do |format|
# format.html
# format.csv { render csv: @csvable, filename: @csvable.name }
# def show
# @csvable = Csvable.find(params[:id])
# respond_to do |format|
# format.html
# format.csv { render csv: @csvable, filename: @csvable.name }
# end
# end
# end
def self.add(key, &block)
define_method(_render_with_renderer_method_name(key), &block)
RENDERERS << key.to_sym
@ -80,7 +81,7 @@ def self.add(key, &block)
#
# To remove a csv renderer:
#
# ActionController::Renderers.remove(:csv)
# ActionController::Renderers.remove(:csv)
def self.remove(key)
RENDERERS.delete(key.to_sym)
method_name = _render_with_renderer_method_name(key)
@ -92,39 +93,39 @@ def self._render_with_renderer_method_name(key)
end
module ClassMethods
# Adds, by name, a renderer or renderers to the +_renderers+ available
# to call within controller actions.
# Adds, by name, a renderer or renderers to the `_renderers` available to call
# within controller actions.
#
# It is useful when rendering from an ActionController::Metal controller or
# otherwise to add an available renderer proc to a specific controller.
#
# Both ActionController::Base and ActionController::API
# include ActionController::Renderers::All, making all renderers
# available in the controller. See Renderers::RENDERERS and Renderers.add.
# Both ActionController::Base and ActionController::API include
# ActionController::Renderers::All, making all renderers available in the
# controller. See Renderers::RENDERERS and Renderers.add.
#
# Since ActionController::Metal controllers cannot render, the controller
# must include AbstractController::Rendering, ActionController::Rendering,
# and ActionController::Renderers, and have at least one renderer.
# Since ActionController::Metal controllers cannot render, the controller must
# include AbstractController::Rendering, ActionController::Rendering, and
# ActionController::Renderers, and have at least one renderer.
#
# Rather than including ActionController::Renderers::All and including all renderers,
# you may specify which renderers to include by passing the renderer name or names to
# +use_renderers+. For example, a controller that includes only the <tt>:json</tt> renderer
# (+_render_with_renderer_json+) might look like:
# Rather than including ActionController::Renderers::All and including all
# renderers, you may specify which renderers to include by passing the renderer
# name or names to `use_renderers`. For example, a controller that includes only
# the `:json` renderer (`_render_with_renderer_json`) might look like:
#
# class MetalRenderingController < ActionController::Metal
# include AbstractController::Rendering
# include ActionController::Rendering
# include ActionController::Renderers
# class MetalRenderingController < ActionController::Metal
# include AbstractController::Rendering
# include ActionController::Rendering
# include ActionController::Renderers
#
# use_renderers :json
# use_renderers :json
#
# def show
# render json: record
# def show
# render json: record
# end
# end
# end
#
# You must specify a +use_renderer+, else the +controller.renderer+ and
# +controller._renderers+ will be <tt>nil</tt>, and the action will fail.
# You must specify a `use_renderer`, else the `controller.renderer` and
# `controller._renderers` will be `nil`, and the action will fail.
def use_renderers(*args)
renderers = _renderers + args
self._renderers = renderers.freeze
@ -132,11 +133,11 @@ def use_renderers(*args)
alias use_renderer use_renderers
end
# Called by +render+ in AbstractController::Rendering
# which sets the return value as the +response_body+.
# Called by `render` in AbstractController::Rendering which sets the return
# value as the `response_body`.
#
# If no renderer is found, +super+ returns control to
# <tt>ActionView::Rendering.render_to_body</tt>, if present.
# If no renderer is found, `super` returns control to
# `ActionView::Rendering.render_to_body`, if present.
def render_to_body(options)
_render_to_body_with_renderer(options) || super
end

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
module Rendering
extend ActiveSupport::Concern
@ -10,8 +12,8 @@ module ClassMethods
# Documentation at ActionController::Renderer#render
delegate :render, to: :renderer
# Returns a renderer instance (inherited from ActionController::Renderer)
# for the controller.
# Returns a renderer instance (inherited from ActionController::Renderer) for
# the controller.
attr_reader :renderer
def setup_renderer! # :nodoc:
@ -24,137 +26,139 @@ def inherited(klass)
end
end
# Renders a template and assigns the result to +self.response_body+.
# Renders a template and assigns the result to `self.response_body`.
#
# If no rendering mode option is specified, the template will be derived
# from the first argument.
# If no rendering mode option is specified, the template will be derived from
# the first argument.
#
# render "posts/show"
# # => renders app/views/posts/show.html.erb
# render "posts/show"
# # => renders app/views/posts/show.html.erb
#
# # In a PostsController action...
# render :show
# # => renders app/views/posts/show.html.erb
# # In a PostsController action...
# render :show
# # => renders app/views/posts/show.html.erb
#
# If the first argument responds to +render_in+, the template will be
# rendered by calling +render_in+ with the current view context.
# If the first argument responds to `render_in`, the template will be rendered
# by calling `render_in` with the current view context.
#
# class Greeting
# def render_in(view_context)
# view_context.render html: "<h1>Hello, World</h1>"
# class Greeting
# def render_in(view_context)
# view_context.render html: "<h1>Hello, World</h1>"
# end
#
# def format
# :html
# end
# end
#
# def format
# :html
# end
# end
# render(Greeting.new)
# # => "<h1>Hello, World</h1>"
#
# render(Greeting.new)
# # => "<h1>Hello, World</h1>"
# render(renderable: Greeting.new)
# # => "<h1>Hello, World</h1>"
#
# render(renderable: Greeting.new)
# # => "<h1>Hello, World</h1>"
# #### Rendering Mode
#
# ==== \Rendering Mode
# `:partial`
# : See ActionView::PartialRenderer for details.
#
# [+:partial+]
# See ActionView::PartialRenderer for details.
# render partial: "posts/form", locals: { post: Post.new }
# # => renders app/views/posts/_form.html.erb
#
# render partial: "posts/form", locals: { post: Post.new }
# # => renders app/views/posts/_form.html.erb
# `:file`
# : Renders the contents of a file. This option should **not** be used with
# unsanitized user input.
#
# [+:file+]
# Renders the contents of a file. This option should <b>not</b> be used
# with unsanitized user input.
# render file: "/path/to/some/file"
# # => renders /path/to/some/file
#
# render file: "/path/to/some/file"
# # => renders /path/to/some/file
# `:inline`
# : Renders an ERB template string.
#
# [+:inline+]
# Renders an ERB template string.
# @name = "World"
# render inline: "<h1>Hello, <%= @name %>!</h1>"
# # => renders "<h1>Hello, World!</h1>"
#
# @name = "World"
# render inline: "<h1>Hello, <%= @name %>!</h1>"
# # => renders "<h1>Hello, World!</h1>"
# `:body`
# : Renders the provided text, and sets the content type as `text/plain`.
#
# [+:body+]
# Renders the provided text, and sets the content type as +text/plain+.
# render body: "Hello, World!"
# # => renders "Hello, World!"
#
# render body: "Hello, World!"
# # => renders "Hello, World!"
# `:plain`
# : Renders the provided text, and sets the content type as `text/plain`.
#
# [+:plain+]
# Renders the provided text, and sets the content type as +text/plain+.
# render plain: "Hello, World!"
# # => renders "Hello, World!"
#
# render plain: "Hello, World!"
# # => renders "Hello, World!"
# `:html`
# : Renders the provided HTML string, and sets the content type as
# `text/html`. If the string is not `html_safe?`, performs HTML escaping on
# the string before rendering.
#
# [+:html+]
# Renders the provided HTML string, and sets the content type as +text/html+.
# If the string is not +html_safe?+, performs HTML escaping on the string
# before rendering.
# render html: "<h1>Hello, World!</h1>".html_safe
# # => renders "<h1>Hello, World!</h1>"
#
# render html: "<h1>Hello, World!</h1>".html_safe
# # => renders "<h1>Hello, World!</h1>"
# render html: "<h1>Hello, World!</h1>"
# # => renders "&lt;h1&gt;Hello, World!&lt;/h1&gt;"
#
# render html: "<h1>Hello, World!</h1>"
# # => renders "&lt;h1&gt;Hello, World!&lt;/h1&gt;"
# `:json`
# : Renders the provided object as JSON, and sets the content type as
# `application/json`. If the object is not a string, it will be converted to
# JSON by calling `to_json`.
#
# [+:json+]
# Renders the provided object as JSON, and sets the content type as
# +application/json+. If the object is not a string, it will be converted
# to JSON by calling +to_json+.
# render json: { hello: "world" }
# # => renders "{\"hello\":\"world\"}"
#
# render json: { hello: "world" }
# # => renders "{\"hello\":\"world\"}"
# `:renderable`
# : Renders the provided object by calling `render_in` with the current view
# context. The response format is determined by calling `format` on the
# renderable if it responds to `format`, falling back to `text/html` by
# default.
#
# [+:renderable+]
# Renders the provided object by calling +render_in+ with the current view
# context. The response format is determined by calling +format+ on the
# renderable if it responds to +format+, falling back to +text/html+ by default.
# render renderable: Greeting.new
# # => renders "<h1>Hello, World</h1>"
#
# render renderable: Greeting.new
# # => renders "<h1>Hello, World</h1>"
#
# By default, when a rendering mode is specified, no layout template is
# rendered.
#
# ==== Options
# #### Options
#
# [+:assigns+]
# Hash of instance variable assignments for the template.
# `:assigns`
# : Hash of instance variable assignments for the template.
#
# render inline: "<h1>Hello, <%= @name %>!</h1>", assigns: { name: "World" }
# # => renders "<h1>Hello, World!</h1>"
# render inline: "<h1>Hello, <%= @name %>!</h1>", assigns: { name: "World" }
# # => renders "<h1>Hello, World!</h1>"
#
# [+:locals+]
# Hash of local variable assignments for the template.
# `:locals`
# : Hash of local variable assignments for the template.
#
# render inline: "<h1>Hello, <%= name %>!</h1>", locals: { name: "World" }
# # => renders "<h1>Hello, World!</h1>"
# render inline: "<h1>Hello, <%= name %>!</h1>", locals: { name: "World" }
# # => renders "<h1>Hello, World!</h1>"
#
# [+:layout+]
# The layout template to render. Can also be +false+ or +true+ to disable
# or (re)enable the default layout template.
# `:layout`
# : The layout template to render. Can also be `false` or `true` to disable or
# (re)enable the default layout template.
#
# render "posts/show", layout: "holiday"
# # => renders app/views/posts/show.html.erb with the app/views/layouts/holiday.html.erb layout
# render "posts/show", layout: "holiday"
# # => renders app/views/posts/show.html.erb with the app/views/layouts/holiday.html.erb layout
#
# render "posts/show", layout: false
# # => renders app/views/posts/show.html.erb with no layout
# render "posts/show", layout: false
# # => renders app/views/posts/show.html.erb with no layout
#
# render inline: "<h1>Hello, World!</h1>", layout: true
# # => renders "<h1>Hello, World!</h1>" with the default layout
# render inline: "<h1>Hello, World!</h1>", layout: true
# # => renders "<h1>Hello, World!</h1>" with the default layout
#
# [+:status+]
# The HTTP status code to send with the response. Can be specified as a
# number or as the status name in Symbol form. Defaults to 200.
# `:status`
# : The HTTP status code to send with the response. Can be specified as a
# number or as the status name in Symbol form. Defaults to 200.
#
# render "posts/new", status: 422
# # => renders app/views/posts/new.html.erb with HTTP status code 422
# render "posts/new", status: 422
# # => renders app/views/posts/new.html.erb with HTTP status code 422
#
# render "posts/new", status: :unprocessable_entity
# # => renders app/views/posts/new.html.erb with HTTP status code 422
# render "posts/new", status: :unprocessable_entity
# # => renders app/views/posts/new.html.erb with HTTP status code 422
#
#--
# Check for double render errors and set the content_type after rendering.
@ -164,7 +168,7 @@ def render(*args)
end
# Similar to #render, but only returns the rendered template as a string,
# instead of setting +self.response_body+.
# instead of setting `self.response_body`.
#--
# Override render_to_string because body can now be set to a Rack body.
def render_to_string(*)

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "rack/session/abstract/id"
require "action_controller/metal/exceptions"
require "active_support/security_utils"
@ -11,51 +13,53 @@ class InvalidAuthenticityToken < ActionControllerError # :nodoc:
class InvalidCrossOriginRequest < ActionControllerError # :nodoc:
end
# = Action Controller Request Forgery Protection
# # Action Controller Request Forgery Protection
#
# 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. All requests are checked except GET requests
# as these should be idempotent. Keep in mind that all session-oriented requests
# are CSRF protected by default, including JavaScript and HTML requests.
# 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. All requests are checked except
# GET requests as these should be idempotent. Keep in mind that all
# session-oriented requests are CSRF protected by default, including JavaScript
# and HTML requests.
#
# Since HTML and JavaScript requests are typically made from the browser, we
# need to ensure to verify request authenticity for the web browser. We can
# use session-oriented authentication for these types of requests, by using
# the <tt>protect_from_forgery</tt> method in our controllers.
# need to ensure to verify request authenticity for the web browser. We can use
# session-oriented authentication for these types of requests, by using the
# `protect_from_forgery` method in our controllers.
#
# GET requests are not protected since they don't have side effects like writing
# to the database and don't leak sensitive information. JavaScript requests are
# 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 requests for JavaScript responses.
# 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 requests
# for JavaScript responses.
#
# Subclasses of ActionController::Base are protected by default with the
# <tt>:exception</tt> strategy, which raises an
# `:exception` strategy, which raises an
# ActionController::InvalidAuthenticityToken error on unverified requests.
#
# APIs may want to disable this behavior since they are typically designed to be
# state-less: that is, the request API client handles the session instead of \Rails.
# One way to achieve this is to use the <tt>:null_session</tt> strategy instead,
# state-less: that is, the request API client handles the session instead of
# Rails. One way to achieve this is to use the `:null_session` strategy instead,
# which allows unverified requests to be handled, but with an empty session:
#
# class ApplicationController < ActionController::Base
# protect_from_forgery with: :null_session
# end
# class ApplicationController < ActionController::Base
# protect_from_forgery with: :null_session
# end
#
# Note that API only applications don't include this module or a session middleware
# by default, and so don't require CSRF protection to be configured.
# Note that API only applications don't include this module or a session
# middleware by default, and so don't require CSRF protection to be configured.
#
# The token parameter is named <tt>authenticity_token</tt> by default. The name and
# value of this token must be added to every layout that renders forms by including
# <tt>csrf_meta_tags</tt> in the HTML +head+.
# The token parameter is named `authenticity_token` by default. The name and
# value of this token must be added to every layout that renders forms by
# including `csrf_meta_tags` in the HTML `head`.
#
# Learn more about CSRF attacks and securing your application in the
# {Ruby on Rails Security Guide}[https://guides.rubyonrails.org/security.html].
# Learn more about CSRF attacks and securing your application in the [Ruby on
# Rails Security Guide](https://guides.rubyonrails.org/security.html).
module RequestForgeryProtection
CSRF_TOKEN = "action_controller.csrf_token"
@ -65,8 +69,8 @@ module RequestForgeryProtection
include AbstractController::Callbacks
included do
# Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+
# sets it to <tt>:authenticity_token</tt> by default.
# Sets the token parameter name for RequestForgery. Calling
# `protect_from_forgery` sets it to `:authenticity_token` by default.
config_accessor :request_forgery_protection_token
self.request_forgery_protection_token ||= :authenticity_token
@ -74,7 +78,8 @@ module RequestForgeryProtection
config_accessor :forgery_protection_strategy
self.forgery_protection_strategy = nil
# Controls whether request forgery protection is turned on or not. Turned off by default only in test mode.
# Controls whether request forgery protection is turned on or not. Turned off by
# default only in test mode.
config_accessor :allow_forgery_protection
self.allow_forgery_protection = true if allow_forgery_protection.nil?
@ -99,79 +104,96 @@ module RequestForgeryProtection
end
module ClassMethods
# Turn on request forgery protection. Bear in mind that GET and HEAD requests are not checked.
# Turn on request forgery protection. Bear in mind that GET and HEAD requests
# are not checked.
#
# class ApplicationController < ActionController::Base
# protect_from_forgery
# end
# class ApplicationController < ActionController::Base
# protect_from_forgery
# end
#
# class FooController < ApplicationController
# protect_from_forgery except: :index
# end
# class FooController < ApplicationController
# protect_from_forgery except: :index
# end
#
# You can disable forgery protection on a controller using skip_forgery_protection:
# You can disable forgery protection on a controller using
# skip_forgery_protection:
#
# class BarController < ApplicationController
# skip_forgery_protection
# end
# class BarController < ApplicationController
# skip_forgery_protection
# end
#
# Valid Options:
#
# * <tt>:only</tt> / <tt>:except</tt> - Only apply forgery protection to a subset of actions. For example <tt>only: [ :create, :create_all ]</tt>.
# * <tt>:if</tt> / <tt>:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference.
# * <tt>:prepend</tt> - By default, the verification of the authentication token will be added at the position of the
# protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful
# when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth).
# * `:only` / `:except` - Only apply forgery protection to a subset of
# actions. For example `only: [ :create, :create_all ]`.
# * `:if` / `:unless` - Turn off the forgery protection entirely depending on
# the passed Proc or method reference.
# * `:prepend` - By default, the verification of the authentication token will
# be added at the position of the protect_from_forgery call in your
# application. This means any callbacks added before are run first. This is
# useful when you want your forgery protection to depend on other callbacks,
# like authentication methods (Oauth vs Cookie auth).
#
# If you need to add verification to the beginning of the callback chain,
# use `prepend: true`.
# * `:with` - Set the method to handle unverified request. Note if
# `default_protect_from_forgery` is true, Rails call protect_from_forgery
# with `with :exception`.
#
# If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>.
# * <tt>:with</tt> - Set the method to handle unverified request.
# Note if <tt>default_protect_from_forgery</tt> is true, Rails call protect_from_forgery with <tt>with :exception</tt>.
#
# Built-in unverified request handling methods are:
# * <tt>:exception</tt> - Raises ActionController::InvalidAuthenticityToken exception.
# * <tt>:reset_session</tt> - Resets the session.
# * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified.
# * `:exception` - Raises ActionController::InvalidAuthenticityToken
# exception.
# * `:reset_session` - Resets the session.
# * `:null_session` - Provides an empty session during request but doesn't
# reset it completely. Used as default if `:with` option is not specified.
#
# You can also implement custom strategy classes for unverified request handling:
#
# class CustomStrategy
# def initialize(controller)
# @controller = controller
# end
# You can also implement custom strategy classes for unverified request
# handling:
#
# def handle_unverified_request
# # Custom behavior for unverfied request
# end
# end
# class CustomStrategy
# def initialize(controller)
# @controller = controller
# end
#
# def handle_unverified_request
# # Custom behavior for unverfied request
# end
# end
#
# class ApplicationController < ActionController::Base
# protect_from_forgery with: CustomStrategy
# end
#
# * `:store` - Set the strategy to store and retrieve CSRF tokens.
#
# class ApplicationController < ActionController::Base
# protect_from_forgery with: CustomStrategy
# end
# * <tt>:store</tt> - Set the strategy to store and retrieve CSRF tokens.
#
# Built-in session token strategies are:
# * <tt>:session</tt> - Store the CSRF token in the session. Used as default if <tt>:store</tt> option is not specified.
# * <tt>:cookie</tt> - Store the CSRF token in an encrypted cookie.
# * `:session` - Store the CSRF token in the session. Used as default if
# `:store` option is not specified.
# * `:cookie` - Store the CSRF token in an encrypted cookie.
#
#
# You can also implement custom strategy classes for CSRF token storage:
#
# class CustomStore
# def fetch(request)
# # Return the token from a custom location
# class CustomStore
# def fetch(request)
# # Return the token from a custom location
# end
#
# def store(request, csrf_token)
# # Store the token in a custom location
# end
#
# def reset(request)
# # Delete the stored session token
# end
# end
#
# def store(request, csrf_token)
# # Store the token in a custom location
# class ApplicationController < ActionController::Base
# protect_from_forgery store: CustomStore.new
# end
#
# def reset(request)
# # Delete the stored session token
# end
# end
#
# class ApplicationController < ActionController::Base
# protect_from_forgery store: CustomStore.new
# end
def protect_from_forgery(options = {})
options = options.reverse_merge(prepend: false)
@ -186,9 +208,9 @@ def protect_from_forgery(options = {})
# Turn off request forgery protection. This is a wrapper for:
#
# skip_before_action :verify_authenticity_token
# skip_before_action :verify_authenticity_token
#
# See +skip_before_action+ for allowed options.
# See `skip_before_action` for allowed options.
def skip_forgery_protection(options = {})
skip_before_action :verify_authenticity_token, options.reverse_merge(raise: false)
end
@ -232,7 +254,8 @@ def initialize(controller)
@controller = controller
end
# This is the method that defines the application behavior when a request is found to be unverified.
# This is the method that defines the application behavior when a request is
# found to be unverified.
def handle_unverified_request
request = @controller.request
request.session = NullSessionHash.new(request)
@ -354,16 +377,15 @@ def commit_csrf_token(request) # :doc:
end
private
# 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
# <tt><script></tt> verification.
# 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.
# 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 # :doc:
mark_for_same_origin_verification!
@ -400,9 +422,9 @@ def unverified_request_warning_message # :nodoc:
private_constant :CROSS_ORIGIN_JAVASCRIPT_WARNING
# :startdoc:
# 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.
# 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 # :doc:
if marked_for_same_origin_verification? && non_xhr_javascript_response?
if logger && log_warning_on_csrf_failure
@ -417,8 +439,8 @@ def mark_for_same_origin_verification! # :doc:
@_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.
# 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? # :doc:
@_marked_for_same_origin_verification ||= false
end
@ -432,9 +454,11 @@ def non_xhr_javascript_response? # :doc:
# Returns true or false if a request is verified. Checks:
#
# * Is it a GET or HEAD request? GETs should be safe and idempotent
# * Does the form_authenticity_token match the given token value from the params?
# * Does the +X-CSRF-Token+ header match the form_authenticity_token?
# * Is it a GET or HEAD request? GETs should be safe and idempotent
# * Does the form_authenticity_token match the given token value from the
# params?
# * Does the `X-CSRF-Token` header match the form_authenticity_token?
#
def verified_request? # :doc:
!protect_against_forgery? || request.get? || request.head? ||
(valid_request_origin? && any_authenticity_token_valid?)
@ -457,9 +481,8 @@ def form_authenticity_token(form_options: {}) # :doc:
masked_authenticity_token(form_options: form_options)
end
# Creates a masked version of the authenticity token that varies
# on each request. The masking is used to mitigate SSL attacks
# like BREACH.
# Creates a masked version of the authenticity token that varies on each
# request. The masking is used to mitigate SSL attacks like BREACH.
def masked_authenticity_token(form_options: {})
action, method = form_options.values_at(:action, :method)
@ -473,9 +496,8 @@ def masked_authenticity_token(form_options: {})
mask_token(raw_token)
end
# Checks the client's masked token to see if it matches the
# session token. Essentially the inverse of
# +masked_authenticity_token+.
# Checks the client's masked token to see if it matches the session token.
# Essentially the inverse of `masked_authenticity_token`.
def valid_authenticity_token?(session, encoded_masked_token) # :doc:
if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String)
return false
@ -487,14 +509,12 @@ def valid_authenticity_token?(session, encoded_masked_token) # :doc:
return false
end
# See if it's actually a masked token or not. In order to
# deploy this code, we should be able to handle any unmasked
# tokens that we've issued without error.
# See if it's actually a masked token or not. In order to deploy this code, we
# should be able to handle any unmasked tokens that we've issued without error.
if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
# This is actually an unmasked token. This is expected if
# you have just upgraded to masked tokens, but should stop
# happening shortly after installing this gem.
# This is actually an unmasked token. This is expected if you have just upgraded
# to masked tokens, but should stop happening shortly after installing this gem.
compare_with_real_token masked_token
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
@ -509,8 +529,7 @@ def valid_authenticity_token?(session, encoded_masked_token) # :doc:
end
def unmask_token(masked_token) # :doc:
# Split the token into the one-time pad and the encrypted
# value and decrypt it.
# Split the token into the one-time pad and the encrypted value and decrypt it.
one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
xor_byte_strings(one_time_pad, encrypted_csrf_token)
@ -602,8 +621,8 @@ def protect_against_forgery? # :doc:
Rails.application.config.action_controller.forgery_protection_origin_check setting.
MSG
# Checks if the request originated from the same origin by looking at the
# Origin header.
# Checks if the request originated from the same origin by looking at the Origin
# header.
def valid_request_origin? # :doc:
if forgery_protection_origin_check
# We accept blank origin headers because some user agents don't send it.

@ -1,21 +1,23 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController # :nodoc:
# = Action Controller \Rescue
# # Action Controller Rescue
#
# This module is responsible for providing
# {rescue_from}[rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from]
# to controllers, wrapping actions to handle configured errors, and
# configuring when detailed exceptions must be shown.
# [rescue_from](rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from) to
# controllers, wrapping actions to handle configured errors, and configuring
# when detailed exceptions must be shown.
module Rescue
extend ActiveSupport::Concern
include ActiveSupport::Rescuable
# Override this method if you want to customize when detailed
# exceptions must be shown. This method is only called when
# +consider_all_requests_local+ is +false+. By default, it returns
# +false+, but someone may set it to <tt>request.local?</tt> so local
# requests in production still show the detailed exception pages.
# Override this method if you want to customize when detailed exceptions must be
# shown. This method is only called when `consider_all_requests_local` is
# `false`. By default, it returns `false`, but someone may set it to
# `request.local?` so local requests in production still show the detailed
# exception pages.
def show_detailed_exceptions?
false
end

@ -1,208 +1,210 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController # :nodoc:
# = Action Controller \Streaming
# # Action Controller Streaming
#
# Allows views to be streamed back to the client as they are rendered.
#
# By default, \Rails renders views by first rendering the template
# and then the layout. The response is sent to the client after the whole
# template is rendered, all queries are made, and the layout is processed.
# By default, Rails renders views by first rendering the template and then the
# layout. The response is sent to the client after the whole template is
# rendered, all queries are made, and the layout is processed.
#
# \Streaming inverts the rendering flow by rendering the layout first and
# Streaming inverts the rendering flow by rendering the layout first and
# subsequently each part of the layout as they are processed. This allows the
# header of the HTML (which is usually in the layout) to be streamed back
# to client very quickly, enabling JavaScripts and stylesheets to be loaded
# earlier than usual.
# header of the HTML (which is usually in the layout) to be streamed back to
# client very quickly, enabling JavaScripts and stylesheets to be loaded earlier
# than usual.
#
# Several Rack middlewares may not work and you need to be careful when streaming.
# This is covered in more detail below, see the Streaming@Middlewares section.
# Several Rack middlewares may not work and you need to be careful when
# streaming. This is covered in more detail below, see the Streaming@Middlewares
# section.
#
# \Streaming can be added to a given template easily, all you need to do is
# to pass the +:stream+ option to +render+.
# Streaming can be added to a given template easily, all you need to do is to
# pass the `:stream` option to `render`.
#
# class PostsController
# def index
# @posts = Post.all
# render stream: true
# class PostsController
# def index
# @posts = Post.all
# render stream: true
# end
# end
# end
#
# == When to use streaming
# ## When to use streaming
#
# \Streaming may be considered to be overkill for lightweight actions like
# +new+ or +edit+. The real benefit of streaming is on expensive actions
# that, for example, do a lot of queries on the database.
# Streaming may be considered to be overkill for lightweight actions like `new`
# or `edit`. The real benefit of streaming is on expensive actions that, for
# example, do a lot of queries on the database.
#
# In such actions, you want to delay queries execution as much as you can.
# For example, imagine the following +dashboard+ action:
# In such actions, you want to delay queries execution as much as you can. For
# example, imagine the following `dashboard` action:
#
# def dashboard
# @posts = Post.all
# @pages = Page.all
# @articles = Article.all
# end
# def dashboard
# @posts = Post.all
# @pages = Page.all
# @articles = Article.all
# end
#
# Most of the queries here are happening in the controller. In order to benefit
# from streaming you would want to rewrite it as:
#
# def dashboard
# # Allow lazy execution of the queries
# @posts = Post.all
# @pages = Page.all
# @articles = Article.all
# render stream: true
# end
# def dashboard
# # Allow lazy execution of the queries
# @posts = Post.all
# @pages = Page.all
# @articles = Article.all
# render stream: true
# end
#
# Notice that +:stream+ only works with templates. \Rendering +:json+
# or +:xml+ with +:stream+ won't work.
# Notice that `:stream` only works with templates. Rendering `:json` or `:xml`
# with `:stream` won't work.
#
# == Communication between layout and template
# ## Communication between layout and template
#
# When streaming, rendering happens top-down instead of inside-out.
# \Rails starts with the layout, and the template is rendered later,
# when its +yield+ is reached.
# When streaming, rendering happens top-down instead of inside-out. Rails starts
# with the layout, and the template is rendered later, when its `yield` is
# reached.
#
# This means that, if your application currently relies on instance
# variables set in the template to be used in the layout, they won't
# work once you move to streaming. The proper way to communicate
# between layout and template, regardless of whether you use streaming
# or not, is by using +content_for+, +provide+, and +yield+.
# This means that, if your application currently relies on instance variables
# set in the template to be used in the layout, they won't work once you move to
# streaming. The proper way to communicate between layout and template,
# regardless of whether you use streaming or not, is by using `content_for`,
# `provide`, and `yield`.
#
# Take a simple example where the layout expects the template to tell
# which title to use:
# Take a simple example where the layout expects the template to tell which
# title to use:
#
# <html>
# <head><title><%= yield :title %></title></head>
# <body><%= yield %></body>
# </html>
# <html>
# <head><title><%= yield :title %></title></head>
# <body><%= yield %></body>
# </html>
#
# You would use +content_for+ in your template to specify the title:
# You would use `content_for` in your template to specify the title:
#
# <%= content_for :title, "Main" %>
# Hello
# <%= content_for :title, "Main" %>
# Hello
#
# And the final result would be:
#
# <html>
# <head><title>Main</title></head>
# <body>Hello</body>
# </html>
# <html>
# <head><title>Main</title></head>
# <body>Hello</body>
# </html>
#
# However, if +content_for+ is called several times, the final result
# would have all calls concatenated. For instance, if we have the following
# template:
# However, if `content_for` is called several times, the final result would have
# all calls concatenated. For instance, if we have the following template:
#
# <%= content_for :title, "Main" %>
# Hello
# <%= content_for :title, " page" %>
# <%= content_for :title, "Main" %>
# Hello
# <%= content_for :title, " page" %>
#
# The final result would be:
#
# <html>
# <head><title>Main page</title></head>
# <body>Hello</body>
# </html>
# <html>
# <head><title>Main page</title></head>
# <body>Hello</body>
# </html>
#
# This means that, if you have <code>yield :title</code> in your layout
# and you want to use streaming, you would have to render the whole template
# (and eventually trigger all queries) before streaming the title and all
# assets, which defeats the purpose of streaming. Alternatively, you can use
# a helper called +provide+ that does the same as +content_for+ but tells the
# layout to stop searching for other entries and continue rendering.
# This means that, if you have `yield :title` in your layout and you want to use
# streaming, you would have to render the whole template (and eventually trigger
# all queries) before streaming the title and all assets, which defeats the
# purpose of streaming. Alternatively, you can use a helper called `provide`
# that does the same as `content_for` but tells the layout to stop searching for
# other entries and continue rendering.
#
# For instance, the template above using +provide+ would be:
# For instance, the template above using `provide` would be:
#
# <%= provide :title, "Main" %>
# Hello
# <%= content_for :title, " page" %>
# <%= provide :title, "Main" %>
# Hello
# <%= content_for :title, " page" %>
#
# Resulting in:
#
# <html>
# <head><title>Main</title></head>
# <body>Hello</body>
# </html>
# <html>
# <head><title>Main</title></head>
# <body>Hello</body>
# </html>
#
# That said, when streaming, you need to properly check your templates
# and choose when to use +provide+ and +content_for+.
# That said, when streaming, you need to properly check your templates and
# choose when to use `provide` and `content_for`.
#
# See also ActionView::Helpers::CaptureHelper for more information.
#
# == Headers, cookies, session, and flash
# ## Headers, cookies, session, and flash
#
# When streaming, the HTTP headers are sent to the client right before
# it renders the first line. This means that, modifying headers, cookies,
# session or flash after the template starts rendering will not propagate
# to the client.
# When streaming, the HTTP headers are sent to the client right before it
# renders the first line. This means that, modifying headers, cookies, session
# or flash after the template starts rendering will not propagate to the client.
#
# == Middlewares
# ## Middlewares
#
# Middlewares that need to manipulate the body won't work with streaming.
# You should disable those middlewares whenever streaming in development
# or production. For instance, +Rack::Bug+ won't work when streaming as it
# needs to inject contents in the HTML body.
# Middlewares that need to manipulate the body won't work with streaming. You
# should disable those middlewares whenever streaming in development or
# production. For instance, `Rack::Bug` won't work when streaming as it needs to
# inject contents in the HTML body.
#
# Also +Rack::Cache+ won't work with streaming as it does not support
# streaming bodies yet. Whenever streaming +Cache-Control+ is automatically
# set to "no-cache".
# Also `Rack::Cache` won't work with streaming as it does not support streaming
# bodies yet. Whenever streaming `Cache-Control` is automatically set to
# "no-cache".
#
# == Errors
# ## Errors
#
# When it comes to streaming, exceptions get a bit more complicated. This
# happens because part of the template was already rendered and streamed to
# the client, making it impossible to render a whole exception page.
# happens because part of the template was already rendered and streamed to the
# client, making it impossible to render a whole exception page.
#
# Currently, when an exception happens in development or production, \Rails
# will automatically stream to the client:
# Currently, when an exception happens in development or production, Rails will
# automatically stream to the client:
#
# "><script>window.location = "/500.html"</script></html>
# "><script>window.location = "/500.html"</script></html>
#
# The first two characters (<tt>"></tt>) are required in case the exception
# happens while rendering attributes for a given tag. You can check the real
# cause for the exception in your logger.
# The first two characters (`">`) are required in case the exception happens
# while rendering attributes for a given tag. You can check the real cause for
# the exception in your logger.
#
# == Web server support
# ## Web server support
#
# Not all web servers support streaming out-of-the-box. You need to check
# the instructions for each of them.
# Not all web servers support streaming out-of-the-box. You need to check the
# instructions for each of them.
#
# ==== Unicorn
# #### Unicorn
#
# Unicorn supports streaming but it needs to be configured. For this, you
# need to create a config file as follow:
# Unicorn supports streaming but it needs to be configured. For this, you need
# to create a config file as follow:
#
# # unicorn.config.rb
# listen 3000, tcp_nopush: false
# # unicorn.config.rb
# listen 3000, tcp_nopush: false
#
# And use it on initialization:
#
# unicorn_rails --config-file unicorn.config.rb
# unicorn_rails --config-file unicorn.config.rb
#
# You may also want to configure other parameters like <tt>:tcp_nodelay</tt>.
# You may also want to configure other parameters like `:tcp_nodelay`.
#
# For more information, please check the
# {documentation}[https://bogomips.org/unicorn/Unicorn/Configurator.html#method-i-listen].
# [documentation](https://bogomips.org/unicorn/Unicorn/Configurator.html#method-
# i-listen).
#
# If you are using Unicorn with NGINX, you may need to tweak NGINX.
# \Streaming should work out of the box on Rainbows.
# If you are using Unicorn with NGINX, you may need to tweak NGINX. Streaming
# should work out of the box on Rainbows.
#
# ==== Passenger
# #### Passenger
#
# Phusion Passenger with NGINX, offers two streaming mechanisms out of the box.
#
# 1. NGINX response buffering mechanism which is dependent on the value of
# +passenger_buffer_response+ option (default is "off").
# 2. Passenger buffering system which is always 'on' irrespective of the value
# of +passenger_buffer_response+.
# 1. NGINX response buffering mechanism which is dependent on the value of
# `passenger_buffer_response` option (default is "off").
# 2. Passenger buffering system which is always 'on' irrespective of the value
# of `passenger_buffer_response`.
#
# When +passenger_buffer_response+ is turned "on", then streaming would be
# done at the NGINX level which waits until the application is done sending
# the response back to the client.
#
# For more information, please check the
# {documentation}[https://www.phusionpassenger.com/docs/references/config_reference/nginx/#passenger_buffer_response].
# When `passenger_buffer_response` is turned "on", then streaming would be done
# at the NGINX level which waits until the application is done sending the
# response back to the client.
#
# For more information, please check the [documentation]
# (https://www.phusionpassenger.com/docs/references/config_reference/nginx/#passenger_buffer_response).
module Streaming
class Body # :nodoc:
TERM = "\r\n"
@ -213,8 +215,8 @@ def initialize(body)
@body = body
end
# For each element yielded by the response body, yield
# the element in chunked encoding.
# For each element yielded by the response body, yield the element in chunked
# encoding.
def each(&block)
term = TERM
@body.each do |chunk|
@ -248,7 +250,7 @@ def _process_options(options)
end
end
# Call render_body if we are streaming instead of usual +render+.
# Call render_body if we are streaming instead of usual `render`.
def _render_template(options)
if options.delete(:stream)
Body.new view_renderer.render_body(view_context, options)

File diff suppressed because it is too large Load Diff

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
module Testing
# Behavior specific to functional tests

@ -1,27 +1,29 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
# = Action Controller \UrlFor
# # Action Controller UrlFor
#
# Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing
# the <tt>_routes</tt> method. Otherwise, an exception will be raised.
# Includes `url_for` into the host class. The class has to provide a `RouteSet`
# by implementing the `_routes` method. Otherwise, an exception will be raised.
#
# In addition to AbstractController::UrlFor, this module accesses the HTTP layer to define
# URL options like the +host+. In order to do so, this module requires the host class
# to implement +env+ which needs to be Rack-compatible, and +request+ which
# returns an ActionDispatch::Request instance.
# In addition to AbstractController::UrlFor, this module accesses the HTTP layer
# to define URL options like the `host`. In order to do so, this module requires
# the host class to implement `env` which needs to be Rack-compatible, and
# `request` which returns an ActionDispatch::Request instance.
#
# class RootUrl
# include ActionController::UrlFor
# include Rails.application.routes.url_helpers
# class RootUrl
# include ActionController::UrlFor
# include Rails.application.routes.url_helpers
#
# delegate :env, :request, to: :controller
# delegate :env, :request, to: :controller
#
# def initialize(controller)
# @controller = controller
# @url = root_path # named route from the application.
# def initialize(controller)
# @controller = controller
# @url = root_path # named route from the application.
# end
# end
# end
module UrlFor
extend ActiveSupport::Concern

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "rails"
require "action_controller"
require "action_dispatch/railtie"

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
module Railties
module Helpers

@ -1,25 +1,28 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
# = Action Controller \Renderer
# # Action Controller Renderer
#
# ActionController::Renderer allows you to render arbitrary templates without
# being inside a controller action.
#
# You can get a renderer instance by calling +renderer+ on a controller class:
# You can get a renderer instance by calling `renderer` on a controller class:
#
# ApplicationController.renderer
# PostsController.renderer
# ApplicationController.renderer
# PostsController.renderer
#
# and render a template by calling the #render method:
#
# ApplicationController.renderer.render template: "posts/show", assigns: { post: Post.first }
# PostsController.renderer.render :show, assigns: { post: Post.first }
# ApplicationController.renderer.render template: "posts/show", assigns: { post: Post.first }
# PostsController.renderer.render :show, assigns: { post: Post.first }
#
# As a shortcut, you can also call +render+ directly on the controller class itself:
# As a shortcut, you can also call `render` directly on the controller class
# itself:
#
# ApplicationController.render template: "posts/show", assigns: { post: Post.first }
# PostsController.render :show, assigns: { post: Post.first }
# ApplicationController.render template: "posts/show", assigns: { post: Post.first }
# PostsController.render :show, assigns: { post: Post.first }
#
class Renderer
attr_reader :controller
@ -64,45 +67,47 @@ def self.for(controller, env = nil, defaults = DEFAULTS)
# Creates a new renderer using the same controller, but with a new Rack env.
#
# ApplicationController.renderer.new(method: "post")
# ApplicationController.renderer.new(method: "post")
#
def new(env = nil)
self.class.new controller, env, @defaults
end
# Creates a new renderer using the same controller, but with the given
# defaults merged on top of the previous defaults.
# Creates a new renderer using the same controller, but with the given defaults
# merged on top of the previous defaults.
def with_defaults(defaults)
self.class.new controller, @env, @defaults.merge(defaults)
end
# Initializes a new Renderer.
#
# ==== Parameters
# #### Parameters
#
# * +controller+ - The controller class to instantiate for rendering.
# * +env+ - The Rack env to use for mocking a request when rendering.
# Entries can be typical Rack env keys and values, or they can be any of
# the following, which will be converted appropriately:
# * +:http_host+ - The HTTP host for the incoming request. Converts to
# Rack's +HTTP_HOST+.
# * +:https+ - Boolean indicating whether the incoming request uses HTTPS.
# Converts to Rack's +HTTPS+.
# * +:method+ - The HTTP method for the incoming request, case-insensitive.
# Converts to Rack's +REQUEST_METHOD+.
# * +:script_name+ - The portion of the incoming request's URL path that
# corresponds to the application. Converts to Rack's +SCRIPT_NAME+.
# * +:input+ - The input stream. Converts to Rack's +rack.input+.
# * +defaults+ - Default values for the Rack env. Entries are specified in
# the same format as +env+. +env+ will be merged on top of these values.
# +defaults+ will be retained when calling #new on a renderer instance.
# * `controller` - The controller class to instantiate for rendering.
# * `env` - The Rack env to use for mocking a request when rendering. Entries
# can be typical Rack env keys and values, or they can be any of the
# following, which will be converted appropriately:
# * `:http_host` - The HTTP host for the incoming request. Converts to
# Rack's `HTTP_HOST`.
# * `:https` - Boolean indicating whether the incoming request uses HTTPS.
# Converts to Rack's `HTTPS`.
# * `:method` - The HTTP method for the incoming request,
# case-insensitive. Converts to Rack's `REQUEST_METHOD`.
# * `:script_name` - The portion of the incoming request's URL path that
# corresponds to the application. Converts to Rack's `SCRIPT_NAME`.
# * `:input` - The input stream. Converts to Rack's `rack.input`.
#
# If no +http_host+ is specified, the env HTTP host will be derived from the
# routes' +default_url_options+. In this case, the +https+ boolean and the
# +script_name+ will also be derived from +default_url_options+ if they were
# not specified. Additionally, the +https+ boolean will fall back to
# +Rails.application.config.force_ssl+ if +default_url_options+ does not
# specify a +protocol+.
# * `defaults` - Default values for the Rack env. Entries are specified in the
# same format as `env`. `env` will be merged on top of these values.
# `defaults` will be retained when calling #new on a renderer instance.
#
#
# If no `http_host` is specified, the env HTTP host will be derived from the
# routes' `default_url_options`. In this case, the `https` boolean and the
# `script_name` will also be derived from `default_url_options` if they were not
# specified. Additionally, the `https` boolean will fall back to
# `Rails.application.config.force_ssl` if `default_url_options` does not specify
# a `protocol`.
def initialize(controller, env, defaults)
@controller = controller
@defaults = defaults
@ -119,7 +124,8 @@ def defaults
@defaults
end
# Renders a template to a string, just like ActionController::Rendering#render_to_string.
# Renders a template to a string, just like
# ActionController::Rendering#render_to_string.
def render(*args)
request = ActionDispatch::Request.new(env_for_request)
request.routes = controller._routes

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionController
module TemplateAssertions # :nodoc:
def assert_template(options = {}, message = nil)

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "rack/session/abstract/id"
require "active_support/core_ext/hash/conversions"
require "active_support/core_ext/object/to_query"
@ -16,10 +18,10 @@ class Metal
end
module Live
# Disable controller / rendering threads in tests. User tests can access
# the database on the main thread, so they could open a txn, then the
# controller thread will open a new connection and try to access data
# that's only visible to the main thread's txn. This is the problem in #23483.
# Disable controller / rendering threads in tests. User tests can access the
# database on the main thread, so they could open a txn, then the controller
# thread will open a new connection and try to access data that's only visible
# to the main thread's txn. This is the problem in #23483.
silence_redefinition_of_method :new_controller_thread
def new_controller_thread # :nodoc:
yield
@ -29,8 +31,8 @@ def new_controller_thread # :nodoc:
Buffer.queue_size = nil
end
# ActionController::TestCase will be deprecated and moved to a gem in the future.
# Please use ActionDispatch::IntegrationTest going forward.
# ActionController::TestCase will be deprecated and moved to a gem in the
# future. Please use ActionDispatch::IntegrationTest going forward.
class TestRequest < ActionDispatch::TestRequest # :nodoc:
DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
DEFAULT_ENV.delete "PATH_INFO"
@ -232,116 +234,125 @@ def load!
end
end
# = Action Controller Test Case
# # Action Controller Test Case
#
# Superclass for ActionController functional tests. Functional tests allow you to
# test a single controller action per test method.
# Superclass for ActionController functional tests. Functional tests allow you
# to test a single controller action per test method.
#
# == Use integration style controller tests over functional style controller tests.
# ## Use integration style controller tests over functional style controller tests.
#
# \Rails discourages the use of functional tests in favor of integration tests
# Rails discourages the use of functional tests in favor of integration tests
# (use ActionDispatch::IntegrationTest).
#
# New \Rails applications no longer generate functional style controller tests and they should
# only be used for backward compatibility. Integration style controller tests perform actual
# requests, whereas functional style controller tests merely simulate a request. Besides,
# integration tests are as fast as functional tests and provide lot of helpers such as +as+,
# +parsed_body+ for effective testing of controller actions including even API endpoints.
# New Rails applications no longer generate functional style controller tests
# and they should only be used for backward compatibility. Integration style
# controller tests perform actual requests, whereas functional style controller
# tests merely simulate a request. Besides, integration tests are as fast as
# functional tests and provide lot of helpers such as `as`, `parsed_body` for
# effective testing of controller actions including even API endpoints.
#
# == Basic example
# ## Basic example
#
# Functional tests are written as follows:
# 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+, or +head+ method to simulate
# an HTTP request.
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
# the controller's HTTP response, the database contents, etc.
# 1. First, one uses the `get`, `post`, `patch`, `put`, `delete`, or `head`
# method to simulate an HTTP request.
# 2. Then, one asserts whether the current state is as expected. "State" can be
# anything: the controller's HTTP response, the database contents, etc.
#
#
# For example:
#
# class BooksControllerTest < ActionController::TestCase
# def test_create
# # Simulate a POST response with the given HTTP parameters.
# post(:create, params: { book: { title: "Love Hina" }})
# class BooksControllerTest < ActionController::TestCase
# def test_create
# # Simulate a POST response with the given HTTP parameters.
# post(:create, params: { book: { title: "Love Hina" }})
#
# # Asserts that the controller tried to redirect us to
# # the created book's URI.
# assert_response :found
# # Asserts that the controller tried to redirect us to
# # the created book's URI.
# assert_response :found
#
# # Asserts that the controller really put the book in the database.
# assert_not_nil Book.find_by(title: "Love Hina")
# # Asserts that the controller really put the book in the database.
# assert_not_nil Book.find_by(title: "Love Hina")
# end
# end
# end
#
# You can also send a real document in the simulated HTTP request.
#
# def test_create
# json = {book: { title: "Love Hina" }}.to_json
# post :create, body: json
# end
# def test_create
# json = {book: { title: "Love Hina" }}.to_json
# post :create, body: json
# end
#
# == Special instance variables
# ## Special instance variables
#
# ActionController::TestCase will also automatically provide the following instance
# variables for use in the tests:
# ActionController::TestCase will also automatically provide the following
# instance variables for use in the tests:
#
# <b>@controller</b>::
# The controller instance that will be tested.
# <b>@request</b>::
# An ActionController::TestRequest, representing the current HTTP
# request. You can modify this object before sending the HTTP request. For example,
# you might want to set some session properties before sending a GET request.
# <b>@response</b>::
# An ActionDispatch::TestResponse object, representing the response
# of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
# after calling +post+. If the various assert methods are not sufficient, then you
# may use this object to inspect the HTTP response in detail.
# **@controller**
# : The controller instance that will be tested.
# **@request**
# : An ActionController::TestRequest, representing the current HTTP request.
# You can modify this object before sending the HTTP request. For example,
# you might want to set some session properties before sending a GET
# request.
# **@response**
# : An ActionDispatch::TestResponse object, representing the response of the
# last HTTP response. In the above example, `@response` becomes valid after
# calling `post`. If the various assert methods are not sufficient, then you
# may use this object to inspect the HTTP response in detail.
#
# == Controller is automatically inferred
#
# ## Controller is automatically inferred
#
# ActionController::TestCase will automatically infer the controller under test
# from the test class name. If the controller cannot be inferred from the test
# class name, you can explicitly set it with +tests+.
# class name, you can explicitly set it with `tests`.
#
# class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
# tests WidgetController
# end
# class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
# tests WidgetController
# end
#
# == \Testing controller internals
# ## Testing controller internals
#
# In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
# can be used against. These collections are:
# In addition to these specific assertions, you also have easy access to various
# collections that the regular test/unit assertions can be used against. These
# collections are:
#
# * session: Objects being saved in the session.
# * flash: The flash objects currently in the session.
# * cookies: Cookies being sent to the user on this request.
#
# * session: Objects being saved in the session.
# * flash: The flash objects currently in the session.
# * cookies: \Cookies being sent to the user on this request.
#
# These collections can be used just like any other hash:
#
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
# assert flash.empty? # makes sure that there's nothing in the flash
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
# assert flash.empty? # makes sure that there's nothing in the flash
#
# On top of the collections, you have the complete URL that a given action redirected to available in <tt>redirect_to_url</tt>.
# On top of the collections, you have the complete URL that a given action
# redirected to available in `redirect_to_url`.
#
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
# action call which can then be asserted against.
# For redirects within the same controller, you can even call follow_redirect
# and the redirect will be followed, triggering another action call which can
# then be asserted against.
#
# == Manipulating session and cookie variables
# ## Manipulating session and cookie variables
#
# Sometimes you need to set up the session and cookie variables for a test.
# To do this just assign a value to the session or cookie collection:
# Sometimes you need to set up the session and cookie variables for a test. To
# do this just assign a value to the session or cookie collection:
#
# session[:key] = "value"
# cookies[:key] = "value"
# session[:key] = "value"
# cookies[:key] = "value"
#
# To clear the cookies for a test just clear the cookie collection:
#
# cookies.clear
# cookies.clear
#
# == \Testing named routes
# ## Testing named routes
#
# If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
# If you're using named routes, they can be easily tested using the original
# named routes' methods straight in the test case.
#
# assert_redirected_to page_url(title: 'foo')
# assert_redirected_to page_url(title: 'foo')
class TestCase < ActiveSupport::TestCase
singleton_class.attr_accessor :executor_around_each_request
@ -354,12 +365,12 @@ module Behavior
attr_reader :response, :request
module ClassMethods
# Sets the controller class name. Useful if the name can't be inferred from test class.
# Normalizes +controller_class+ before using.
# Sets the controller class name. Useful if the name can't be inferred from test
# class. Normalizes `controller_class` before using.
#
# tests WidgetController
# tests :widget
# tests 'widget'
# tests WidgetController
# tests :widget
# tests 'widget'
def tests(controller_class)
case controller_class
when String, Symbol
@ -392,21 +403,24 @@ def determine_default_controller_class(name)
# Simulate a GET request with the given parameters.
#
# - +action+: The controller action to call.
# - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
# - +body+: The request body with a string that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
# - +session+: A hash of parameters to store in the session. This may be +nil+.
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
# * `action`: The controller action to call.
# * `params`: The hash with HTTP parameters that you want to pass. This may be
# `nil`.
# * `body`: The request body with a string that is appropriately encoded
# (`application/x-www-form-urlencoded` or `multipart/form-data`).
# * `session`: A hash of parameters to store in the session. This may be
# `nil`.
# * `flash`: A hash of parameters to store in the flash. This may be `nil`.
#
# You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
# +post+, +patch+, +put+, +delete+, and +head+.
# Example sending parameters, session, and setting a flash message:
#
# get :show,
# params: { id: 7 },
# session: { user_id: 1 },
# flash: { notice: 'This is flash message' }
# You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with `post`,
# `patch`, `put`, `delete`, and `head`. Example sending parameters, session, and
# setting a flash message:
#
# get :show,
# params: { id: 7 },
# session: { user_id: 1 },
# flash: { notice: 'This is flash message' }
#
# Note that the request method is not verified. The different methods are
# available to make the tests more expressive.
@ -417,67 +431,71 @@ def get(action, **args)
end
# Simulate a POST request with the given parameters and set/volley the response.
# See +get+ for more details.
# See `get` for more details.
def post(action, **args)
process(action, method: "POST", **args)
end
# Simulate a PATCH request with the given parameters and set/volley the response.
# See +get+ for more details.
# Simulate a PATCH request with the given parameters and set/volley the
# response. See `get` for more details.
def patch(action, **args)
process(action, method: "PATCH", **args)
end
# Simulate a PUT request with the given parameters and set/volley the response.
# See +get+ for more details.
# See `get` for more details.
def put(action, **args)
process(action, method: "PUT", **args)
end
# Simulate a DELETE request with the given parameters and set/volley the response.
# See +get+ for more details.
# Simulate a DELETE request with the given parameters and set/volley the
# response. See `get` for more details.
def delete(action, **args)
process(action, method: "DELETE", **args)
end
# Simulate a HEAD request with the given parameters and set/volley the response.
# See +get+ for more details.
# See `get` for more details.
def head(action, **args)
process(action, method: "HEAD", **args)
end
# Simulate an HTTP request to +action+ by specifying request method,
# parameters and set/volley the response.
# Simulate an HTTP request to `action` by specifying request method, parameters
# and set/volley the response.
#
# - +action+: The controller action to call.
# - +method+: Request method used to send the HTTP request. Possible values
# are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+. Can be a symbol.
# - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
# - +body+: The request body with a string that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
# - +session+: A hash of parameters to store in the session. This may be +nil+.
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
# - +format+: Request format. Defaults to +nil+. Can be string or symbol.
# - +as+: Content type. Defaults to +nil+. Must be a symbol that corresponds
# to a mime type.
# * `action`: The controller action to call.
# * `method`: Request method used to send the HTTP request. Possible values
# are `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, `HEAD`. Defaults to `GET`.
# Can be a symbol.
# * `params`: The hash with HTTP parameters that you want to pass. This may be
# `nil`.
# * `body`: The request body with a string that is appropriately encoded
# (`application/x-www-form-urlencoded` or `multipart/form-data`).
# * `session`: A hash of parameters to store in the session. This may be
# `nil`.
# * `flash`: A hash of parameters to store in the flash. This may be `nil`.
# * `format`: Request format. Defaults to `nil`. Can be string or symbol.
# * `as`: Content type. Defaults to `nil`. Must be a symbol that corresponds
# to a mime type.
#
# Example calling +create+ action and sending two params:
#
# process :create,
# method: 'POST',
# params: {
# user: { name: 'Gaurish Sharma', email: 'user@example.com' }
# },
# session: { user_id: 1 },
# flash: { notice: 'This is flash message' }
# Example calling `create` action and sending two params:
#
# To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, and +HEAD+ requests
# prefer using #get, #post, #patch, #put, #delete and #head methods
# respectively which will make tests more expressive.
# process :create,
# method: 'POST',
# params: {
# user: { name: 'Gaurish Sharma', email: 'user@example.com' }
# },
# session: { user_id: 1 },
# flash: { notice: 'This is flash message' }
#
# To simulate `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, and `HEAD` requests
# prefer using #get, #post, #patch, #put, #delete and #head methods respectively
# which will make tests more expressive.
#
# It's not recommended to make more than one request in the same test. Instance
# variables that are set in one request will not persist to the next request,
# but it's not guaranteed that all \Rails internal state will be reset. Prefer
# but it's not guaranteed that all Rails internal state will be reset. Prefer
# ActionDispatch::IntegrationTest for making multiple requests in the same test.
#
# Note that the request method is not verified.
@ -654,8 +672,8 @@ def document_root_element
end
def check_required_ivars
# Check for required instance variables so we can give an
# understandable error message.
# Check for required instance variables so we can give an understandable error
# message.
[:@routes, :@controller, :@request, :@response].each do |iv_name|
if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
raise "#{iv_name} is nil: make sure you set it in your test's setup method."

@ -3,26 +3,27 @@
#--
# Copyright (c) David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#++
# :markup: markdown
require "active_support"
require "active_support/rails"
require "active_support/core_ext/module/attribute_accessors"
@ -35,14 +36,14 @@ module Rack # :nodoc:
autoload :Test, "rack/test"
end
# = Action Dispatch
# # Action Dispatch
#
# Action Dispatch is a module of Action Pack.
#
# Action Dispatch parses information about the web request, handles
# routing as defined by the user, and does advanced processing related to HTTP
# such as MIME-type negotiation, decoding parameters in POST, PATCH, or PUT
# bodies, handling HTTP caching logic, cookies and sessions.
# Action Dispatch parses information about the web request, handles routing as
# defined by the user, and does advanced processing related to HTTP such as
# MIME-type negotiation, decoding parameters in POST, PATCH, or PUT bodies,
# handling HTTP caching logic, cookies and sessions.
module ActionDispatch
extend ActiveSupport::Autoload

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "rack/version"
module ActionDispatch

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionDispatch
def self.deprecator # :nodoc:
@deprecator ||= ActiveSupport::Deprecation.new

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionDispatch
module Http
module Cache
@ -32,8 +34,8 @@ def etag_matches?(etag)
end
end
# Check response freshness (+Last-Modified+ and ETag) against request
# +If-Modified-Since+ and +If-None-Match+ conditions. If both headers are
# Check response freshness (`Last-Modified` and ETag) against request
# `If-Modified-Since` and `If-None-Match` conditions. If both headers are
# supplied, both must match, or the request is not considered fresh.
def fresh?(response)
last_modified = if_modified_since
@ -79,25 +81,24 @@ def date=(utc_time)
set_header DATE, utc_time.httpdate
end
# This method sets a weak ETag validator on the response so browsers
# and proxies may cache the response, keyed on the ETag. On subsequent
# requests, the +If-None-Match+ header is set to the cached ETag. If it
# matches the current ETag, we can return a <tt>304 Not Modified</tt> response
# with no body, letting the browser or proxy know that their cache is
# current. Big savings in request time and network bandwidth.
# This method sets a weak ETag validator on the response so browsers and proxies
# may cache the response, keyed on the ETag. On subsequent requests, the
# `If-None-Match` header is set to the cached ETag. If it matches the current
# ETag, we can return a `304 Not Modified` response with no body, letting the
# browser or proxy know that their cache is current. Big savings in request time
# and network bandwidth.
#
# Weak ETags are considered to be semantically equivalent but not
# byte-for-byte identical. This is perfect for browser caching of HTML
# pages where we don't care about exact equality, just what the user
# is viewing.
# Weak ETags are considered to be semantically equivalent but not byte-for-byte
# identical. This is perfect for browser caching of HTML pages where we don't
# care about exact equality, just what the user is viewing.
#
# Strong ETags are considered byte-for-byte identical. They allow a
# browser or proxy cache to support +Range+ requests, useful for paging
# through a PDF file or scrubbing through a video. Some CDNs only
# support strong ETags and will ignore weak ETags entirely.
# Strong ETags are considered byte-for-byte identical. They allow a browser or
# proxy cache to support `Range` requests, useful for paging through a PDF file
# or scrubbing through a video. Some CDNs only support strong ETags and will
# ignore weak ETags entirely.
#
# Weak ETags are what we almost always need, so they're the default.
# Check out #strong_etag= to provide a strong ETag validator.
# Weak ETags are what we almost always need, so they're the default. Check out
# #strong_etag= to provide a strong ETag validator.
def etag=(weak_validators)
self.weak_etag = weak_validators
end
@ -112,12 +113,13 @@ def strong_etag=(strong_validators)
def etag?; etag; end
# True if an ETag is set, and it's a weak validator (preceded with <tt>W/</tt>).
# True if an ETag is set, and it's a weak validator (preceded with `W/`).
def weak_etag?
etag? && etag.start_with?('W/"')
end
# True if an ETag is set, and it isn't a weak validator (not preceded with <tt>W/</tt>).
# True if an ETag is set, and it isn't a weak validator (not preceded with
# `W/`).
def strong_etag?
etag? && !weak_etag?
end
@ -171,10 +173,9 @@ def prepare_cache_control!
MUST_REVALIDATE = "must-revalidate"
def handle_conditional_get!
# Normally default cache control setting is handled by ETag
# middleware. But, if an etag is already set, the middleware
# defaults to `no-cache` unless a default `Cache-Control` value is
# previously set. So, set a default one here.
# Normally default cache control setting is handled by ETag middleware. But, if
# an etag is already set, the middleware defaults to `no-cache` unless a default
# `Cache-Control` value is previously set. So, set a default one here.
if (etag? || last_modified?) && !self._cache_control
self._cache_control = DEFAULT_CACHE_CONTROL
end
@ -186,8 +187,8 @@ def merge_and_normalize_cache_control!(cache_control)
return if control.empty? && cache_control.empty? # Let middleware handle default behavior
if cache_control.any?
# Any caching directive coming from a controller overrides
# no-cache/no-store in the default Cache-Control header.
# Any caching directive coming from a controller overrides no-cache/no-store in
# the default Cache-Control header.
control.delete(:no_cache)
control.delete(:no_store)

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionDispatch
module Http
class ContentDisposition # :nodoc:

@ -1,28 +1,31 @@
# frozen_string_literal: true
# :markup: markdown
require "active_support/core_ext/object/deep_dup"
require "active_support/core_ext/array/wrap"
module ActionDispatch # :nodoc:
# = Action Dispatch Content Security Policy
# # Action Dispatch Content Security Policy
#
# Configures the HTTP
# {Content-Security-Policy}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy]
# response header to help protect against XSS and injection attacks.
# Configures the HTTP [Content-Security-Policy]
# (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
# response header to help protect against XSS and
# injection attacks.
#
# Example global policy:
#
# Rails.application.config.content_security_policy do |policy|
# policy.default_src :self, :https
# policy.font_src :self, :https, :data
# policy.img_src :self, :https, :data
# policy.object_src :none
# policy.script_src :self, :https
# policy.style_src :self, :https
# Rails.application.config.content_security_policy do |policy|
# policy.default_src :self, :https
# policy.font_src :self, :https, :data
# policy.img_src :self, :https, :data
# policy.object_src :none
# policy.script_src :self, :https
# policy.style_src :self, :https
#
# # Specify URI for violation reports
# policy.report_uri "/csp-violation-report-endpoint"
# end
# # Specify URI for violation reports
# policy.report_uri "/csp-violation-report-endpoint"
# end
class ContentSecurityPolicy
class Middleware
def initialize(app)
@ -32,8 +35,8 @@ def initialize(app)
def call(env)
status, headers, _ = response = @app.call(env)
# Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
# CSP headers might not match nonces in the cached HTML.
# Returning CSP headers with a 304 Not Modified is harmful, since nonces in the
# new CSP headers might not match nonces in the cached HTML.
return response if status == 304
return response if policy_present?(headers)
@ -190,14 +193,14 @@ def initialize_copy(other)
end
end
# Specify whether to prevent the user agent from loading any assets over
# HTTP when the page uses HTTPS:
# Specify whether to prevent the user agent from loading any assets over HTTP
# when the page uses HTTPS:
#
# policy.block_all_mixed_content
# policy.block_all_mixed_content
#
# Pass +false+ to allow it again:
# Pass `false` to allow it again:
#
# policy.block_all_mixed_content false
# policy.block_all_mixed_content false
#
def block_all_mixed_content(enabled = true)
if enabled
@ -209,11 +212,11 @@ def block_all_mixed_content(enabled = true)
# Restricts the set of plugins that can be embedded:
#
# policy.plugin_types "application/x-shockwave-flash"
# policy.plugin_types "application/x-shockwave-flash"
#
# Leave empty to allow all plugins:
#
# policy.plugin_types
# policy.plugin_types
#
def plugin_types(*types)
if types.first
@ -223,23 +226,25 @@ def plugin_types(*types)
end
end
# Enable the {report-uri}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri]
# directive. Violation reports will be sent to the specified URI:
# Enable the [report-uri]
# (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri)
# directive. Violation reports will be sent to the
# specified URI:
#
# policy.report_uri "/csp-violation-report-endpoint"
# policy.report_uri "/csp-violation-report-endpoint"
#
def report_uri(uri)
@directives["report-uri"] = [uri]
end
# Specify asset types for which {Subresource Integrity}[https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity]
# is required:
# Specify asset types for which [Subresource Integrity]
# (https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) is required:
#
# policy.require_sri_for :script, :style
# policy.require_sri_for :script, :style
#
# Leave empty to not require Subresource Integrity:
#
# policy.require_sri_for
# policy.require_sri_for
#
def require_sri_for(*types)
if types.first
@ -249,18 +254,19 @@ def require_sri_for(*types)
end
end
# Specify whether a {sandbox}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox]
# Specify whether a [sandbox]
# (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox)
# should be enabled for the requested resource:
#
# policy.sandbox
# policy.sandbox
#
# Values can be passed as arguments:
#
# policy.sandbox "allow-scripts", "allow-modals"
# policy.sandbox "allow-scripts", "allow-modals"
#
# Pass +false+ to disable the sandbox:
# Pass `false` to disable the sandbox:
#
# policy.sandbox false
# policy.sandbox false
#
def sandbox(*values)
if values.empty?
@ -274,11 +280,11 @@ def sandbox(*values)
# Specify whether user agents should treat any assets over HTTP as HTTPS:
#
# policy.upgrade_insecure_requests
# policy.upgrade_insecure_requests
#
# Pass +false+ to disable it:
# Pass `false` to disable it:
#
# policy.upgrade_insecure_requests false
# policy.upgrade_insecure_requests false
#
def upgrade_insecure_requests(enabled = true)
if enabled

@ -1,18 +1,21 @@
# frozen_string_literal: true
# :markup: markdown
require "active_support/parameter_filter"
module ActionDispatch
module Http
# = Action Dispatch HTTP Filter Parameters
# # Action Dispatch HTTP Filter Parameters
#
# Allows you to specify sensitive query string and POST parameters to filter
# from the request log.
#
# # Replaces values with "[FILTERED]" for keys that match /foo|bar/i.
# env["action_dispatch.parameter_filter"] = [:foo, "bar"]
# # Replaces values with "[FILTERED]" for keys that match /foo|bar/i.
# env["action_dispatch.parameter_filter"] = [:foo, "bar"]
#
# For more information about filter behavior, see ActiveSupport::ParameterFilter.
# For more information about filter behavior, see
# ActiveSupport::ParameterFilter.
module FilterParameters
ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc:
@ -43,7 +46,8 @@ def filtered_path
@filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
end
# Returns the +ActiveSupport::ParameterFilter+ object used to filter in this request.
# Returns the `ActiveSupport::ParameterFilter` object used to filter in this
# request.
def parameter_filter
@parameter_filter ||= if has_header?("action_dispatch.parameter_filter")
parameter_filter_for get_header("action_dispatch.parameter_filter")

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionDispatch
module Http
module FilterRedirect

@ -1,28 +1,30 @@
# frozen_string_literal: true
# :markup: markdown
module ActionDispatch
module Http
# = Action Dispatch HTTP \Headers
# # Action Dispatch HTTP Headers
#
# Provides access to the request's HTTP headers from the environment.
#
# env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
# headers = ActionDispatch::Http::Headers.from_hash(env)
# headers["Content-Type"] # => "text/plain"
# headers["User-Agent"] # => "curl/7.43.0"
# env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
# headers = ActionDispatch::Http::Headers.from_hash(env)
# headers["Content-Type"] # => "text/plain"
# headers["User-Agent"] # => "curl/7.43.0"
#
# Also note that when headers are mapped to CGI-like variables by the Rack
# server, both dashes and underscores are converted to underscores. This
# ambiguity cannot be resolved at this stage anymore. Both underscores and
# dashes have to be interpreted as if they were originally sent as dashes.
#
# # GET / HTTP/1.1
# # ...
# # User-Agent: curl/7.43.0
# # X_Custom_Header: token
# # GET / HTTP/1.1
# # ...
# # User-Agent: curl/7.43.0
# # X_Custom_Header: token
#
# headers["X_Custom_Header"] # => nil
# headers["X-Custom-Header"] # => "token"
# headers["X_Custom_Header"] # => nil
# headers["X-Custom-Header"] # => "token"
class Headers
CGI_VARIABLES = Set.new(%W[
AUTH_TYPE
@ -67,7 +69,7 @@ def []=(key, value)
@req.set_header env_name(key), value
end
# Add a value to a multivalued header like +Vary+ or +Accept-Encoding+.
# Add a value to a multivalued header like `Vary` or `Accept-Encoding`.
def add(key, value)
@req.add_header env_name(key), value
end
@ -81,11 +83,10 @@ def key?(key)
# Returns the value for the given key mapped to @env.
#
# If the key is not found and an optional code block is not provided,
# raises a <tt>KeyError</tt> exception.
# If the key is not found and an optional code block is not provided, raises a
# `KeyError` exception.
#
# If the code block is provided, then it will be run and
# its result returned.
# If the code block is provided, then it will be run and its result returned.
def fetch(key, default = DEFAULT)
@req.fetch_header(env_name(key)) do
return default unless default == DEFAULT
@ -99,16 +100,15 @@ def each(&block)
end
# Returns a new Http::Headers instance containing the contents of
# <tt>headers_or_env</tt> and the original instance.
# `headers_or_env` and the original instance.
def merge(headers_or_env)
headers = @req.dup.headers
headers.merge!(headers_or_env)
headers
end
# Adds the contents of <tt>headers_or_env</tt> to original instance
# entries; duplicate keys are overwritten with the values from
# <tt>headers_or_env</tt>.
# Adds the contents of `headers_or_env` to original instance entries; duplicate
# keys are overwritten with the values from `headers_or_env`.
def merge!(headers_or_env)
headers_or_env.each do |key, value|
@req.set_header env_name(key), value
@ -118,8 +118,8 @@ def merge!(headers_or_env)
def env; @req.env.dup; end
private
# Converts an HTTP header name to an environment variable name if it is
# not contained within the headers hash.
# Converts an HTTP header name to an environment variable name if it is not
# contained within the headers hash.
def env_name(key)
key = key.to_s
if HTTP_HEADER.match?(key)

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "active_support/core_ext/module/attribute_accessors"
module ActionDispatch
@ -18,7 +20,7 @@ class InvalidType < ::Mime::Type::InvalidMimeType; end
mattr_accessor :ignore_accept_header, default: false
end
# The MIME type of the HTTP request, such as Mime[:xml].
# The MIME type of the HTTP request, such as [Mime](:xml).
def content_mime_type
fetch_header("action_dispatch.request.content_type") do |k|
v = if get_header("CONTENT_TYPE") =~ /^([^,;]*)/
@ -52,11 +54,11 @@ def accepts
end
end
# Returns the MIME type for the \format used in the request.
# Returns the MIME type for the format used in the request.
#
# GET /posts/5.xml | request.format => Mime[:xml]
# GET /posts/5.xhtml | request.format => Mime[:html]
# GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first
# GET /posts/5.xml | request.format => Mime[:xml]
# GET /posts/5.xhtml | request.format => Mime[:html]
# GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first
#
def format(_view_path = nil)
formats.first || Mime::NullType.instance
@ -84,7 +86,7 @@ def formats
end
end
# Sets the \variant for template.
# Sets the variant for template.
def variant=(variant)
variant = Array(variant)
@ -99,36 +101,37 @@ def variant
@variant ||= ActiveSupport::ArrayInquirer.new
end
# Sets the \format by string extension, which can be used to force custom formats
# Sets the format by string extension, which can be used to force custom formats
# that are not controlled by the extension.
#
# class ApplicationController < ActionController::Base
# before_action :adjust_format_for_iphone
# class ApplicationController < ActionController::Base
# before_action :adjust_format_for_iphone
#
# private
# def adjust_format_for_iphone
# request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
# end
# end
# private
# def adjust_format_for_iphone
# request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
# end
# end
def format=(extension)
parameters[:format] = extension.to_s
set_header "action_dispatch.request.formats", [Mime::Type.lookup_by_extension(parameters[:format])]
end
# Sets the \formats by string extensions. This differs from #format= by allowing you
# to set multiple, ordered formats, which is useful when you want to have a fallback.
# Sets the formats by string extensions. This differs from #format= by allowing
# you to set multiple, ordered formats, which is useful when you want to have a
# fallback.
#
# In this example, the +:iphone+ format will be used if it's available, otherwise it'll fall back
# to the +:html+ format.
# In this example, the `:iphone` format will be used if it's available,
# otherwise it'll fall back to the `:html` format.
#
# class ApplicationController < ActionController::Base
# before_action :adjust_format_for_iphone_with_html_fallback
# class ApplicationController < ActionController::Base
# before_action :adjust_format_for_iphone_with_html_fallback
#
# private
# def adjust_format_for_iphone_with_html_fallback
# request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/]
# end
# end
# private
# def adjust_format_for_iphone_with_html_fallback
# request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/]
# end
# end
def formats=(extensions)
parameters[:format] = extensions.first.to_s
set_header "action_dispatch.request.formats", extensions.collect { |extension|
@ -154,8 +157,8 @@ def should_apply_vary_header?
end
private
# We use normal content negotiation unless you include */* in your list,
# in which case we assume you're a browser and send HTML.
# We use normal content negotiation unless you include **/** in your list, in
# which case we assume you're a browser and send HTML.
BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
def params_readable?

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "singleton"
module Mime
@ -65,19 +67,20 @@ def fetch(type, &block)
end
end
# Encapsulates the notion of a MIME type. Can be used at render time, for example, with:
# Encapsulates the notion of a MIME type. Can be used at render time, for
# example, with:
#
# class PostsController < ActionController::Base
# def show
# @post = Post.find(params[:id])
# class PostsController < ActionController::Base
# def show
# @post = Post.find(params[:id])
#
# respond_to do |format|
# format.html
# format.ics { render body: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") }
# format.xml { render xml: @post }
# respond_to do |format|
# format.html
# format.ics { render body: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") }
# format.xml { render xml: @post }
# end
# end
# end
# end
class Type
attr_reader :symbol
@ -170,8 +173,9 @@ def lookup_by_extension(extension)
EXTENSION_LOOKUP[extension.to_s]
end
# Registers an alias that's not used on MIME type lookup, but can be referenced directly. Especially useful for
# rendering different HTML versions depending on the user agent, like an iPhone.
# Registers an alias that's not used on MIME type lookup, but can be referenced
# directly. Especially useful for rendering different HTML versions depending on
# the user agent, like an iPhone.
def register_alias(string, symbol, extension_synonyms = [])
register(string, symbol, [], extension_synonyms, true)
end
@ -221,11 +225,11 @@ def parse_trailing_star(accept_header)
parse_data_with_trailing_star($1) if accept_header =~ TRAILING_STAR_REGEXP
end
# For an input of <tt>'text'</tt>, returns <tt>[Mime[:json], Mime[:xml], Mime[:ics],
# Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]]</tt>.
# For an input of `'text'`, returns `[Mime[:json], Mime[:xml], Mime[:ics],
# Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]]`.
#
# For an input of <tt>'application'</tt>, returns <tt>[Mime[:html], Mime[:js],
# Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]]</tt>.
# For an input of `'application'`, returns `[Mime[:html], Mime[:js], Mime[:xml],
# Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]]`.
def parse_data_with_trailing_star(type)
Mime::SET.select { |m| m.match?(type) }
end
@ -234,7 +238,7 @@ def parse_data_with_trailing_star(type)
#
# To unregister a MIME type:
#
# Mime::Type.unregister(:mobile)
# Mime::Type.unregister(:mobile)
def unregister(symbol)
symbol = symbol.downcase
if mime = Mime[symbol]
@ -350,9 +354,9 @@ def all?; true; end
def html?; true; end
end
# ALL isn't a real MIME type, so we don't register it for lookup with the
# other concrete types. It's a wildcard match that we use for +respond_to+
# negotiation internals.
# ALL isn't a real MIME type, so we don't register it for lookup with the other
# concrete types. It's a wildcard match that we use for `respond_to` negotiation
# internals.
ALL = AllType.instance
class NullType

@ -3,6 +3,8 @@
# Build list of Mime types for HTTP responses
# https://www.iana.org/assignments/media-types/
# :markup: markdown
Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
Mime::Type.register "text/plain", :text, [], %w(txt)
Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript )

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionDispatch
module Http
module Parameters
@ -14,8 +16,8 @@ module Parameters
}
}
# Raised when raw data from the request cannot be parsed by the parser
# defined for request's content MIME type.
# Raised when raw data from the request cannot be parsed by the parser defined
# for request's content MIME type.
class ParseError < StandardError
def initialize(message = $!.message)
super(message)
@ -34,8 +36,8 @@ class << self
module ClassMethods
# Configure the parameter parser for a given MIME type.
#
# It accepts a hash where the key is the symbol of the MIME type
# and the value is a proc.
# It accepts a hash where the key is the symbol of the MIME type and the value
# is a proc.
#
# original_parsers = ActionDispatch::Request.parameter_parsers
# xml_parser = -> (raw_post) { Hash.from_xml(raw_post) || {} }
@ -46,7 +48,7 @@ def parameter_parsers=(parsers)
end
end
# Returns both GET and POST \parameters in a single hash.
# Returns both GET and POST parameters in a single hash.
def parameters
params = get_header("action_dispatch.request.parameters")
return params if params
@ -66,8 +68,8 @@ def path_parameters=(parameters) # :nodoc:
delete_header("action_dispatch.request.parameters")
parameters = Request::Utils.set_binary_encoding(self, parameters, parameters[:controller], parameters[:action])
# If any of the path parameters has an invalid encoding then
# raise since it's likely to trigger errors further on.
# If any of the path parameters has an invalid encoding then raise since it's
# likely to trigger errors further on.
Request::Utils.check_param_encoding(parameters)
set_header PARAMETERS_KEY, parameters
@ -75,10 +77,10 @@ def path_parameters=(parameters) # :nodoc:
raise ActionController::BadRequest.new("Invalid path parameters: #{e.message}")
end
# Returns a hash with the \parameters used to form the \path of the request.
# Returns a hash with the parameters used to form the path of the request.
# Returned hash keys are symbols:
#
# { action: "my_action", controller: "my_controller" }
# { action: "my_action", controller: "my_controller" }
def path_parameters
get_header(PARAMETERS_KEY) || set_header(PARAMETERS_KEY, {})
end

@ -1,31 +1,33 @@
# frozen_string_literal: true
# :markup: markdown
require "active_support/core_ext/object/deep_dup"
module ActionDispatch # :nodoc:
# = Action Dispatch \PermissionsPolicy
# # Action Dispatch PermissionsPolicy
#
# Configures the HTTP
# {Feature-Policy}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy]
# response header to specify which browser features the current document and
# its iframes can use.
# [Feature-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy)
# response header to specify which browser features the current
# document and its iframes can use.
#
# Example global policy:
#
# Rails.application.config.permissions_policy do |policy|
# policy.camera :none
# policy.gyroscope :none
# policy.microphone :none
# policy.usb :none
# policy.fullscreen :self
# policy.payment :self, "https://secure.example.com"
# end
# Rails.application.config.permissions_policy do |policy|
# policy.camera :none
# policy.gyroscope :none
# policy.microphone :none
# policy.usb :none
# policy.fullscreen :self
# policy.payment :self, "https://secure.example.com"
# end
#
# The Feature-Policy header has been renamed to Permissions-Policy.
# The Permissions-Policy requires a different implementation and isn't
# yet supported by all browsers. To avoid having to rename this
# middleware in the future we use the new name for the middleware but
# keep the old header name and implementation for now.
# The Feature-Policy header has been renamed to Permissions-Policy. The
# Permissions-Policy requires a different implementation and isn't yet supported
# by all browsers. To avoid having to rename this middleware in the future we
# use the new name for the middleware but keep the old header name and
# implementation for now.
class PermissionsPolicy
class Middleware
def initialize(app)

@ -2,6 +2,8 @@
# :enddoc:
# :markup: markdown
require "rack/cache"
require "rack/cache/context"
require "active_support/cache"

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "stringio"
require "active_support/inflector"
@ -102,26 +104,26 @@ def controller_class_for(name)
# Returns true if the request has a header matching the given key parameter.
#
# request.key? :ip_spoofing_check # => true
# request.key? :ip_spoofing_check # => true
def key?(key)
has_header? key
end
# HTTP methods from {RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1}[https://www.ietf.org/rfc/rfc2616.txt]
# HTTP methods from [RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1](https://www.ietf.org/rfc/rfc2616.txt)
RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT)
# HTTP methods from {RFC 2518: HTTP Extensions for Distributed Authoring -- WEBDAV}[https://www.ietf.org/rfc/rfc2518.txt]
# HTTP methods from [RFC 2518: HTTP Extensions for Distributed Authoring -- WEBDAV](https://www.ietf.org/rfc/rfc2518.txt)
RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
# HTTP methods from {RFC 3253: Versioning Extensions to WebDAV}[https://www.ietf.org/rfc/rfc3253.txt]
# HTTP methods from [RFC 3253: Versioning Extensions to WebDAV](https://www.ietf.org/rfc/rfc3253.txt)
RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY)
# HTTP methods from {RFC 3648: WebDAV Ordered Collections Protocol}[https://www.ietf.org/rfc/rfc3648.txt]
# HTTP methods from [RFC 3648: WebDAV Ordered Collections Protocol](https://www.ietf.org/rfc/rfc3648.txt)
RFC3648 = %w(ORDERPATCH)
# HTTP methods from {RFC 3744: WebDAV Access Control Protocol}[https://www.ietf.org/rfc/rfc3744.txt]
# HTTP methods from [RFC 3744: WebDAV Access Control Protocol](https://www.ietf.org/rfc/rfc3744.txt)
RFC3744 = %w(ACL)
# HTTP methods from {RFC 5323: WebDAV SEARCH}[https://www.ietf.org/rfc/rfc5323.txt]
# HTTP methods from [RFC 5323: WebDAV SEARCH](https://www.ietf.org/rfc/rfc5323.txt)
RFC5323 = %w(SEARCH)
# HTTP methods from {RFC 4791: Calendaring Extensions to WebDAV}[https://www.ietf.org/rfc/rfc4791.txt]
# HTTP methods from [RFC 4791: Calendaring Extensions to WebDAV](https://www.ietf.org/rfc/rfc4791.txt)
RFC4791 = %w(MKCALENDAR)
# HTTP methods from {RFC 5789: PATCH Method for HTTP}[https://www.ietf.org/rfc/rfc5789.txt]
# HTTP methods from [RFC 5789: PATCH Method for HTTP](https://www.ietf.org/rfc/rfc5789.txt)
RFC5789 = %w(PATCH)
HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789
@ -135,20 +137,19 @@ def key?(key)
alias raw_request_method request_method # :nodoc:
# Returns the HTTP \method that the application should see.
# In the case where the \method was overridden by a middleware
# (for instance, if a HEAD request was converted to a GET,
# or if a _method parameter was used to determine the \method
# the application should use), this \method returns the overridden
# value, not the original.
# Returns the HTTP method that the application should see. In the case where the
# method was overridden by a middleware (for instance, if a HEAD request was
# converted to a GET, or if a _method parameter was used to determine the method
# the application should use), this method returns the overridden value, not the
# original.
def request_method
@request_method ||= check_method(super)
end
# Returns the URI pattern of the matched route for the request,
# using the same format as <tt>bin/rails routes</tt>:
# Returns the URI pattern of the matched route for the request, using the same
# format as `bin/rails routes`:
#
# request.route_uri_pattern # => "/:controller(/:action(/:id))(.:format)"
# request.route_uri_pattern # => "/:controller(/:action(/:id))(.:format)"
def route_uri_pattern
get_header("action_dispatch.route_uri_pattern")
end
@ -196,12 +197,11 @@ def request_method_symbol
HTTP_METHOD_LOOKUP[request_method]
end
# Returns the original value of the environment's REQUEST_METHOD,
# even if it was overridden by middleware. See #request_method for
# more information.
# Returns the original value of the environment's REQUEST_METHOD, even if it was
# overridden by middleware. See #request_method for more information.
#
# For debugging purposes, when called with arguments this method will
# fall back to Object#method
# For debugging purposes, when called with arguments this method will fall back
# to Object#method
def method(*args)
if args.empty?
@method ||= check_method(
@ -221,60 +221,61 @@ def method_symbol
# Provides access to the request's HTTP headers, for example:
#
# request.headers["Content-Type"] # => "text/plain"
# request.headers["Content-Type"] # => "text/plain"
def headers
@headers ||= Http::Headers.new(self)
end
# Early Hints is an HTTP/2 status code that indicates hints to help a client start
# making preparations for processing the final response.
# Early Hints is an HTTP/2 status code that indicates hints to help a client
# start making preparations for processing the final response.
#
# If the env contains +rack.early_hints+ then the server accepts HTTP2 push for Link headers.
# If the env contains `rack.early_hints` then the server accepts HTTP2 push for
# Link headers.
#
# The +send_early_hints+ method accepts a hash of links as follows:
# The `send_early_hints` method accepts a hash of links as follows:
#
# send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload")
# send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload")
#
# If you are using +javascript_include_tag+ or +stylesheet_link_tag+ the
# Early Hints headers are included by default if supported.
# If you are using `javascript_include_tag` or `stylesheet_link_tag` the Early
# Hints headers are included by default if supported.
def send_early_hints(links)
env["rack.early_hints"]&.call(links)
end
# Returns a +String+ with the last requested path including their params.
# Returns a `String` with the last requested path including their params.
#
# # get '/foo'
# request.original_fullpath # => '/foo'
# # get '/foo'
# request.original_fullpath # => '/foo'
#
# # get '/foo?bar'
# request.original_fullpath # => '/foo?bar'
# # get '/foo?bar'
# request.original_fullpath # => '/foo?bar'
def original_fullpath
@original_fullpath ||= (get_header("ORIGINAL_FULLPATH") || fullpath)
end
# Returns the +String+ full path including params of the last URL requested.
# Returns the `String` full path including params of the last URL requested.
#
# # get "/articles"
# request.fullpath # => "/articles"
# # get "/articles"
# request.fullpath # => "/articles"
#
# # get "/articles?page=2"
# request.fullpath # => "/articles?page=2"
# # get "/articles?page=2"
# request.fullpath # => "/articles?page=2"
def fullpath
@fullpath ||= super
end
# Returns the original request URL as a +String+.
# Returns the original request URL as a `String`.
#
# # get "/articles?page=2"
# request.original_url # => "http://www.example.com/articles?page=2"
# # get "/articles?page=2"
# request.original_url # => "http://www.example.com/articles?page=2"
def original_url
base_url + original_fullpath
end
# The +String+ MIME type of the request.
# The `String` MIME type of the request.
#
# # get "/articles"
# request.media_type # => "application/x-www-form-urlencoded"
# # get "/articles"
# request.media_type # => "application/x-www-form-urlencoded"
def media_type
content_mime_type&.to_s
end
@ -285,7 +286,7 @@ def content_length
super.to_i
end
# Returns true if the +X-Requested-With+ header contains "XMLHttpRequest"
# Returns true if the `X-Requested-With` header contains "XMLHttpRequest"
# (case-insensitive), which may need to be manually added depending on the
# choice of JavaScript libraries and frameworks.
def xml_http_request?
@ -293,13 +294,13 @@ def xml_http_request?
end
alias :xhr? :xml_http_request?
# Returns the IP address of client as a +String+.
# Returns the IP address of client as a `String`.
def ip
@ip ||= super
end
# Returns the IP address of client as a +String+,
# usually set by the RemoteIp middleware.
# Returns the IP address of client as a `String`, usually set by the RemoteIp
# middleware.
def remote_ip
@remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s
end
@ -311,12 +312,14 @@ def remote_ip=(remote_ip)
ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id" # :nodoc:
# Returns the unique request id, which is based on either the +X-Request-Id+ header that can
# be generated by a firewall, load balancer, or web server, or by the RequestId middleware
# (which sets the +action_dispatch.request_id+ environment variable).
# Returns the unique request id, which is based on either the `X-Request-Id`
# header that can be generated by a firewall, load balancer, or web server, or
# by the RequestId middleware (which sets the `action_dispatch.request_id`
# environment variable).
#
# This unique ID is useful for tracing a request from end-to-end as part of logging or debugging.
# This relies on the Rack variable set by the ActionDispatch::RequestId middleware.
# This unique ID is useful for tracing a request from end-to-end as part of
# logging or debugging. This relies on the Rack variable set by the
# ActionDispatch::RequestId middleware.
def request_id
get_header ACTION_DISPATCH_REQUEST_ID
end
@ -332,8 +335,8 @@ def server_software
(get_header("SERVER_SOFTWARE") && /^([a-zA-Z]+)/ =~ get_header("SERVER_SOFTWARE")) ? $1.downcase : nil
end
# Read the request \body. This is useful for web services that need to
# work with raw requests directly.
# Read the request body. This is useful for web services that need to work with
# raw requests directly.
def raw_post
unless has_header? "RAW_POST_DATA"
set_header("RAW_POST_DATA", read_body_stream)
@ -353,14 +356,13 @@ def body
end
end
# Determine whether the request body contains form-data by checking
# the request +Content-Type+ for one of the media-types:
# +application/x-www-form-urlencoded+ or +multipart/form-data+. The
# list of form-data media types can be modified through the
# +FORM_DATA_MEDIA_TYPES+ array.
# Determine whether the request body contains form-data by checking the request
# `Content-Type` for one of the media-types: `application/x-www-form-urlencoded`
# or `multipart/form-data`. The list of form-data media types can be modified
# through the `FORM_DATA_MEDIA_TYPES` array.
#
# A request body is not assumed to contain form-data when no
# +Content-Type+ header is provided and the request_method is POST.
# A request body is not assumed to contain form-data when no `Content-Type`
# header is provided and the request_method is POST.
def form_data?
FORM_DATA_MEDIA_TYPES.include?(media_type)
end
@ -413,8 +415,8 @@ def POST
end
alias :request_parameters :POST
# Returns the authorization header regardless of whether it was specified directly or through one of the
# proxy alternatives.
# Returns the authorization header regardless of whether it was specified
# directly or through one of the proxy alternatives.
def authorization
get_header("HTTP_AUTHORIZATION") ||
get_header("X-HTTP_AUTHORIZATION") ||

@ -1,38 +1,40 @@
# frozen_string_literal: true
# :markup: markdown
require "active_support/core_ext/module/attribute_accessors"
require "action_dispatch/http/filter_redirect"
require "action_dispatch/http/cache"
require "monitor"
module ActionDispatch # :nodoc:
# = Action Dispatch \Response
# # Action Dispatch Response
#
# Represents an HTTP response generated by a controller action. Use it to
# retrieve the current state of the response, or customize the response. It can
# either represent a real HTTP response (i.e. one that is meant to be sent
# back to the web browser) or a TestResponse (i.e. one that is generated
# from integration tests).
# either represent a real HTTP response (i.e. one that is meant to be sent back
# to the web browser) or a TestResponse (i.e. one that is generated from
# integration tests).
#
# The \Response object for the current request is exposed on controllers as
# ActionController::Metal#response. ActionController::Metal also provides a
# few additional methods that delegate to attributes of the \Response such as
# The Response object for the current request is exposed on controllers as
# ActionController::Metal#response. ActionController::Metal also provides a few
# additional methods that delegate to attributes of the Response such as
# ActionController::Metal#headers.
#
# Integration tests will likely also want to inspect responses in
# more detail. Methods such as Integration::RequestHelpers#get
# and Integration::RequestHelpers#post return instances of
# TestResponse (which inherits from \Response) for this purpose.
# Integration tests will likely also want to inspect responses in more detail.
# Methods such as Integration::RequestHelpers#get and
# Integration::RequestHelpers#post return instances of TestResponse (which
# inherits from Response) for this purpose.
#
# For example, the following demo integration test prints the body of the
# controller response to the console:
#
# class DemoControllerTest < ActionDispatch::IntegrationTest
# def test_print_root_path_to_console
# get('/')
# puts response.body
# end
# end
# class DemoControllerTest < ActionDispatch::IntegrationTest
# def test_print_root_path_to_console
# get('/')
# puts response.body
# end
# end
class Response
begin
# For `Rack::Headers` (Rack 3+):
@ -55,17 +57,17 @@ class Response
# The headers for the response.
#
# header["Content-Type"] # => "text/plain"
# header["Content-Type"] = "application/json"
# header["Content-Type"] # => "application/json"
# header["Content-Type"] # => "text/plain"
# header["Content-Type"] = "application/json"
# header["Content-Type"] # => "application/json"
#
# Also aliased as +headers+.
# Also aliased as `headers`.
#
# headers["Content-Type"] # => "text/plain"
# headers["Content-Type"] = "application/json"
# headers["Content-Type"] # => "application/json"
# headers["Content-Type"] # => "text/plain"
# headers["Content-Type"] = "application/json"
# headers["Content-Type"] # => "application/json"
#
# Also aliased as +header+ for compatibility.
# Also aliased as `header` for compatibility.
attr_reader :headers
alias_method :header, :headers
@ -234,13 +236,13 @@ def status=(status)
@status = Rack::Utils.status_code(status)
end
# Sets the HTTP response's content MIME type. For example, in the controller
# you could write this:
# Sets the HTTP response's content MIME type. For example, in the controller you
# could write this:
#
# response.content_type = "text/plain"
# response.content_type = "text/plain"
#
# If a character set has been defined for this response (see charset=) then
# the character set information will also be included in the content type
# If a character set has been defined for this response (see charset=) then the
# character set information will also be included in the content type
# information.
def content_type=(content_type)
return unless content_type
@ -267,11 +269,11 @@ def sending_file=(v)
end
end
# Sets the HTTP character set. In case of +nil+ parameter
# it sets the charset to +default_charset+.
# Sets the HTTP character set. In case of `nil` parameter it sets the charset to
# `default_charset`.
#
# response.charset = 'utf-16' # => 'utf-16'
# response.charset = nil # => 'utf-8'
# response.charset = 'utf-16' # => 'utf-16'
# response.charset = nil # => 'utf-8'
def charset=(charset)
content_type = parsed_content_type_header.mime_type
if false == charset
@ -281,8 +283,8 @@ def charset=(charset)
end
end
# The charset of the response. HTML wants to know the encoding of the
# content you're giving them, so we need to send that along.
# The charset of the response. HTML wants to know the encoding of the content
# you're giving them, so we need to send that along.
def charset
header_info = parsed_content_type_header
header_info.charset || self.class.default_charset
@ -293,26 +295,26 @@ def response_code
@status
end
# Returns a string to ensure compatibility with +Net::HTTPResponse+.
# Returns a string to ensure compatibility with `Net::HTTPResponse`.
def code
@status.to_s
end
# Returns the corresponding message for the current HTTP status code:
#
# response.status = 200
# response.message # => "OK"
# response.status = 200
# response.message # => "OK"
#
# response.status = 404
# response.message # => "Not Found"
# response.status = 404
# response.message # => "Not Found"
#
def message
Rack::Utils::HTTP_STATUS_CODES[@status]
end
alias_method :status_message, :message
# Returns the content of the response as a string. This contains the contents
# of any calls to <tt>render</tt>.
# Returns the content of the response as a string. This contains the contents of
# any calls to `render`.
def body
@stream.body
end
@ -332,9 +334,9 @@ def body=(body)
end
end
# Avoid having to pass an open file handle as the response body.
# Rack::Sendfile will usually intercept the response and uses
# the path directly, so there is no reason to open the file.
# Avoid having to pass an open file handle as the response body. Rack::Sendfile
# will usually intercept the response and uses the path directly, so there is no
# reason to open the file.
class FileBody # :nodoc:
attr_reader :to_path
@ -356,7 +358,7 @@ def each
end
end
# Send the file stored at +path+ as the response body.
# Send the file stored at `path` as the response body.
def send_file(path)
commit!
@stream = FileBody.new(path)
@ -383,17 +385,16 @@ def abort
if stream.respond_to?(:abort)
stream.abort
elsif stream.respond_to?(:close)
# `stream.close` should really be reserved for a close from the
# other direction, but we must fall back to it for
# compatibility.
# `stream.close` should really be reserved for a close from the other direction,
# but we must fall back to it for compatibility.
stream.close
end
end
# Turns the Response into a Rack-compatible array of the status, headers,
# and body. Allows explicit splatting:
# Turns the Response into a Rack-compatible array of the status, headers, and
# body. Allows explicit splatting:
#
# status, headers, body = *response
# status, headers, body = *response
def to_a
commit!
rack_response @status, @headers.to_hash
@ -402,7 +403,7 @@ def to_a
# Returns the response cookies, converted to a Hash of (name => value) pairs
#
# assert_equal 'AuthorOfNewPage', r.cookies['author']
# assert_equal 'AuthorOfNewPage', r.cookies['author']
def cookies
cookies = {}
if header = get_header(SET_COOKIE)
@ -456,11 +457,10 @@ def before_committed
end
def before_sending
# Normally we've already committed by now, but it's possible
# (e.g., if the controller action tries to read back its own
# response) to get here before that. In that case, we must force
# an "early" commit: we're about to freeze the headers, so this is
# our last chance.
# Normally we've already committed by now, but it's possible (e.g., if the
# controller action tries to read back its own response) to get here before
# that. In that case, we must force an "early" commit: we're about to freeze the
# headers, so this is our last chance.
commit! unless committed?
@request.commit_cookie_jar! unless committed?
@ -488,8 +488,8 @@ def initialize(response)
end
def close
# Rack "close" maps to Response#abort, and *not* Response#close
# (which is used when the controller's finished writing)
# Rack "close" maps to Response#abort, and **not** Response#close (which is used
# when the controller's finished writing)
@response.abort
end

@ -1,17 +1,19 @@
# frozen_string_literal: true
# :markup: markdown
module ActionDispatch
module Http
# = Action Dispatch HTTP \UploadedFile
# # Action Dispatch HTTP UploadedFile
#
# Models uploaded files.
#
# The actual file is accessible via the +tempfile+ accessor, though some
# of its interface is available directly for convenience.
# The actual file is accessible via the `tempfile` accessor, though some of its
# interface is available directly for convenience.
#
# Uploaded files are temporary files whose lifespan is one request. When
# the object is finalized Ruby unlinks the file, so there is no need to
# clean them with a separate maintenance task.
# Uploaded files are temporary files whose lifespan is one request. When the
# object is finalized Ruby unlinks the file, so there is no need to clean them
# with a separate maintenance task.
class UploadedFile
# The basename of the file in the client.
attr_accessor :original_filename
@ -19,8 +21,8 @@ class UploadedFile
# A string with the MIME type of the file.
attr_accessor :content_type
# A +Tempfile+ object with the actual uploaded file. Note that some of
# its interface is available directly.
# A `Tempfile` object with the actual uploaded file. Note that some of its
# interface is available directly.
attr_accessor :tempfile
# A string with the headers of the multipart request.
@ -57,42 +59,42 @@ def initialize(hash) # :nodoc:
end
end
# Shortcut for +tempfile.read+.
# Shortcut for `tempfile.read`.
def read(length = nil, buffer = nil)
@tempfile.read(length, buffer)
end
# Shortcut for +tempfile.open+.
# Shortcut for `tempfile.open`.
def open
@tempfile.open
end
# Shortcut for +tempfile.close+.
# Shortcut for `tempfile.close`.
def close(unlink_now = false)
@tempfile.close(unlink_now)
end
# Shortcut for +tempfile.path+.
# Shortcut for `tempfile.path`.
def path
@tempfile.path
end
# Shortcut for +tempfile.to_path+.
# Shortcut for `tempfile.to_path`.
def to_path
@tempfile.to_path
end
# Shortcut for +tempfile.rewind+.
# Shortcut for `tempfile.rewind`.
def rewind
@tempfile.rewind
end
# Shortcut for +tempfile.size+.
# Shortcut for `tempfile.size`.
def size
@tempfile.size
end
# Shortcut for +tempfile.eof?+.
# Shortcut for `tempfile.eof?`.
def eof?
@tempfile.eof?
end

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "active_support/core_ext/module/attribute_accessors"
module ActionDispatch
@ -15,20 +17,20 @@ module URL
class << self
# Returns the domain part of a host given the domain level.
#
# # Top-level domain example
# extract_domain('www.example.com', 1) # => "example.com"
# # Second-level domain example
# extract_domain('dev.www.example.co.uk', 2) # => "example.co.uk"
# # Top-level domain example
# extract_domain('www.example.com', 1) # => "example.com"
# # Second-level domain example
# extract_domain('dev.www.example.co.uk', 2) # => "example.co.uk"
def extract_domain(host, tld_length)
extract_domain_from(host, tld_length) if named_host?(host)
end
# Returns the subdomains of a host as an Array given the domain level.
#
# # Top-level domain example
# extract_subdomains('www.example.com', 1) # => ["www"]
# # Second-level domain example
# extract_subdomains('dev.www.example.co.uk', 2) # => ["dev", "www"]
# # Top-level domain example
# extract_subdomains('www.example.com', 1) # => ["www"]
# # Second-level domain example
# extract_subdomains('dev.www.example.co.uk', 2) # => ["dev", "www"]
def extract_subdomains(host, tld_length)
if named_host?(host)
extract_subdomains_from(host, tld_length)
@ -39,10 +41,10 @@ def extract_subdomains(host, tld_length)
# Returns the subdomains of a host as a String given the domain level.
#
# # Top-level domain example
# extract_subdomain('www.example.com', 1) # => "www"
# # Second-level domain example
# extract_subdomain('dev.www.example.co.uk', 2) # => "dev.www"
# # Top-level domain example
# extract_subdomain('www.example.com', 1) # => "www"
# # Second-level domain example
# extract_subdomain('dev.www.example.co.uk', 2) # => "dev.www"
def extract_subdomain(host, tld_length)
extract_subdomains(host, tld_length).join(".")
end
@ -184,33 +186,33 @@ def initialize
# Returns the complete URL used for this request.
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
# req.url # => "http://example.com"
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
# req.url # => "http://example.com"
def url
protocol + host_with_port + fullpath
end
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
# req.protocol # => "http://"
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
# req.protocol # => "http://"
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on'
# req.protocol # => "https://"
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on'
# req.protocol # => "https://"
def protocol
@protocol ||= ssl? ? "https://" : "http://"
end
# Returns the \host and port for this request, such as "example.com:8080".
# Returns the host and port for this request, such as "example.com:8080".
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
# req.raw_host_with_port # => "example.com"
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
# req.raw_host_with_port # => "example.com"
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
# req.raw_host_with_port # => "example.com:80"
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
# req.raw_host_with_port # => "example.com:80"
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.raw_host_with_port # => "example.com:8080"
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.raw_host_with_port # => "example.com:8080"
def raw_host_with_port
if forwarded = x_forwarded_host.presence
forwarded.split(/,\s?/).last
@ -221,35 +223,35 @@ def raw_host_with_port
# Returns the host for this request, such as "example.com".
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.host # => "example.com"
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.host # => "example.com"
def host
raw_host_with_port.sub(/:\d+$/, "")
end
# Returns a \host:\port string for this request, such as "example.com" or
# "example.com:8080". Port is only included if it is not a default port
# (80 or 443)
# Returns a host:port string for this request, such as "example.com" or
# "example.com:8080". Port is only included if it is not a default port (80 or
# 443)
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
# req.host_with_port # => "example.com"
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
# req.host_with_port # => "example.com"
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
# req.host_with_port # => "example.com"
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
# req.host_with_port # => "example.com"
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.host_with_port # => "example.com:8080"
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.host_with_port # => "example.com:8080"
def host_with_port
"#{host}#{port_string}"
end
# Returns the port number of this request as an integer.
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
# req.port # => 80
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
# req.port # => 80
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.port # => 8080
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.port # => 8080
def port
@port ||= if raw_host_with_port =~ /:(\d+)$/
$1.to_i
@ -258,10 +260,10 @@ def port
end
end
# Returns the standard \port number for this request's protocol.
# Returns the standard port number for this request's protocol.
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.standard_port # => 80
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.standard_port # => 80
def standard_port
if "https://" == protocol
443
@ -272,68 +274,68 @@ def standard_port
# Returns whether this request is using the standard port
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
# req.standard_port? # => true
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
# req.standard_port? # => true
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.standard_port? # => false
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.standard_port? # => false
def standard_port?
port == standard_port
end
# Returns a number \port suffix like 8080 if the \port number of this request
# is not the default HTTP \port 80 or HTTPS \port 443.
# Returns a number port suffix like 8080 if the port number of this request is
# not the default HTTP port 80 or HTTPS port 443.
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
# req.optional_port # => nil
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
# req.optional_port # => nil
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.optional_port # => 8080
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.optional_port # => 8080
def optional_port
standard_port? ? nil : port
end
# Returns a string \port suffix, including colon, like ":8080" if the \port
# number of this request is not the default HTTP \port 80 or HTTPS \port 443.
# Returns a string port suffix, including colon, like ":8080" if the port number
# of this request is not the default HTTP port 80 or HTTPS port 443.
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
# req.port_string # => ""
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
# req.port_string # => ""
#
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.port_string # => ":8080"
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.port_string # => ":8080"
def port_string
standard_port? ? "" : ":#{port}"
end
# Returns the requested port, such as 8080, based on SERVER_PORT
#
# req = ActionDispatch::Request.new 'SERVER_PORT' => '80'
# req.server_port # => 80
# req = ActionDispatch::Request.new 'SERVER_PORT' => '80'
# req.server_port # => 80
#
# req = ActionDispatch::Request.new 'SERVER_PORT' => '8080'
# req.server_port # => 8080
# req = ActionDispatch::Request.new 'SERVER_PORT' => '8080'
# req.server_port # => 8080
def server_port
get_header("SERVER_PORT").to_i
end
# Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
# a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
# Returns the domain part of a host, such as "rubyonrails.org" in
# "www.rubyonrails.org". You can specify a different `tld_length`, such as 2 to
# catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
def domain(tld_length = @@tld_length)
ActionDispatch::Http::URL.extract_domain(host, tld_length)
end
# Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
# returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
# such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
# in "www.rubyonrails.co.uk".
# Returns all the subdomains as an array, so `["dev", "www"]` would be returned
# for "dev.www.rubyonrails.org". You can specify a different `tld_length`, such
# as 2 to catch `["www"]` instead of `["www", "rubyonrails"]` in
# "www.rubyonrails.co.uk".
def subdomains(tld_length = @@tld_length)
ActionDispatch::Http::URL.extract_subdomains(host, tld_length)
end
# Returns all the \subdomains as a string, so <tt>"dev.www"</tt> would be
# returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
# such as 2 to catch <tt>"www"</tt> instead of <tt>"www.rubyonrails"</tt>
# in "www.rubyonrails.co.uk".
# Returns all the subdomains as a string, so `"dev.www"` would be returned for
# "dev.www.rubyonrails.org". You can specify a different `tld_length`, such as 2
# to catch `"www"` instead of `"www.rubyonrails"` in "www.rubyonrails.co.uk".
def subdomain(tld_length = @@tld_length)
ActionDispatch::Http::URL.extract_subdomain(host, tld_length)
end

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "action_dispatch/journey/router"
require "action_dispatch/journey/gtg/builder"
require "action_dispatch/journey/gtg/simulator"

@ -1,12 +1,14 @@
# frozen_string_literal: true
# :markup: markdown
require "action_controller/metal/exceptions"
module ActionDispatch
# :stopdoc:
module Journey
# The Formatter class is used for formatting URLs. For example, parameters
# passed to +url_for+ in Rails will eventually call Formatter#generate.
# passed to `url_for` in Rails will eventually call Formatter#generate.
class Formatter
attr_reader :routes
@ -66,16 +68,16 @@ def generate(name, options, path_parameters)
match_route(name, constraints) do |route|
parameterized_parts = extract_parameterized_parts(route, options, path_parameters)
# Skip this route unless a name has been provided or it is a
# standard Rails route since we can't determine whether an options
# hash passed to url_for matches a Rack application or a redirect.
# Skip this route unless a name has been provided or it is a standard Rails
# route since we can't determine whether an options hash passed to url_for
# matches a Rack application or a redirect.
next unless name || route.dispatcher?
missing_keys = missing_keys(route, parameterized_parts)
next if missing_keys && !missing_keys.empty?
params = options.delete_if do |key, _|
# top-level params' normal behavior of generating query_params
# should be preserved even if the same key is also a bind_param
# top-level params' normal behavior of generating query_params should be
# preserved even if the same key is also a bind_param
parameterized_parts.key?(key) || route.defaults.key?(key) ||
(path_params.key?(key) && !original_options.key?(key))
end

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "action_dispatch/journey/gtg/transition_table"
module ActionDispatch
@ -66,9 +68,8 @@ def nullable?(node)
when Nodes::Group
true
when Nodes::Star
# the default star regex is /(.+)/ which is NOT nullable
# but since different constraints can be provided we must
# actually check if this is the case or not.
# the default star regex is /(.+)/ which is NOT nullable but since different
# constraints can be provided we must actually check if this is the case or not.
node.regexp.match?("")
when Nodes::Or
node.children.any? { |c| nullable?(c) }

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "strscan"
module ActionDispatch

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "action_dispatch/journey/nfa/dot"
module ActionDispatch
@ -55,8 +57,8 @@ def move(t, full_string, start_index, end_index)
t.each { |s, previous_start|
if previous_start.nil?
# In the simple case of a "default" param regex do this fast-path
# and add all next states.
# In the simple case of a "default" param regex do this fast-path and add all
# next states.
if token_matches_default_component && states = @stdparam_states[s]
states.each { |re, v| next_states << [v, nil].freeze if !v.nil? }
end
@ -67,10 +69,10 @@ def move(t, full_string, start_index, end_index)
end
end
# For regexes that aren't the "default" style, they may potentially
# not be terminated by the first "token" [./?], so we need to continue
# to attempt to match this regexp as well as any successful paths that
# continue out of it. both paths could be valid.
# For regexes that aren't the "default" style, they may potentially not be
# terminated by the first "token" [./?], so we need to continue to attempt to
# match this regexp as well as any successful paths that continue out of it.
# both paths could be valid.
if states = @regexp_states[s]
slice_start = if previous_start.nil?
start_index
@ -86,8 +88,8 @@ def move(t, full_string, start_index, end_index)
next_states << [v, nil].freeze if !v.nil? && re.match?(curr_slice)
}
# and regardless, we must continue accepting tokens and retrying this regexp.
# we need to remember where we started as well so we can take bigger slices.
# and regardless, we must continue accepting tokens and retrying this regexp. we
# need to remember where we started as well so we can take bigger slices.
next_states << [s, slice_start].freeze
end
}

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionDispatch
module Journey # :nodoc:
module NFA # :nodoc:

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "action_dispatch/journey/visitors"
module ActionDispatch
@ -21,9 +23,8 @@ def initialize(tree, formatted)
end
def requirements=(requirements)
# inject any regexp requirements for `star` nodes so they can be
# determined nullable, which requires knowing if the regex accepts an
# empty string.
# inject any regexp requirements for `star` nodes so they can be determined
# nullable, which requires knowing if the regex accepts an empty string.
(symbols + stars).each do |node|
re = requirements[node.to_sym]
node.regexp = re if re
@ -51,8 +52,8 @@ def visit_tree(formatted)
stars << node
if formatted != false
# Add a constraint for wildcard route to make it non-greedy and
# match the optional format part of the route by default.
# Add a constraint for wildcard route to make it non-greedy and match the
# optional format part of the route by default.
wildcard_options[node.name.to_sym] ||= /.+?/m
end
end

@ -1,8 +1,9 @@
#
# DO NOT MODIFY!!!!
# This file is automatically generated by Racc 1.4.16
# from Racc grammar file "".
#
# This file is automatically generated by Racc 1.4.16 from
# Racc grammar file "".
# :markup: markdown
require 'racc/parser.rb'

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "action_dispatch/journey/scanner"
require "action_dispatch/journey/nodes/node"

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionDispatch
module Journey # :nodoc:
module Path # :nodoc:
@ -32,7 +34,8 @@ def eager_load!
end
def requirements_anchored?
# each required param must not be surrounded by a literal, otherwise it isn't simple to chunk-match the url piecemeal
# each required param must not be surrounded by a literal, otherwise it isn't
# simple to chunk-match the url piecemeal
terminals = ast.terminals
terminals.each_with_index { |s, index|

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionDispatch
# :stopdoc:
module Journey
@ -52,7 +54,7 @@ def self.verb_matcher(verb)
##
# +path+ is a path constraint.
# +constraints+ is a hash of constraints to be applied to this route.
# `constraints` is a hash of constraints to be applied to this route.
def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, request_method_match: nil, precedence: 0, scope_options: {}, internal: false, source_location: nil)
@name = name
@app = app
@ -82,14 +84,14 @@ def eager_load!
nil
end
# Needed for `bin/rails routes`. Picks up succinctly defined requirements
# for a route, for example route
# Needed for `bin/rails routes`. Picks up succinctly defined requirements for a
# route, for example route
#
# get 'photo/:id', :controller => 'photos', :action => 'show',
# :id => /[A-Z]\d{5}/
# get 'photo/:id', :controller => 'photos', :action => 'show',
# :id => /[A-Z]\d{5}/
#
# will have {:controller=>"photos", :action=>"show", :id=>/[A-Z]\d{5}/}
# as requirements.
# will have {:controller=>"photos", :action=>"show", :[id=>/](A-Z){5}/} as
# requirements.
def requirements
@defaults.merge(path.requirements).delete_if { |_, v|
/.+?/m == v

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "action_dispatch/journey/router/utils"
require "action_dispatch/journey/routes"
require "action_dispatch/journey/formatter"
@ -22,8 +24,8 @@ def initialize(routes)
end
def eager_load!
# Eagerly trigger the simulator's initialization so
# it doesn't happen during a request cycle.
# Eagerly trigger the simulator's initialization so it doesn't happen during a
# request cycle.
simulator
nil
end

@ -1,19 +1,21 @@
# frozen_string_literal: true
# :markup: markdown
module ActionDispatch
module Journey # :nodoc:
class Router # :nodoc:
class Utils # :nodoc:
# Normalizes URI path.
#
# Strips off trailing slash and ensures there is a leading slash.
# Also converts downcase URL encoded string to uppercase.
# Strips off trailing slash and ensures there is a leading slash. Also converts
# downcase URL encoded string to uppercase.
#
# normalize_path("/foo") # => "/foo"
# normalize_path("/foo/") # => "/foo"
# normalize_path("foo") # => "/foo"
# normalize_path("") # => "/"
# normalize_path("/%ab") # => "/%AB"
# normalize_path("/foo") # => "/foo"
# normalize_path("/foo/") # => "/foo"
# normalize_path("foo") # => "/foo"
# normalize_path("") # => "/"
# normalize_path("/%ab") # => "/%AB"
def self.normalize_path(path)
path ||= ""
encoding = path.encoding
@ -28,8 +30,7 @@ def self.normalize_path(path)
path.force_encoding(encoding)
end
# URI path and fragment escaping
# https://tools.ietf.org/html/rfc3986
# URI path and fragment escaping https://tools.ietf.org/html/rfc3986
class UriEncoder # :nodoc:
ENCODE = "%%%02X"
US_ASCII = Encoding::US_ASCII
@ -93,8 +94,8 @@ def self.escape_fragment(fragment)
# Replaces any escaped sequences with their unescaped representations.
#
# uri = "/topics?title=Ruby%20on%20Rails"
# unescape_uri(uri) #=> "/topics?title=Ruby on Rails"
# uri = "/topics?title=Ruby%20on%20Rails"
# unescape_uri(uri) #=> "/topics?title=Ruby on Rails"
def self.unescape_uri(uri)
ENCODER.unescape_uri(uri)
end

@ -1,9 +1,11 @@
# frozen_string_literal: true
# :markup: markdown
module ActionDispatch
module Journey # :nodoc:
# The Routing table. Contains all routes for a system. Routes can be
# added to the table by calling Routes#add_route.
# The Routing table. Contains all routes for a system. Routes can be added to
# the table by calling Routes#add_route.
class Routes # :nodoc:
include Enumerable

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "strscan"
module ActionDispatch
@ -33,8 +35,8 @@ def next_token
end
private
# takes advantage of String @- deduping capabilities in Ruby 2.5 upwards
# see: https://bugs.ruby-lang.org/issues/13077
# takes advantage of String @- deduping capabilities in Ruby 2.5 upwards see:
# https://bugs.ruby-lang.org/issues/13077
def dedup_scan(regex)
r = @ss.scan(regex)
r ? -r : nil

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionDispatch
# :stopdoc:
module Journey

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
module ActionDispatch
class LogSubscriber < ActiveSupport::LogSubscriber
def redirect(event)

@ -1,5 +1,7 @@
# frozen_string_literal: true
# :markup: markdown
require "uri"
require "active_support/actionable_error"

@ -1,12 +1,15 @@
# frozen_string_literal: true
# :markup: markdown
module ActionDispatch
# = Action Dispatch \AssumeSSL
# # Action Dispatch AssumeSSL
#
# When proxying through a load balancer that terminates SSL, the forwarded request will appear
# as though it's HTTP instead of HTTPS to the application. This makes redirects and cookie
# security target HTTP instead of HTTPS. This middleware makes the server assume that the
# proxy already terminated SSL, and that the request really is HTTPS.
# When proxying through a load balancer that terminates SSL, the forwarded
# request will appear as though it's HTTP instead of HTTPS to the application.
# This makes redirects and cookie security target HTTP instead of HTTPS. This
# middleware makes the server assume that the proxy already terminated SSL, and
# that the request really is HTTPS.
class AssumeSSL
def initialize(app)
@app = app

@ -1,7 +1,9 @@
# frozen_string_literal: true
# :markup: markdown
module ActionDispatch
# = Action Dispatch \Callbacks
# # Action Dispatch Callbacks
#
# Provides callbacks to be executed before and after dispatching the request.
class Callbacks

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