/text/src/test/resources/examples/ruby/rails4.in.rb
Ruby | 15296 lines | 9112 code | 1840 blank | 4344 comment | 628 complexity | b0245bf2875f2473b31162a1cc38de04 MD5 | raw file
Possible License(s): EPL-1.0, MPL-2.0-no-copyleft-exception
- require 'mail'
- require 'action_mailer/collector'
- require 'active_support/core_ext/string/inflections'
- require 'active_support/core_ext/hash/except'
- require 'active_support/core_ext/module/anonymous'
- require 'action_mailer/log_subscriber'
- module ActionMailer
- # Action Mailer allows you to send email from your application using a mailer model and views.
- #
- # = Mailer Models
- #
- # To use Action Mailer, you need to create a mailer model.
- #
- # $ rails generate mailer Notifier
- #
- # The generated model inherits from <tt>ActionMailer::Base</tt>. A mailer model defines methods
- # used to generate an email message. In these methods, you can setup variables to be used in
- # the mailer views, options on the mail itself such as the <tt>:from</tt> address, and attachments.
- #
- # class Notifier < ActionMailer::Base
- # default from: 'no-reply@example.com',
- # return_path: 'system@example.com'
- #
- # def welcome(recipient)
- # @account = recipient
- # mail(to: recipient.email_address_with_name,
- # bcc: ["bcc@example.com", "Order Watcher <watcher@example.com>"])
- # end
- # end
- #
- # Within the mailer method, you have access to the following methods:
- #
- # * <tt>attachments[]=</tt> - Allows you to add attachments to your email in an intuitive
- # manner; <tt>attachments['filename.png'] = File.read('path/to/filename.png')</tt>
- #
- # * <tt>attachments.inline[]=</tt> - Allows you to add an inline attachment to your email
- # in the same manner as <tt>attachments[]=</tt>
- #
- # * <tt>headers[]=</tt> - Allows you to specify any header field in your email such
- # as <tt>headers['X-No-Spam'] = 'True'</tt>. Note, while most fields like <tt>To:</tt>
- # <tt>From:</tt> can only appear once in an email header, other fields like <tt>X-Anything</tt>
- # can appear multiple times. If you want to change a field that can appear multiple times,
- # you need to set it to nil first so that Mail knows you are replacing it and not adding
- # another field of the same name.
- #
- # * <tt>headers(hash)</tt> - Allows you to specify multiple headers in your email such
- # as <tt>headers({'X-No-Spam' => 'True', 'In-Reply-To' => '1234@message.id'})</tt>
- #
- # * <tt>mail</tt> - Allows you to specify email to be sent.
- #
- # The hash passed to the mail method allows you to specify any header that a Mail::Message
- # will accept (any valid Email header including optional fields).
- #
- # The mail method, if not passed a block, will inspect your views and send all the views with
- # the same name as the method, so the above action would send the +welcome.text.erb+ view
- # file as well as the +welcome.text.html.erb+ view file in a +multipart/alternative+ email.
- #
- # If you want to explicitly render only certain templates, pass a block:
- #
- # mail(to: user.email) do |format|
- # format.text
- # format.html
- # end
- #
- # The block syntax is also useful in providing information specific to a part:
- #
- # mail(to: user.email) do |format|
- # format.text(content_transfer_encoding: "base64")
- # format.html
- # end
- #
- # Or even to render a special view:
- #
- # mail(to: user.email) do |format|
- # format.text
- # format.html { render "some_other_template" }
- # end
- #
- # = Mailer views
- #
- # Like Action Controller, each mailer class has a corresponding view directory in which each
- # method of the class looks for a template with its name.
- #
- # To define a template to be used with a mailing, create an <tt>.erb</tt> file with the same
- # name as the method in your mailer model. For example, in the mailer defined above, the template at
- # <tt>app/views/notifier/welcome.text.erb</tt> would be used to generate the email.
- #
- # Variables defined in the model are accessible as instance variables in the view.
- #
- # Emails by default are sent in plain text, so a sample view for our model example might look like this:
- #
- # Hi <%= @account.name %>,
- # Thanks for joining our service! Please check back often.
- #
- # You can even use Action Pack helpers in these views. For example:
- #
- # You got a new note!
- # <%= truncate(@note.body, length: 25) %>
- #
- # If you need to access the subject, from or the recipients in the view, you can do that through message object:
- #
- # You got a new note from <%= message.from %>!
- # <%= truncate(@note.body, length: 25) %>
- #
- #
- # = Generating URLs
- #
- # URLs can be generated in mailer views using <tt>url_for</tt> or named routes. Unlike controllers from
- # Action Pack, the mailer instance doesn't have any context about the incoming request, so you'll need
- # to provide all of the details needed to generate a URL.
- #
- # When using <tt>url_for</tt> you'll need to provide the <tt>:host</tt>, <tt>:controller</tt>, and <tt>:action</tt>:
- #
- # <%= url_for(host: "example.com", controller: "welcome", action: "greeting") %>
- #
- # When using named routes you only need to supply the <tt>:host</tt>:
- #
- # <%= users_url(host: "example.com") %>
- #
- # You should use the <tt>named_route_url</tt> style (which generates absolute URLs) and avoid using the
- # <tt>named_route_path</tt> style (which generates relative URLs), since clients reading the mail will
- # have no concept of a current URL from which to determine a relative path.
- #
- # It is also possible to set a default host that will be used in all mailers by setting the <tt>:host</tt>
- # option as a configuration option in <tt>config/application.rb</tt>:
- #
- # config.action_mailer.default_url_options = { host: "example.com" }
- #
- # When you decide to set a default <tt>:host</tt> for your mailers, then you need to make sure to use the
- # <tt>only_path: false</tt> option when using <tt>url_for</tt>. Since the <tt>url_for</tt> view helper
- # will generate relative URLs by default when a <tt>:host</tt> option isn't explicitly provided, passing
- # <tt>only_path: false</tt> will ensure that absolute URLs are generated.
- #
- # = Sending mail
- #
- # Once a mailer action and template are defined, you can deliver your message or create it and save it
- # for delivery later:
- #
- # Notifier.welcome(david).deliver # sends the email
- # mail = Notifier.welcome(david) # => a Mail::Message object
- # mail.deliver # sends the email
- #
- # You never instantiate your mailer class. Rather, you just call the method you defined on the class itself.
- #
- # = Multipart Emails
- #
- # Multipart messages can also be used implicitly because Action Mailer will automatically detect and use
- # multipart templates, where each template is named after the name of the action, followed by the content
- # type. Each such detected template will be added as a separate part to the message.
- #
- # For example, if the following templates exist:
- # * signup_notification.text.erb
- # * signup_notification.text.html.erb
- # * signup_notification.text.xml.builder
- # * signup_notification.text.yaml.erb
- #
- # Each would be rendered and added as a separate part to the message, with the corresponding content
- # type. The content type for the entire message is automatically set to <tt>multipart/alternative</tt>,
- # which indicates that the email contains multiple different representations of the same email
- # body. The same instance variables defined in the action are passed to all email templates.
- #
- # Implicit template rendering is not performed if any attachments or parts have been added to the email.
- # This means that you'll have to manually add each part to the email and set the content type of the email
- # to <tt>multipart/alternative</tt>.
- #
- # = Attachments
- #
- # Sending attachment in emails is easy:
- #
- # class ApplicationMailer < ActionMailer::Base
- # def welcome(recipient)
- # attachments['free_book.pdf'] = File.read('path/to/file.pdf')
- # mail(to: recipient, subject: "New account information")
- # end
- # end
- #
- # Which will (if it had both a <tt>welcome.text.erb</tt> and <tt>welcome.text.html.erb</tt>
- # template in the view directory), send a complete <tt>multipart/mixed</tt> email with two parts,
- # the first part being a <tt>multipart/alternative</tt> with the text and HTML email parts inside,
- # and the second being a <tt>application/pdf</tt> with a Base64 encoded copy of the file.pdf book
- # with the filename +free_book.pdf+.
- #
- # If you need to send attachments with no content, you need to create an empty view for it,
- # or add an empty body parameter like this:
- #
- # class ApplicationMailer < ActionMailer::Base
- # def welcome(recipient)
- # attachments['free_book.pdf'] = File.read('path/to/file.pdf')
- # mail(to: recipient, subject: "New account information", body: "")
- # end
- # end
- #
- # = Inline Attachments
- #
- # You can also specify that a file should be displayed inline with other HTML. This is useful
- # if you want to display a corporate logo or a photo.
- #
- # class ApplicationMailer < ActionMailer::Base
- # def welcome(recipient)
- # attachments.inline['photo.png'] = File.read('path/to/photo.png')
- # mail(to: recipient, subject: "Here is what we look like")
- # end
- # end
- #
- # And then to reference the image in the view, you create a <tt>welcome.html.erb</tt> file and
- # make a call to +image_tag+ passing in the attachment you want to display and then call
- # +url+ on the attachment to get the relative content id path for the image source:
- #
- # <h1>Please Don't Cringe</h1>
- #
- # <%= image_tag attachments['photo.png'].url -%>
- #
- # As we are using Action View's +image_tag+ method, you can pass in any other options you want:
- #
- # <h1>Please Don't Cringe</h1>
- #
- # <%= image_tag attachments['photo.png'].url, alt: 'Our Photo', class: 'photo' -%>
- #
- # = Observing and Intercepting Mails
- #
- # Action Mailer provides hooks into the Mail observer and interceptor methods. These allow you to
- # register classes that are called during the mail delivery life cycle.
- #
- # An observer class must implement the <tt>:delivered_email(message)</tt> method which will be
- # called once for every email sent after the email has been sent.
- #
- # An interceptor class must implement the <tt>:delivering_email(message)</tt> method which will be
- # called before the email is sent, allowing you to make modifications to the email before it hits
- # the delivery agents. Your class should make any needed modifications directly to the passed
- # in Mail::Message instance.
- #
- # = Default Hash
- #
- # Action Mailer provides some intelligent defaults for your emails, these are usually specified in a
- # default method inside the class definition:
- #
- # class Notifier < ActionMailer::Base
- # default sender: 'system@example.com'
- # end
- #
- # You can pass in any header value that a <tt>Mail::Message</tt> accepts. Out of the box,
- # <tt>ActionMailer::Base</tt> sets the following:
- #
- # * <tt>mime_version: "1.0"</tt>
- # * <tt>charset: "UTF-8",</tt>
- # * <tt>content_type: "text/plain",</tt>
- # * <tt>parts_order: [ "text/plain", "text/enriched", "text/html" ]</tt>
- #
- # <tt>parts_order</tt> and <tt>charset</tt> are not actually valid <tt>Mail::Message</tt> header fields,
- # but Action Mailer translates them appropriately and sets the correct values.
- #
- # As you can pass in any header, you need to either quote the header as a string, or pass it in as
- # an underscored symbol, so the following will work:
- #
- # class Notifier < ActionMailer::Base
- # default 'Content-Transfer-Encoding' => '7bit',
- # content_description: 'This is a description'
- # end
- #
- # Finally, Action Mailer also supports passing <tt>Proc</tt> objects into the default hash, so you
- # can define methods that evaluate as the message is being generated:
- #
- # class Notifier < ActionMailer::Base
- # default 'X-Special-Header' => Proc.new { my_method }
- #
- # private
- #
- # def my_method
- # 'some complex call'
- # end
- # end
- #
- # Note that the proc is evaluated right at the start of the mail message generation, so if you
- # set something in the defaults using a proc, and then set the same thing inside of your
- # mailer method, it will get over written by the mailer method.
- #
- # It is also possible to set these default options that will be used in all mailers through
- # the <tt>default_options=</tt> configuration in <tt>config/application.rb</tt>:
- #
- # config.action_mailer.default_options = { from: "no-reply@example.org" }
- #
- # = Callbacks
- #
- # You can specify callbacks using before_action and after_action for configuring your messages.
- # This may be useful, for example, when you want to add default inline attachments for all
- # messages sent out by a certain mailer class:
- #
- # class Notifier < ActionMailer::Base
- # before_action :add_inline_attachment!
- #
- # def welcome
- # mail
- # end
- #
- # private
- #
- # def add_inline_attachment!
- # attachments.inline["footer.jpg"] = File.read('/path/to/filename.jpg')
- # end
- # end
- #
- # Callbacks in ActionMailer are implemented using AbstractController::Callbacks, so you
- # can define and configure callbacks in the same manner that you would use callbacks in
- # classes that inherit from ActionController::Base.
- #
- # Note that unless you have a specific reason to do so, you should prefer using before_action
- # rather than after_action in your ActionMailer classes so that headers are parsed properly.
- #
- # = Configuration options
- #
- # These options are specified on the class level, like
- # <tt>ActionMailer::Base.raise_delivery_errors = true</tt>
- #
- # * <tt>default_options</tt> - You can pass this in at a class level as well as within the class itself as
- # per the above section.
- #
- # * <tt>logger</tt> - the logger is used for generating information on the mailing run if available.
- # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
- #
- # * <tt>smtp_settings</tt> - Allows detailed configuration for <tt>:smtp</tt> delivery method:
- # * <tt>:address</tt> - Allows you to use a remote mail server. Just change it from its default
- # "localhost" setting.
- # * <tt>:port</tt> - On the off chance that your mail server doesn't run on port 25, you can change it.
- # * <tt>:domain</tt> - If you need to specify a HELO domain, you can do it here.
- # * <tt>:user_name</tt> - If your mail server requires authentication, set the username in this setting.
- # * <tt>:password</tt> - If your mail server requires authentication, set the password in this setting.
- # * <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the
- # authentication type here.
- # This is a symbol and one of <tt>:plain</tt> (will send the password in the clear), <tt>:login</tt> (will
- # send password Base64 encoded) or <tt>:cram_md5</tt> (combines a Challenge/Response mechanism to exchange
- # information and a cryptographic Message Digest 5 algorithm to hash important information)
- # * <tt>:enable_starttls_auto</tt> - When set to true, detects if STARTTLS is enabled in your SMTP server
- # and starts to use it.
- # * <tt>:openssl_verify_mode</tt> - When using TLS, you can set how OpenSSL checks the certificate. This is
- # really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name
- # of an OpenSSL verify constant ('none', 'peer', 'client_once','fail_if_no_peer_cert') or directly the
- # constant (OpenSSL::SSL::VERIFY_NONE, OpenSSL::SSL::VERIFY_PEER,...).
- #
- # * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
- # * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>.
- # * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i -t</tt> with <tt>-f sender@address</tt>
- # added automatically before the message is sent.
- #
- # * <tt>file_settings</tt> - Allows you to override options for the <tt>:file</tt> delivery method.
- # * <tt>:location</tt> - The directory into which emails will be written. Defaults to the application
- # <tt>tmp/mails</tt>.
- #
- # * <tt>raise_delivery_errors</tt> - Whether or not errors should be raised if the email fails to be delivered.
- #
- # * <tt>delivery_method</tt> - Defines a delivery method. Possible values are <tt>:smtp</tt> (default),
- # <tt>:sendmail</tt>, <tt>:test</tt>, and <tt>:file</tt>. Or you may provide a custom delivery method
- # object e.g. MyOwnDeliveryMethodClass. See the Mail gem documentation on the interface you need to
- # implement for a custom delivery agent.
- #
- # * <tt>perform_deliveries</tt> - Determines whether emails are actually sent from Action Mailer when you
- # call <tt>.deliver</tt> on an mail message or on an Action Mailer method. This is on by default but can
- # be turned off to aid in functional testing.
- #
- # * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with
- # <tt>delivery_method :test</tt>. Most useful for unit and functional testing.
- class Base < AbstractController::Base
- include DeliveryMethods
- abstract!
- include AbstractController::Logger
- include AbstractController::Rendering
- include AbstractController::Layouts
- include AbstractController::Helpers
- include AbstractController::Translation
- include AbstractController::AssetPaths
- include AbstractController::Callbacks
- self.protected_instance_variables = [:@_action_has_layout]
- helper ActionMailer::MailHelper
- private_class_method :new #:nodoc:
- class_attribute :default_params
- self.default_params = {
- mime_version: "1.0",
- charset: "UTF-8",
- content_type: "text/plain",
- parts_order: [ "text/plain", "text/enriched", "text/html" ]
- }.freeze
- class << self
- # Register one or more Observers which will be notified when mail is delivered.
- def register_observers(*observers)
- observers.flatten.compact.each { |observer| register_observer(observer) }
- end
- # Register one or more Interceptors which will be called before mail is sent.
- def register_interceptors(*interceptors)
- interceptors.flatten.compact.each { |interceptor| register_interceptor(interceptor) }
- end
- # Register an Observer which will be notified when mail is delivered.
- # Either a class or a string can be passed in as the Observer. If a string is passed in
- # it will be <tt>constantize</tt>d.
- def register_observer(observer)
- delivery_observer = (observer.is_a?(String) ? observer.constantize : observer)
- Mail.register_observer(delivery_observer)
- end
- # Register an Interceptor which will be called before mail is sent.
- # Either a class or a string can be passed in as the Interceptor. If a string is passed in
- # it will be <tt>constantize</tt>d.
- def register_interceptor(interceptor)
- delivery_interceptor = (interceptor.is_a?(String) ? interceptor.constantize : interceptor)
- Mail.register_interceptor(delivery_interceptor)
- end
- def mailer_name
- @mailer_name ||= anonymous? ? "anonymous" : name.underscore
- end
- attr_writer :mailer_name
- alias :controller_path :mailer_name
- def default(value = nil)
- self.default_params = default_params.merge(value).freeze if value
- default_params
- end
- # Allows to set defaults through app configuration:
- #
- # config.action_mailer.default_options = { from: "no-reply@example.org" }
- alias :default_options= :default
- # Receives a raw email, parses it into an email object, decodes it,
- # instantiates a new mailer, and passes the email object to the mailer
- # object's +receive+ method. If you want your mailer to be able to
- # process incoming messages, you'll need to implement a +receive+
- # method that accepts the raw email string as a parameter:
- #
- # class MyMailer < ActionMailer::Base
- # def receive(mail)
- # ...
- # end
- # end
- def receive(raw_mail)
- ActiveSupport::Notifications.instrument("receive.action_mailer") do |payload|
- mail = Mail.new(raw_mail)
- set_payload_for_mail(payload, mail)
- new.receive(mail)
- end
- end
- # Wraps an email delivery inside of Active Support Notifications instrumentation. This
- # method is actually called by the <tt>Mail::Message</tt> object itself through a callback
- # when you call <tt>:deliver</tt> on the Mail::Message, calling +deliver_mail+ directly
- # and passing a Mail::Message will do nothing except tell the logger you sent the email.
- def deliver_mail(mail) #:nodoc:
- ActiveSupport::Notifications.instrument("deliver.action_mailer") do |payload|
- set_payload_for_mail(payload, mail)
- yield # Let Mail do the delivery actions
- end
- end
- def respond_to?(method, include_private = false) #:nodoc:
- super || action_methods.include?(method.to_s)
- end
- protected
- def set_payload_for_mail(payload, mail) #:nodoc:
- payload[:mailer] = name
- payload[:message_id] = mail.message_id
- payload[:subject] = mail.subject
- payload[:to] = mail.to
- payload[:from] = mail.from
- payload[:bcc] = mail.bcc if mail.bcc.present?
- payload[:cc] = mail.cc if mail.cc.present?
- payload[:date] = mail.date
- payload[:mail] = mail.encoded
- end
- def method_missing(method_name, *args)
- if respond_to?(method_name)
- new(method_name, *args).message
- else
- super
- end
- end
- end
- attr_internal :message
- # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
- # will be initialized according to the named method. If not, the mailer will
- # remain uninitialized (useful when you only need to invoke the "receive"
- # method, for instance).
- def initialize(method_name=nil, *args)
- super()
- @_mail_was_called = false
- @_message = Mail.new
- process(method_name, *args) if method_name
- end
- def process(*args) #:nodoc:
- lookup_context.skip_default_locale!
- super
- @_message = NullMail.new unless @_mail_was_called
- end
- class NullMail #:nodoc:
- def body; '' end
- def method_missing(*args)
- nil
- end
- end
- def mailer_name
- self.class.mailer_name
- end
- # Allows you to pass random and unusual headers to the new <tt>Mail::Message</tt> object
- # which will add them to itself.
- #
- # headers['X-Special-Domain-Specific-Header'] = "SecretValue"
- #
- # You can also pass a hash into headers of header field names and values, which
- # will then be set on the Mail::Message object:
- #
- # headers 'X-Special-Domain-Specific-Header' => "SecretValue",
- # 'In-Reply-To' => incoming.message_id
- #
- # The resulting Mail::Message will have the following in its header:
- #
- # X-Special-Domain-Specific-Header: SecretValue
- def headers(args = nil)
- if args
- @_message.headers(args)
- else
- @_message
- end
- end
- # Allows you to add attachments to an email, like so:
- #
- # mail.attachments['filename.jpg'] = File.read('/path/to/filename.jpg')
- #
- # If you do this, then Mail will take the file name and work out the mime type
- # set the Content-Type, Content-Disposition, Content-Transfer-Encoding and
- # base64 encode the contents of the attachment all for you.
- #
- # You can also specify overrides if you want by passing a hash instead of a string:
- #
- # mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip',
- # content: File.read('/path/to/filename.jpg')}
- #
- # If you want to use a different encoding than Base64, you can pass an encoding in,
- # but then it is up to you to pass in the content pre-encoded, and don't expect
- # Mail to know how to decode this data:
- #
- # file_content = SpecialEncode(File.read('/path/to/filename.jpg'))
- # mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip',
- # encoding: 'SpecialEncoding',
- # content: file_content }
- #
- # You can also search for specific attachments:
- #
- # # By Filename
- # mail.attachments['filename.jpg'] # => Mail::Part object or nil
- #
- # # or by index
- # mail.attachments[0] # => Mail::Part (first attachment)
- #
- def attachments
- @_message.attachments
- end
- # The main method that creates the message and renders the email templates. There are
- # two ways to call this method, with a block, or without a block.
- #
- # Both methods accept a headers hash. This hash allows you to specify the most used headers
- # in an email message, these are:
- #
- # * <tt>:subject</tt> - The subject of the message, if this is omitted, Action Mailer will
- # ask the Rails I18n class for a translated <tt>:subject</tt> in the scope of
- # <tt>[mailer_scope, action_name]</tt> or if this is missing, will translate the
- # humanized version of the <tt>action_name</tt>
- # * <tt>:to</tt> - Who the message is destined for, can be a string of addresses, or an array
- # of addresses.
- # * <tt>:from</tt> - Who the message is from
- # * <tt>:cc</tt> - Who you would like to Carbon-Copy on this email, can be a string of addresses,
- # or an array of addresses.
- # * <tt>:bcc</tt> - Who you would like to Blind-Carbon-Copy on this email, can be a string of
- # addresses, or an array of addresses.
- # * <tt>:reply_to</tt> - Who to set the Reply-To header of the email to.
- # * <tt>:date</tt> - The date to say the email was sent on.
- #
- # You can set default values for any of the above headers (except :date) by using the <tt>default</tt>
- # class method:
- #
- # class Notifier < ActionMailer::Base
- # self.default from: 'no-reply@test.lindsaar.net',
- # bcc: 'email_logger@test.lindsaar.net',
- # reply_to: 'bounces@test.lindsaar.net'
- # end
- #
- # If you need other headers not listed above, you can either pass them in
- # as part of the headers hash or use the <tt>headers['name'] = value</tt>
- # method.
- #
- # When a <tt>:return_path</tt> is specified as header, that value will be used as the 'envelope from'
- # address for the Mail message. Setting this is useful when you want delivery notifications
- # sent to a different address than the one in <tt>:from</tt>. Mail will actually use the
- # <tt>:return_path</tt> in preference to the <tt>:sender</tt> in preference to the <tt>:from</tt>
- # field for the 'envelope from' value.
- #
- # If you do not pass a block to the +mail+ method, it will find all templates in the
- # view paths using by default the mailer name and the method name that it is being
- # called from, it will then create parts for each of these templates intelligently,
- # making educated guesses on correct content type and sequence, and return a fully
- # prepared Mail::Message ready to call <tt>:deliver</tt> on to send.
- #
- # For example:
- #
- # class Notifier < ActionMailer::Base
- # default from: 'no-reply@test.lindsaar.net',
- #
- # def welcome
- # mail(to: 'mikel@test.lindsaar.net')
- # end
- # end
- #
- # Will look for all templates at "app/views/notifier" with name "welcome".
- # If no welcome template exists, it will raise an ActionView::MissingTemplate error.
- #
- # However, those can be customized:
- #
- # mail(template_path: 'notifications', template_name: 'another')
- #
- # And now it will look for all templates at "app/views/notifications" with name "another".
- #
- # If you do pass a block, you can render specific templates of your choice:
- #
- # mail(to: 'mikel@test.lindsaar.net') do |format|
- # format.text
- # format.html
- # end
- #
- # You can even render text directly without using a template:
- #
- # mail(to: 'mikel@test.lindsaar.net') do |format|
- # format.text { render text: "Hello Mikel!" }
- # format.html { render text: "<h1>Hello Mikel!</h1>" }
- # end
- #
- # Which will render a <tt>multipart/alternative</tt> email with <tt>text/plain</tt> and
- # <tt>text/html</tt> parts.
- #
- # The block syntax also allows you to customize the part headers if desired:
- #
- # mail(to: 'mikel@test.lindsaar.net') do |format|
- # format.text(content_transfer_encoding: "base64")
- # format.html
- # end
- #
- def mail(headers = {}, &block)
- @_mail_was_called = true
- m = @_message
- # At the beginning, do not consider class default for content_type
- content_type = headers[:content_type]
- # Call all the procs (if any)
- class_default = self.class.default
- default_values = class_default.merge(class_default) do |k,v|
- v.respond_to?(:to_proc) ? instance_eval(&v) : v
- end
- # Handle defaults
- headers = headers.reverse_merge(default_values)
- headers[:subject] ||= default_i18n_subject
- # Apply charset at the beginning so all fields are properly quoted
- m.charset = charset = headers[:charset]
- # Set configure delivery behavior
- wrap_delivery_behavior!(headers.delete(:delivery_method),headers.delete(:delivery_method_options))
- # Assign all headers except parts_order, content_type and body
- assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path)
- assignable.each { |k, v| m[k] = v }
- # Render the templates and blocks
- responses = collect_responses(headers, &block)
- create_parts_from_responses(m, responses)
- # Setup content type, reapply charset and handle parts order
- m.content_type = set_content_type(m, content_type, headers[:content_type])
- m.charset = charset
- if m.multipart?
- m.body.set_sort_order(headers[:parts_order])
- m.body.sort_parts!
- end
- m
- end
- protected
- def set_content_type(m, user_content_type, class_default)
- params = m.content_type_parameters || {}
- case
- when user_content_type.present?
- user_content_type
- when m.has_attachments?
- if m.attachments.detect { |a| a.inline? }
- ["multipart", "related", params]
- else
- ["multipart", "mixed", params]
- end
- when m.multipart?
- ["multipart", "alternative", params]
- else
- m.content_type || class_default
- end
- end
- # Translates the +subject+ using Rails I18n class under <tt>[mailer_scope, action_name]</tt> scope.
- # If it does not find a translation for the +subject+ under the specified scope it will default to a
- # humanized version of the <tt>action_name</tt>.
- # If the subject has interpolations, you can pass them through the +interpolations+ parameter.
- def default_i18n_subject(interpolations = {})
- mailer_scope = self.class.mailer_name.tr('/', '.')
- I18n.t(:subject, interpolations.merge(scope: [mailer_scope, action_name], default: action_name.humanize))
- end
- def collect_responses(headers) #:nodoc:
- responses = []
- if block_given?
- collector = ActionMailer::Collector.new(lookup_context) { render(action_name) }
- yield(collector)
- responses = collector.responses
- elsif headers[:body]
- responses << {
- body: headers.delete(:body),
- content_type: self.class.default[:content_type] || "text/plain"
- }
- else
- templates_path = headers.delete(:template_path) || self.class.mailer_name
- templates_name = headers.delete(:template_name) || action_name
- each_template(Array(templates_path), templates_name) do |template|
- self.formats = template.formats
- responses << {
- body: render(template: template),
- content_type: template.type.to_s
- }
- end
- end
- responses
- end
- def each_template(paths, name, &block) #:nodoc:
- templates = lookup_context.find_all(name, paths)
- if templates.empty?
- raise ActionView::MissingTemplate.new(paths, name, paths, false, 'mailer')
- else
- templates.uniq { |t| t.formats }.each(&block)
- end
- end
- def create_parts_from_responses(m, responses) #:nodoc:
- if responses.size == 1 && !m.has_attachments?
- responses[0].each { |k,v| m[k] = v }
- elsif responses.size > 1 && m.has_attachments?
- container = Mail::Part.new
- container.content_type = "multipart/alternative"
- responses.each { |r| insert_part(container, r, m.charset) }
- m.add_part(container)
- else
- responses.each { |r| insert_part(m, r, m.charset) }
- end
- end
- def insert_part(container, response, charset) #:nodoc:
- response[:charset] ||= charset
- part = Mail::Part.new(response)
- container.add_part(part)
- end
- ActiveSupport.run_load_hooks(:action_mailer, self)
- end
- end
- require 'abstract_controller/collector'
- require 'active_support/core_ext/hash/reverse_merge'
- require 'active_support/core_ext/array/extract_options'
- module ActionMailer
- class Collector
- include AbstractController::Collector
- attr_reader :responses
- def initialize(context, &block)
- @context = context
- @responses = []
- @default_render = block
- end
- def any(*args, &block)
- options = args.extract_options!
- raise ArgumentError, "You have to supply at least one format" if args.empty?
- args.each { |type| send(type, options.dup, &block) }
- end
- alias :all :any
- def custom(mime, options = {})
- options.reverse_merge!(content_type: mime.to_s)
- @context.formats = [mime.to_sym]
- options[:body] = block_given? ? yield : @default_render.call
- @responses << options
- end
- end
- end
- require 'tmpdir'
- module ActionMailer
- # This module handles everything related to mail delivery, from registering
- # new delivery methods to configuring the mail object to be sent.
- module DeliveryMethods
- extend ActiveSupport::Concern
- included do
- class_attribute :delivery_methods, :delivery_method
- # Do not make this inheritable, because we always want it to propagate
- cattr_accessor :raise_delivery_errors
- self.raise_delivery_errors = true
- cattr_accessor :perform_deliveries
- self.perform_deliveries = true
- self.delivery_methods = {}.freeze
- self.delivery_method = :smtp
- add_delivery_method :smtp, Mail::SMTP,
- address: "localhost",
- port: 25,
- domain: 'localhost.localdomain',
- user_name: nil,
- password: nil,
- authentication: nil,
- enable_starttls_auto: true
- add_delivery_method :file, Mail::FileDelivery,
- location: defined?(Rails.root) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails"
- add_delivery_method :sendmail, Mail::Sendmail,
- location: '/usr/sbin/sendmail',
- arguments: '-i -t'
- add_delivery_method :test, Mail::TestMailer
- end
- module ClassMethods
- # Provides a list of emails that have been delivered by Mail::TestMailer
- delegate :deliveries, :deliveries=, to: Mail::TestMailer
- # Adds a new delivery method through the given class using the given
- # symbol as alias and the default options supplied.
- #
- # add_delivery_method :sendmail, Mail::Sendmail,
- # location: '/usr/sbin/sendmail',
- # arguments: '-i -t'
- def add_delivery_method(symbol, klass, default_options={})
- class_attribute(:"#{symbol}_settings") unless respond_to?(:"#{symbol}_settings")
- send(:"#{symbol}_settings=", default_options)
- self.delivery_methods = delivery_methods.merge(symbol.to_sym => klass).freeze
- end
- def wrap_delivery_behavior(mail, method=nil, options=nil) # :nodoc:
- method ||= self.delivery_method
- mail.delivery_handler = self
- case method
- when NilClass
- raise "Delivery method cannot be nil"
- when Symbol
- if klass = delivery_methods[method]
- mail.delivery_method(klass,(send(:"#{method}_settings") || {}).merge!(options || {}))
- else
- raise "Invalid delivery method #{method.inspect}"
- end
- else
- mail.delivery_method(method)
- end
- mail.perform_deliveries = perform_deliveries
- mail.raise_delivery_errors = raise_delivery_errors
- end
- end
- def wrap_delivery_behavior!(*args) # :nodoc:
- self.class.wrap_delivery_behavior(message, *args)
- end
- end
- end
- module ActionMailer
- class LogSubscriber < ActiveSupport::LogSubscriber
- def deliver(event)
- return unless logger.info?
- recipients = Array(event.payload[:to]).join(', ')
- info("\nSent mail to #{recipients} (#{event.duration.round(1)}ms)")
- debug(event.payload[:mail])
- end
- def receive(event)
- return unless logger.info?
- info("\nReceived mail (#{event.duration.round(1)}ms)")
- debug(event.payload[:mail])
- end
- def logger
- ActionMailer::Base.logger
- end
- end
- end
- ActionMailer::LogSubscriber.attach_to :action_mailer
- module ActionMailer
- module MailHelper
- # Take the text and format it, indented two spaces for each line, and
- # wrapped at 72 columns.
- def block_format(text)
- formatted = text.split(/\n\r?\n/).collect { |paragraph|
- format_paragraph(paragraph)
- }.join("\n\n")
- # Make list points stand on their own line
- formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" }
- formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { |s| " #{$1} #{$2.strip}\n" }
- formatted
- end
- # Access the mailer instance.
- def mailer
- @_controller
- end
- # Access the message instance.
- def message
- @_message
- end
- # Access the message attachments list.
- def attachments
- @_message.attachments
- end
- # Returns +text+ wrapped at +len+ columns and indented +indent+ spaces.
- #
- # my_text = 'Here is a sample text with more than 40 characters'
- #
- # format_paragraph(my_text, 25, 4)
- # # => " Here is a sample text with\n more than 40 characters"
- def format_paragraph(text, len = 72, indent = 2)
- sentences = [[]]
- text.split.each do |word|
- if sentences.first.present? && (sentences.last + [word]).join(' ').length > len
- sentences << [word]
- else
- sentences.last << word
- end
- end
- sentences.map { |sentence|
- "#{" " * indent}#{sentence.join(' ')}"
- }.join "\n"
- end
- end
- end
- require "action_mailer"
- require "rails"
- require "abstract_controller/railties/routes_helpers"
- module ActionMailer
- class Railtie < Rails::Railtie # :nodoc:
- config.action_mailer = ActiveSupport::OrderedOptions.new
- config.eager_load_namespaces << ActionMailer
- initializer "action_mailer.logger" do
- ActiveSupport.on_load(:action_mailer) { self.logger ||= Rails.logger }
- end
- initializer "action_mailer.set_configs" do |app|
- paths = app.config.paths
- options = app.config.action_mailer
- options.assets_dir ||= paths["public"].first
- options.javascripts_dir ||= paths["public/javascripts"].first
- options.stylesheets_dir ||= paths["public/stylesheets"].first
- # make sure readers methods get compiled
- options.asset_host ||= app.config.asset_host
- options.relative_url_root ||= app.config.relative_url_root
- ActiveSupport.on_load(:action_mailer) do
- include AbstractController::UrlFor
- extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
- include app.routes.mounted_helpers
- register_interceptors(options.delete(:interceptors))
- register_observers(options.delete(:observers))
- options.each { |k,v| send("#{k}=", v) }
- end
- end
- initializer "action_mailer.compile_config_methods" do
- ActiveSupport.on_load(:action_mailer) do
- config.compile_methods! if config.respond_to?(:compile_methods!)
- end
- end
- end
- end
- require 'active_support/test_case'
- module ActionMailer
- class NonInferrableMailerError < ::StandardError
- def initialize(name)
- super "Unable to determine the mailer to test from #{name}. " +
- "You'll need to specify it using tests YourMailer in your " +
- "test case definition"
- end
- end
- class TestCase < ActiveSupport::TestCase
- module Behavior
- extend ActiveSupport::Concern
- include ActiveSupport::Testing::ConstantLookup
- include TestHelper
- included do
- class_attribute :_mailer_class
- setup :initialize_test_deliveries
- setup :set_expected_mail
- end
- module ClassMethods
- def tests(mailer)
- case mailer
- when String, Symbol
- self._mailer_class = mailer.to_s.camelize.constantize
- when Module
- self._mailer_class = mailer
- else
- raise NonInferrableMailerError.new(mailer)
- end
- end
- def mailer_class
- if mailer = self._mailer_class
- mailer
- else
- tests determine_default_mailer(name)
- end
- end
- def determine_default_mailer(name)
- mailer = determine_constant_from_test_name(name) do |constant|
- Class === constant && constant < ActionMailer::Base
- end
- raise NonInferrableMailerError.new(name) if mailer.nil?
- mailer
- end
- end
- protected
- def initialize_test_deliveries
- ActionMailer::Base.delivery_method = :test
- ActionMailer::Base.perform_deliveries = true
- ActionMailer::Base.deliveries.clear
- end
- def set_expected_mail
- @expected = Mail.new
- @expected.content_type ["text", "plain", { "charset" => charset }]
- @expected.mime_version = '1.0'
- end
- private
- def charset
- "UTF-8"
- end
- def encode(subject)
- Mail::Encodings.q_value_encode(subject, charset)
- end
- def read_fixture(action)
- IO.readlines(File.join(Rails.root, 'test', 'fixtures', self.class.mailer_class.name.underscore, action))
- end
- end
- include Behavior
- end
- end
- module ActionMailer
- module TestHelper
- # Asserts that the number of emails sent matches the given number.
- #
- # def test_emails
- # assert_emails 0
- # ContactMailer.welcome.deliver
- # assert_emails 1
- # ContactMailer.welcome.deliver
- # assert_emails 2
- # end
- #
- # If a block is passed, that block should cause the specified number of
- # emails to be sent.
- #
- # def test_emails_again
- # assert_emails 1 do
- # ContactMailer.welcome.deliver
- # end
- #
- # assert_emails 2 do
- # ContactMailer.welcome.deliver
- # ContactMailer.welcome.deliver
- # end
- # end
- def assert_emails(number)
- if block_given?
- original_count = ActionMailer::Base.deliveries.size
- yield
- new_count = ActionMailer::Base.deliveries.size
- assert_equal original_count + number, new_count, "#{number} emails expected, but #{new_count - original_count} were sent"
- else
- assert_equal number, ActionMailer::Base.deliveries.size
- end
- end
- # Assert that no emails have been sent.
- #
- # def test_emails
- # assert_no_emails
- # ContactMailer.welcome.deliver
- # assert_emails 1
- # end
- #
- # If a block is passed, that block should not cause any emails to be sent.
- #
- # def test_emails_again
- # assert_no_emails do
- # # No emails should be sent from this block
- # end
- # end
- #
- # Note: This assertion is simply a shortcut for:
- #
- # assert_emails 0
- def assert_no_emails(&block)
- assert_emails 0, &block
- end
- end
- end
- module ActionMailer
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta"
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
- end
- end
- #--
- # Copyright (c) 2004-2013 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:
- #
- # 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.
- #++
- require 'abstract_controller'
- require 'action_view'
- require 'action_mailer/version'
- # Common Active Support usage in Action Mailer
- require 'active_support/rails'
- require 'active_support/core_ext/class'
- require 'active_support/core_ext/module/attr_internal'
- require 'active_support/core_ext/string/inflections'
- require 'active_support/lazy_load_hooks'
- module ActionMailer
- extend ::ActiveSupport::Autoload
- eager_autoload do
- autoload :Collector
- end
- autoload :Base
- autoload :DeliveryMethods
- autoload :MailHelper
- autoload :TestCase
- autoload :TestHelper
- end
- module Rails
- module Generators
- class MailerGenerator < NamedBase
- source_root File.expand_path("../templates", __FILE__)
- argument :actions, type: :array, default: [], banner: "method method"
- check_class_collision
- def create_mailer_file
- template "mailer.rb", File.join('app/mailers', class_path, "#{file_name}.rb")
- end
- hook_for :template_engine, :test_framework
- end
- end
- end
- <% module_namespacing do -%>
- class <%= class_name %> < ActionMailer::Base
- default from: "from@example.com"
- <% actions.each do |action| -%>
- # Subject can be set in your I18n file at config/locales/en.yml
- # with the following lookup:
- #
- # en.<%= file_path.tr("/",".") %>.<%= action %>.subject
- #
- def <%= action %>
- @greeting = "Hi"
- mail to: "to@example.org"
- end
- <% end -%>
- end
- <% end -%>
- module AbstractController
- module AssetPaths #:nodoc:
- extend ActiveSupport::Concern
- included do
- config_accessor :asset_host, :assets_dir, :javascripts_dir,
- :stylesheets_dir, :default_asset_host_protocol, :relative_url_root
- end
- end
- end
- require 'erubis'
- require 'set'
- require 'active_support/configurable'
- require 'active_support/descendants_tracker'
- require 'active_support/core_ext/module/anonymous'
- module AbstractController
- class Error < StandardError #:nodoc:
- end
- class ActionNotFound < StandardError #:nodoc:
- end
- # <tt>AbstractController::Base</tt> 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
- attr_internal :response_body
- attr_internal :action_name
- attr_internal :formats
- include ActiveSupport::Configurable
- extend ActiveSupport::DescendantsTracker
- undef_method :not_implemented
- class << self
- attr_reader :abstract
- alias_method :abstract?, :abstract
- # 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
- 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)
- def internal_methods
- controller = self
- controller = controller.superclass until controller.abstract?
- controller.public_instance_methods(true)
- end
- # The list of hidden actions. Defaults to an empty array.
- # This can be modified by other modules or subclasses
- # to specify particular actions as hidden.
- #
- # ==== Returns
- # * <tt>Array</tt> - An array of method names that should not be considered actions.
- def hidden_actions
- []
- 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. Finally, #hidden_actions are removed.
- #
- # ==== 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
- methods = (public_instance_methods(true) -
- # Except for public instance methods of Base and its ancestors
- internal_methods +
- # Be sure to include shadowed public instance methods of this class
- public_instance_methods(false)).uniq.map { |x| x.to_s } -
- # And always exclude explicitly hidden actions
- hidden_actions.to_a
- # Clear out AS callback method pollution
- Set.new(methods.reject { |method| method =~ /_one_time_conditions/ })
- end
- end
- # action_methods are cached and there is sometimes 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.
- # For instance, MyApp::MyPostsController would return "my_app/my_posts" for
- # controller_path.
- #
- # ==== Returns
- # * <tt>String</tt>
- def controller_path
- @controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous?
- end
- # Refresh the cached action_methods when a new action_method is added.
- def method_added(name)
- super
- clear_action_methods!
- end
- end
- abstract!
- # 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
- # ActionNotFound error is raised.
- #
- # ==== Returns
- # * <tt>self</tt>
- def process(action, *args)
- @_action_name = action_name = action.to_s
- unless action_name = method_for_action(action_name)
- raise ActionNotFound, "The action '#{action}' could not be found for #{self.class.name}"
- end
- @_response_body = nil
- process_action(action_name, *args)
- end
- # Delegates to the class' #controller_path
- def controller_path
- self.class.controller_path
- end
- # Delegates to the class' #action_methods
- def action_methods
- self.class.action_methods
- end
- # 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.
- #
- # ==== Parameters
- # * <tt>action_name</tt> - The name of an action to be tested
- #
- # ==== Returns
- # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
- def available_action?(action_name)
- method_for_action(action_name).present?
- end
- private
- # Returns true if the name can be considered an action because
- # it has a method defined in the controller.
- #
- # ==== Parameters
- # * <tt>name</tt> - The name of an action to be tested
- #
- # ==== Returns
- # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
- #
- # :api: private
- 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.
- #
- # Notice that the first argument is the method to be dispatched
- # which is *not* necessarily the same as the action name.
- def process_action(method_name, *args)
- send_action(method_name, *args)
- 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.
- 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.
- 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. 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.
- #
- # 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 ActionNotFound exception will be raised.
- #
- # ==== Parameters
- # * <tt>action_name</tt> - An action name to find a method name for
- #
- # ==== Returns
- # * <tt>string</tt> - The name of the method that handles the action
- # * <tt>nil</tt> - No method name could be found. Raise ActionNotFound.
- def method_for_action(action_name)
- if action_method?(action_name)
- action_name
- elsif respond_to?(:action_missing, true)
- "_handle_action_missing"
- end
- end
- end
- end
- module AbstractController
- 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.
- include ActiveSupport::Callbacks
- included do
- define_callbacks :process_action, :terminator => "response_body", :skip_after_callbacks_if_terminated => true
- end
- # Override AbstractController::Base's process_action to run the
- # process_action callbacks around the normal behavior.
- def process_action(*args)
- run_callbacks(:process_action) do
- super
- end
- end
- module ClassMethods
- # If :only or :except are used, convert the options into the
- # :unless and :if options of ActiveSupport::Callbacks.
- # The basic idea is that :only => :index gets converted to
- # :if => proc {|c| c.action_name == "index" }.
- #
- # ==== 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)
- end
- def _normalize_callback_option(options, from, to) # :nodoc:
- if from = options[from]
- from = Array(from).map {|o| "action_name == '#{o}'"}.join(" || ")
- options[to] = Array(options[to]) << from
- end
- end
- # Skip before, after, and around action callbacks matching any of the names
- # Aliased as skip_filter.
- #
- # ==== Parameters
- # * <tt>names</tt> - A list of valid names that could be used for
- # callbacks. Note that skipping uses Ruby equality, so it's
- # impossible to skip a callback defined using an anonymous proc
- # using #skip_filter
- def skip_action_callback(*names)
- skip_before_action(*names)
- skip_after_action(*names)
- skip_around_action(*names)
- end
- alias_method :skip_filter, :skip_action_callback
- # 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.
- #
- # ==== 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.last.is_a?(Hash) ? callbacks.pop : {}
- _normalize_callback_options(options)
- callbacks.push(block) if block
- callbacks.each do |callback|
- yield callback, options
- end
- end
- ##
- # :method: before_action
- #
- # :call-seq: before_action(names, block)
- #
- # Append a callback before actions. See _insert_callbacks for parameter details.
- # Aliased as before_filter.
- ##
- # :method: prepend_before_action
- #
- # :call-seq: prepend_before_action(names, block)
- #
- # Prepend a callback before actions. See _insert_callbacks for parameter details.
- # Aliased as prepend_before_filter.
- ##
- # :method: skip_before_action
- #
- # :call-seq: skip_before_action(names)
- #
- # Skip a callback before actions. See _insert_callbacks for parameter details.
- # Aliased as skip_before_filter.
- ##
- # :method: append_before_action
- #
- # :call-seq: append_before_action(names, block)
- #
- # Append a callback before actions. See _insert_callbacks for parameter details.
- # Aliased as append_before_filter.
- ##
- # :method: after_action
- #
- # :call-seq: after_action(names, block)
- #
- # Append a callback after actions. See _insert_callbacks for parameter details.
- # Aliased as after_filter.
- ##
- # :method: prepend_after_action
- #
- # :call-seq: prepend_after_action(names, block)
- #
- # Prepend a callback after actions. See _insert_callbacks for parameter details.
- # Aliased as prepend_after_filter.
- ##
- # :method: skip_after_action
- #
- # :call-seq: skip_after_action(names)
- #
- # Skip a callback after actions. See _insert_callbacks for parameter details.
- # Aliased as skip_after_filter.
- ##
- # :method: append_after_action
- #
- # :call-seq: append_after_action(names, block)
- #
- # Append a callback after actions. See _insert_callbacks for parameter details.
- # Aliased as append_after_filter.
- ##
- # :method: around_action
- #
- # :call-seq: around_action(names, block)
- #
- # Append a callback around actions. See _insert_callbacks for parameter details.
- # Aliased as around_filter.
- ##
- # :method: prepend_around_action
- #
- # :call-seq: prepend_around_action(names, block)
- #
- # Prepend a callback around actions. See _insert_callbacks for parameter details.
- # Aliased as prepend_around_filter.
- ##
- # :method: skip_around_action
- #
- # :call-seq: skip_around_action(names)
- #
- # Skip a callback around actions. See _insert_callbacks for parameter details.
- # Aliased as skip_around_filter.
- ##
- # :method: append_around_action
- #
- # :call-seq: append_around_action(names, block)
- #
- # Append a callback around actions. See _insert_callbacks for parameter details.
- # Aliased as append_around_filter.
- # set up before_action, prepend_before_action, skip_before_action, etc.
- # for each of before, after, and around.
- [:before, :after, :around].each do |callback|
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- # Append a before, after or around callback. See _insert_callbacks
- # for details on the allowed parameters.
- def #{callback}_action(*names, &blk) # def before_action(*names, &blk)
- _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- set_callback(:process_action, :#{callback}, name, options) # set_callback(:process_action, :before, name, options)
- end # end
- end # end
- alias_method :#{callback}_filter, :#{callback}_action
- # Prepend a before, after or around callback. See _insert_callbacks
- # for details on the allowed parameters.
- def prepend_#{callback}_action(*names, &blk) # def prepend_before_action(*names, &blk)
- _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- set_callback(:process_action, :#{callback}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true))
- end # end
- end # end
- alias_method :prepend_#{callback}_filter, :prepend_#{callback}_action
- # Skip a before, after or around callback. See _insert_callbacks
- # for details on the allowed parameters.
- def skip_#{callback}_action(*names) # def skip_before_action(*names)
- _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options|
- skip_callback(:process_action, :#{callback}, name, options) # skip_callback(:process_action, :before, name, options)
- end # end
- end # end
- alias_method :skip_#{callback}_filter, :skip_#{callback}_action
- # *_action is the same as append_*_action
- alias_method :append_#{callback}_action, :#{callback}_action # alias_method :append_before_action, :before_action
- alias_method :append_#{callback}_filter, :#{callback}_action # alias_method :append_before_filter, :before_action
- RUBY_EVAL
- end
- end
- end
- end
- require "action_dispatch/http/mime_type"
- module AbstractController
- module Collector
- def self.generate_method_for_mime(mime)
- sym = mime.is_a?(Symbol) ? mime : mime.to_sym
- const = sym.upcase
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{sym}(*args, &block) # def html(*args, &block)
- custom(Mime::#{const}, *args, &block) # custom(Mime::HTML, *args, &block)
- end # end
- RUBY
- end
- Mime::SET.each do |mime|
- generate_method_for_mime(mime)
- end
- Mime::Type.register_callback do |mime|
- generate_method_for_mime(mime) unless self.instance_methods.include?(mime.to_sym)
- end
- protected
- def method_missing(symbol, &block)
- mime_constant = Mime.const_get(symbol.upcase)
- if Mime::SET.include?(mime_constant)
- AbstractController::Collector.generate_method_for_mime(mime_constant)
- send(symbol, &block)
- else
- super
- end
- end
- end
- end
- require 'active_support/dependencies'
- module AbstractController
- module Helpers
- extend ActiveSupport::Concern
- included do
- class_attribute :_helpers
- self._helpers = Module.new
- class_attribute :_helper_methods
- self._helper_methods = Array.new
- end
- 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.
- def inherited(klass)
- helpers = _helpers
- klass._helpers = Module.new { include helpers }
- klass.class_eval { default_helper_module! } unless klass.anonymous?
- super
- end
- # Declare a controller method as a helper. For example, the following
- # makes the +current_user+ controller method available to the view:
- # class ApplicationController < ActionController::Base
- # helper_method :current_user, :logged_in?
- #
- # def current_user
- # @current_user ||= User.find_by_id(session[:user])
- # end
- #
- # def logged_in?
- # current_user != nil
- # end
- # end
- #
- # In a view:
- # <% 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.
- def helper_method(*meths)
- meths.flatten!
- self._helper_methods += meths
- meths.each do |meth|
- _helpers.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
- def #{meth}(*args, &blk) # def current_user(*args, &blk)
- controller.send(%(#{meth}), *args, &blk) # controller.send(:current_user, *args, &blk)
- end # end
- ruby_eval
- end
- end
- # The +helper+ class method can take a series of helper module names, a block, or both.
- #
- # ==== Options
- # * <tt>*args</tt> - Module, Symbol, String, :all
- # * <tt>block</tt> - A block defining helper methods
- #
- # When the argument is a module it will be included directly in the template class.
- # helper FooHelper # => includes FooHelper
- #
- # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file
- # and include the module in the template class. The second form illustrates how to include custom helpers
- # when working with namespaced controllers, or other cases where the file containing the helper definition is not
- # in one of Rails' standard load paths:
- # helper :foo # => requires 'foo_helper' and includes FooHelper
- # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper
- #
- # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available
- # to the template.
- #
- # # One line
- # helper { def hello() "Hello, world!" end }
- #
- # # Multi-line
- # helper do
- # def foo(bar)
- # "#{bar} is the very best"
- # end
- # end
- #
- # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of
- # +symbols+, +strings+, +modules+ and blocks.
- #
- # helper(:three, BlindHelper) { def mice() 'mice' end }
- #
- def helper(*args, &block)
- modules_for_helpers(args).each do |mod|
- add_template_helper(mod)
- end
- _helpers.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.
- def clear_helpers
- inherited_helper_methods = _helper_methods
- self._helpers = Module.new
- self._helper_methods = Array.new
- inherited_helper_methods.each { |meth| helper_method meth }
- default_helper_module! unless anonymous?
- end
- # Returns a list of modules, normalized from the acceptable kinds of
- # helpers with the following behavior:
- #
- # String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper",
- # and "foo_bar_helper.rb" is loaded using require_dependency.
- #
- # Module:: No further processing
- #
- # After loading the appropriate files, the corresponding modules
- # are returned.
- #
- # ==== Parameters
- # * <tt>args</tt> - An array of helpers
- #
- # ==== Returns
- # * <tt>Array</tt> - A normalized list of modules for the list of
- # helpers provided.
- def modules_for_helpers(args)
- args.flatten.map! do |arg|
- case arg
- when String, Symbol
- file_name = "#{arg.to_s.underscore}_helper"
- begin
- require_dependency(file_name)
- rescue LoadError => e
- raise MissingHelperError.new(e, file_name)
- end
- file_name.camelize.constantize
- when Module
- arg
- else
- raise ArgumentError, "helper must be a String, Symbol, or Module"
- end
- end
- end
- class MissingHelperError < LoadError
- def initialize(error, path)
- @error = error
- @path = "helpers/#{path}.rb"
- set_backtrace error.backtrace
- super("Missing helper file helpers/%s.rb" % path)
- end
- end
- private
- # Makes all the (instance) methods in the helper module available to templates
- # rendered through this controller.
- #
- # ==== Parameters
- # * <tt>module</tt> - The module to include into the current helper module
- # for the class
- def add_template_helper(mod)
- _helpers.module_eval { include mod }
- end
- def default_helper_module!
- module_name = name.sub(/Controller$/, '')
- module_path = module_name.underscore
- helper module_path
- rescue MissingSourceFile => e
- raise e unless e.is_missing? "helpers/#{module_path}_helper"
- rescue NameError => e
- raise e unless e.missing_name? "#{module_name}Helper"
- end
- end
- end
- end
- require "active_support/core_ext/module/remove_method"
- module AbstractController
- # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
- # repeated setups. The inclusion pattern has pages that look like this:
- #
- # <%= render "shared/header" %>
- # Hello World
- # <%= render "shared/footer" %>
- #
- # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
- # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
- #
- # With layouts, you can flip it around and have the common structure know where to insert changing content. This means
- # that the header and footer are only mentioned in one place, like this:
- #
- # // The header part of this layout
- # <%= yield %>
- # // The footer part of this layout
- #
- # And then you have content pages that look like this:
- #
- # hello world
- #
- # At rendering time, the content page is computed and then inserted in the layout, like this:
- #
- # // The header part of this layout
- # hello world
- # // The footer part of this layout
- #
- # == Accessing shared variables
- #
- # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
- # references that won't materialize before rendering time:
- #
- # <h1><%= @page_title %></h1>
- # <%= yield %>
- #
- # ...and content pages that fulfill these references _at_ rendering time:
- #
- # <% @page_title = "Welcome" %>
- # Off-world colonies offers you a chance to start a new life
- #
- # The result after rendering is:
- #
- # <h1>Welcome</h1>
- # Off-world colonies offers you a chance to start a new life
- #
- # == Layout assignment
- #
- # You can either specify a layout declaratively (using the #layout class method) or give
- # it the same name as your controller, and place it in <tt>app/views/layouts</tt>.
- # If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance.
- #
- # For instance, if you have PostsController and a template named <tt>app/views/layouts/posts.html.erb</tt>,
- # that template will be used for all actions in PostsController and controllers inheriting
- # from PostsController.
- #
- # If you use a module, for instance Weblog::PostsController, you will need a template named
- # <tt>app/views/layouts/weblog/posts.html.erb</tt>.
- #
- # Since all your controllers inherit from ApplicationController, they will use
- # <tt>app/views/layouts/application.html.erb</tt> if no other layout is specified
- # or provided.
- #
- # == Inheritance Examples
- #
- # class BankController < ActionController::Base
- # # bank.html.erb exists
- #
- # class ExchangeController < BankController
- # # exchange.html.erb exists
- #
- # class CurrencyController < BankController
- #
- # class InformationController < BankController
- # layout "information"
- #
- # class TellerController < InformationController
- # # teller.html.erb exists
- #
- # class EmployeeController < InformationController
- # # employee.html.erb exists
- # layout nil
- #
- # class VaultController < BankController
- # layout :access_level_layout
- #
- # class TillController < BankController
- # layout false
- #
- # In these examples, we have three implicit lookup scenarios:
- # * The BankController uses the "bank" layout.
- # * The ExchangeController uses the "exchange" layout.
- # * The CurrencyController inherits the layout from BankController.
- #
- # However, when a layout is explicitly set, the explicitly set layout wins:
- # * The InformationController uses the "information" layout, explicitly set.
- # * The TellerController also uses the "information" layout, because the parent explicitly set it.
- # * The EmployeeController uses the "employee" layout, because it set the layout to nil, resetting the parent configuration.
- # * The VaultController chooses a layout dynamically by calling the <tt>access_level_layout</tt> method.
- # * The TillController does not use a layout at all.
- #
- # == Types of layouts
- #
- # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
- # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
- # be done either by specifying a method reference as a symbol or using an inline method (as a proc).
- #
- # The method reference is the preferred approach to variable layouts and is used like this:
- #
- # class WeblogController < ActionController::Base
- # layout :writers_and_readers
- #
- # def index
- # # fetching posts
- # end
- #
- # private
- # def writers_and_readers
- # logged_in? ? "writer_layout" : "reader_layout"
- # end
- # end
- #
- # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
- # is logged in or not.
- #
- # If you want to use an inline method, such as a proc, do something like this:
- #
- # class WeblogController < ActionController::Base
- # layout proc { |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
- # end
- #
- # If an argument isn't given to the proc, it's evaluated in the context of
- # the current controller anyway.
- #
- # class WeblogController < ActionController::Base
- # layout proc { logged_in? ? "writer_layout" : "reader_layout" }
- # end
- #
- # Of course, the most common way of specifying a layout is still just as a plain template name:
- #
- # class WeblogController < ActionController::Base
- # layout "weblog_standard"
- # end
- #
- # The template will be looked always in <tt>app/views/layouts/</tt> folder. But you can point
- # <tt>layouts</tt> folder direct also. <tt>layout "layouts/demo"</tt> is the same as <tt>layout "demo"</tt>.
- #
- # Setting the layout to nil forces it to be looked up in the filesystem and fallbacks to the parent behavior if none exists.
- # Setting it to nil is useful to re-enable template lookup overriding a previous configuration set in the parent:
- #
- # class ApplicationController < ActionController::Base
- # layout "application"
- # end
- #
- # class PostsController < ApplicationController
- # # Will use "application" layout
- # end
- #
- # class CommentsController < ApplicationController
- # # Will search for "comments" layout and fallback "application" layout
- # layout nil
- # end
- #
- # == Conditional layouts
- #
- # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
- # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
- # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
- #
- # class WeblogController < ActionController::Base
- # layout "weblog_standard", except: :rss
- #
- # # ...
- #
- # end
- #
- # This will assign "weblog_standard" as the WeblogController's layout for all actions except for the +rss+ action, which will
- # be rendered directly, without wrapping a layout around the rendered view.
- #
- # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
- # #<tt>except: [ :rss, :text_only ]</tt> is valid, as is <tt>except: :rss</tt>.
- #
- # == Using a different layout in the action render call
- #
- # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
- # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller.
- # You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example:
- #
- # class WeblogController < ActionController::Base
- # layout "weblog_standard"
- #
- # def help
- # render action: "help", layout: "help"
- # end
- # end
- #
- # This will override the controller-wide "weblog_standard" layout, and will render the help action with the "help" layout instead.
- module Layouts
- extend ActiveSupport::Concern
- include Rendering
- included do
- class_attribute :_layout, :_layout_conditions, :instance_accessor => false
- self._layout = nil
- self._layout_conditions = {}
- _write_layout_method
- end
- delegate :_layout_conditions, to: :class
- module ClassMethods
- def inherited(klass) # :nodoc:
- super
- klass._write_layout_method
- end
- # This module is mixed in if layout conditions are provided. This means
- # that if no layout conditions are used, this method is not used
- module LayoutConditions # :nodoc:
- private
- # Determines whether the current action has a layout definition by
- # checking the action name against the :only and :except conditions
- # set by the <tt>layout</tt> method.
- #
- # ==== Returns
- # * <tt> Boolean</tt> - True if the action has a layout definition, false otherwise.
- def _conditional_layout?
- return unless super
- conditions = _layout_conditions
- if only = conditions[:only]
- only.include?(action_name)
- elsif except = conditions[:except]
- !except.include?(action_name)
- else
- true
- end
- end
- end
- # Specify the layout to use for this class.
- #
- # If the specified layout is a:
- # String:: the String is the template name
- # Symbol:: call the method specified by the symbol, which will return the template name
- # false:: There is no layout
- # true:: raise an ArgumentError
- # nil:: Force default layout behavior with inheritance
- #
- # ==== Parameters
- # * <tt>layout</tt> - The layout to use.
- #
- # ==== Options (conditions)
- # * :only - A list of actions to apply this layout to.
- # * :except - Apply this layout to all actions but this one.
- def layout(layout, conditions = {})
- include LayoutConditions unless conditions.empty?
- conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
- self._layout_conditions = conditions
- self._layout = layout
- _write_layout_method
- end
- # If no layout is supplied, look for a template named the return
- # value of this method.
- #
- # ==== Returns
- # * <tt>String</tt> - A template name
- def _implied_layout_name # :nodoc:
- controller_path
- end
- # Creates a _layout method to be called by _default_layout .
- #
- # If a layout is not explicitly mentioned then look for a layout with the controller's name.
- # if nothing is found then try same procedure to find super class's layout.
- def _write_layout_method # :nodoc:
- remove_possible_method(:_layout)
- prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
- name_clause = if name
- <<-RUBY
- lookup_context.find_all("#{_implied_layout_name}", #{prefixes.inspect}).first || super
- RUBY
- else
- <<-RUBY
- super
- RUBY
- end
- layout_definition = case _layout
- when String
- _layout.inspect
- when Symbol
- <<-RUBY
- #{_layout}.tap do |layout|
- unless layout.is_a?(String) || !layout
- raise ArgumentError, "Your layout method :#{_layout} returned \#{layout}. It " \
- "should have returned a String, false, or nil"
- end
- end
- RUBY
- when Proc
- define_method :_layout_from_proc, &_layout
- _layout.arity == 0 ? "_layout_from_proc" : "_layout_from_proc(self)"
- when false
- nil
- when true
- raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil"
- when nil
- name_clause
- end
- self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def _layout
- if _conditional_layout?
- #{layout_definition}
- else
- #{name_clause}
- end
- end
- private :_layout
- RUBY
- end
- end
- def _normalize_options(options) # :nodoc:
- super
- if _include_layout?(options)
- layout = options.delete(:layout) { :default }
- options[:layout] = _layout_for_option(layout)
- end
- end
- attr_internal_writer :action_has_layout
- def initialize(*) # :nodoc:
- @_action_has_layout = true
- super
- end
- # Controls whether an action should be rendered using a layout.
- # If you want to disable any <tt>layout</tt> settings for the
- # current action so that it is rendered without a layout then
- # either override this method in your controller to return false
- # for that action or set the <tt>action_has_layout</tt> attribute
- # to false before rendering.
- def action_has_layout?
- @_action_has_layout
- end
- private
- def _conditional_layout?
- true
- end
- # This will be overwritten by _write_layout_method
- def _layout; end
- # Determine the layout for a given name, taking into account the name type.
- #
- # ==== Parameters
- # * <tt>name</tt> - The name of the template
- def _layout_for_option(name)
- case name
- when String then _normalize_layout(name)
- when Proc then name
- when true then Proc.new { _default_layout(true) }
- when :default then Proc.new { _default_layout(false) }
- when false, nil then nil
- else
- raise ArgumentError,
- "String, Proc, :default, true, or false, expected for `layout'; you passed #{name.inspect}"
- end
- end
- def _normalize_layout(value)
- value.is_a?(String) && value !~ /\blayouts/ ? "layouts/#{value}" : value
- end
- # Returns the default layout for this controller.
- # Optionally raises an exception if the layout could not be found.
- #
- # ==== Parameters
- # * <tt>require_layout</tt> - If set to true and layout is not found,
- # an ArgumentError exception is raised (defaults to false)
- #
- # ==== Returns
- # * <tt>template</tt> - The template object for the default layout (or nil)
- def _default_layout(require_layout = false)
- begin
- value = _layout if action_has_layout?
- rescue NameError => e
- raise e, "Could not render layout: #{e.message}"
- end
- if require_layout && action_has_layout? && !value
- raise ArgumentError,
- "There was no default layout for #{self.class} in #{view_paths.inspect}"
- end
- _normalize_layout(value)
- end
- def _include_layout?(options)
- (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout)
- end
- end
- end
- require "active_support/benchmarkable"
- module AbstractController
- module Logger #:nodoc:
- extend ActiveSupport::Concern
- included do
- config_accessor :logger
- include ActiveSupport::Benchmarkable
- end
- end
- end
- module AbstractController
- module Railties
- module RoutesHelpers
- def self.with(routes)
- Module.new do
- define_method(:inherited) do |klass|
- super(klass)
- if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_routes_url_helpers) }
- klass.send(:include, namespace.railtie_routes_url_helpers)
- else
- klass.send(:include, routes.url_helpers)
- end
- end
- end
- end
- end
- end
- end
- require "abstract_controller/base"
- require "action_view"
- module AbstractController
- class DoubleRenderError < Error
- DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"."
- def initialize(message = nil)
- super(message || DEFAULT_MESSAGE)
- end
- end
- # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,
- # it will trigger the lookup_context and consequently expire the cache.
- class I18nProxy < ::I18n::Config #:nodoc:
- attr_reader :original_config, :lookup_context
- def initialize(original_config, lookup_context)
- original_config = original_config.original_config if original_config.respond_to?(:original_config)
- @original_config, @lookup_context = original_config, lookup_context
- end
- def locale
- @original_config.locale
- end
- def locale=(value)
- @lookup_context.locale = value
- end
- end
- module Rendering
- extend ActiveSupport::Concern
- include AbstractController::ViewPaths
- included do
- class_attribute :protected_instance_variables
- self.protected_instance_variables = []
- end
- # Overwrite process to setup I18n proxy.
- def process(*) #:nodoc:
- old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
- super
- ensure
- I18n.config = old_config
- end
- module ClassMethods
- def view_context_class
- @view_context_class ||= begin
- routes = respond_to?(:_routes) && _routes
- helpers = respond_to?(:_helpers) && _helpers
- Class.new(ActionView::Base) do
- if routes
- include routes.url_helpers
- include routes.mounted_helpers
- end
- if helpers
- include helpers
- end
- end
- end
- end
- end
- attr_internal_writer :view_context_class
- def view_context_class
- @_view_context_class ||= self.class.view_context_class
- end
- # An instance of a view class. The default view class is ActionView::Base
- #
- # The view class must have the following methods:
- # View.new[lookup_context, assigns, controller]
- # Create a new ActionView instance for a controller
- # View#render[options]
- # Returns String with the rendered template
- #
- # Override this method in a module to change the default behavior.
- def view_context
- view_context_class.new(view_renderer, view_assigns, self)
- end
- # Returns an object that is able to render templates.
- def view_renderer
- @_view_renderer ||= ActionView::Renderer.new(lookup_context)
- end
- # Normalize arguments, options and then delegates render_to_body and
- # sticks the result in self.response_body.
- def render(*args, &block)
- options = _normalize_render(*args, &block)
- self.response_body = render_to_body(options)
- end
- # Raw rendering of a template to a string. Just convert the results of
- # render_response into a String.
- # :api: plugin
- def render_to_string(*args, &block)
- options = _normalize_render(*args, &block)
- render_to_body(options)
- end
- # Raw rendering of a template to a Rack-compatible body.
- # :api: plugin
- def render_to_body(options = {})
- _process_options(options)
- _render_template(options)
- end
- # Find and renders a template based on the options given.
- # :api: private
- def _render_template(options) #:nodoc:
- lookup_context.rendered_format = nil if options[:formats]
- view_renderer.render(view_context, options)
- end
- DEFAULT_PROTECTED_INSTANCE_VARIABLES = [
- :@_action_name, :@_response_body, :@_formats, :@_prefixes, :@_config,
- :@_view_context_class, :@_view_renderer, :@_lookup_context
- ]
- # This method should return a hash with assigns.
- # You can overwrite this configuration per controller.
- # :api: public
- def view_assigns
- hash = {}
- variables = instance_variables
- variables -= protected_instance_variables
- variables -= DEFAULT_PROTECTED_INSTANCE_VARIABLES
- variables.each { |name| hash[name[1..-1]] = instance_variable_get(name) }
- hash
- end
- private
- # Normalize args and options.
- # :api: private
- def _normalize_render(*args, &block)
- options = _normalize_args(*args, &block)
- _normalize_options(options)
- options
- end
- # Normalize args by converting render "foo" to render :action => "foo" and
- # render "foo/bar" to render :file => "foo/bar".
- # :api: plugin
- def _normalize_args(action=nil, options={})
- case action
- when NilClass
- when Hash
- options = action
- when String, Symbol
- action = action.to_s
- key = action.include?(?/) ? :file : :action
- options[key] = action
- else
- options[:partial] = action
- end
- options
- end
- # Normalize options.
- # :api: plugin
- def _normalize_options(options)
- if options[:partial] == true
- options[:partial] = action_name
- end
- if (options.keys & [:partial, :file, :template]).empty?
- options[:prefixes] ||= _prefixes
- end
- options[:template] ||= (options[:action] || action_name).to_s
- options
- end
- # Process extra options.
- # :api: plugin
- def _process_options(options)
- end
- end
- end
- module AbstractController
- module Translation
- # Delegates to <tt>I18n.translate</tt>. Also aliased as <tt>t</tt>.
- #
- # 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.
- def translate(*args)
- key = args.first
- if key.is_a?(String) && (key[0] == '.')
- key = "#{ controller_path.gsub('/', '.') }.#{ action_name }#{ key }"
- args[0] = key
- end
- I18n.translate(*args)
- end
- alias :t :translate
- # Delegates to <tt>I18n.localize</tt>. Also aliased as <tt>l</tt>.
- def localize(*args)
- I18n.localize(*args)
- end
- alias :l :localize
- end
- end
- module AbstractController
- # 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.
- #
- # Note that this module is completely decoupled from HTTP - the only requirement is a valid
- # <tt>_routes</tt> implementation.
- module UrlFor
- extend ActiveSupport::Concern
- include ActionDispatch::Routing::UrlFor
- def _routes
- raise "In order to use #url_for, you must include routing helpers explicitly. " \
- "For instance, `include Rails.application.routes.url_helpers"
- end
- module ClassMethods
- def _routes
- nil
- end
- def action_methods
- @action_methods ||= begin
- if _routes
- super - _routes.named_routes.helper_names
- else
- super
- end
- end
- end
- end
- end
- end
- require 'action_view/base'
- module AbstractController
- module ViewPaths
- extend ActiveSupport::Concern
- included do
- class_attribute :_view_paths
- self._view_paths = ActionView::PathSet.new
- self._view_paths.freeze
- end
- delegate :template_exists?, :view_paths, :formats, :formats=,
- :locale, :locale=, :to => :lookup_context
- module ClassMethods
- def parent_prefixes
- @parent_prefixes ||= begin
- parent_controller = superclass
- prefixes = []
- until parent_controller.abstract?
- prefixes << parent_controller.controller_path
- parent_controller = parent_controller.superclass
- end
- prefixes
- end
- end
- end
- # The prefixes used in render "foo" shortcuts.
- def _prefixes
- @_prefixes ||= begin
- parent_prefixes = self.class.parent_prefixes
- parent_prefixes.dup.unshift(controller_path)
- end
- end
- # LookupContext is the object responsible to hold all information required to lookup
- # templates, i.e. view paths and details. Check ActionView::LookupContext for more
- # information.
- def lookup_context
- @_lookup_context ||=
- ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes)
- end
- def details_for_lookup
- { }
- end
- def append_view_path(path)
- lookup_context.view_paths.push(*path)
- end
- def prepend_view_path(path)
- lookup_context.view_paths.unshift(*path)
- end
- module ClassMethods
- # Append a path to the list of view paths for this controller.
- #
- # ==== Parameters
- # * <tt>path</tt> - If a String is provided, it gets converted into
- # the default view path. You may also provide a custom view path
- # (see ActionView::PathSet for more information)
- def append_view_path(path)
- self._view_paths = view_paths + Array(path)
- end
- # Prepend a path to the list of view paths for this controller.
- #
- # ==== Parameters
- # * <tt>path</tt> - If a String is provided, it gets converted into
- # the default view path. You may also provide a custom view path
- # (see ActionView::PathSet for more information)
- def prepend_view_path(path)
- self._view_paths = ActionView::PathSet.new(Array(path) + view_paths)
- end
- # A list of all of the default view paths for this controller.
- def view_paths
- _view_paths
- end
- # Set the view paths.
- #
- # ==== Parameters
- # * <tt>paths</tt> - If a PathSet is provided, use that;
- # otherwise, process the parameter into a PathSet.
- def view_paths=(paths)
- self._view_paths = ActionView::PathSet.new(Array(paths))
- end
- end
- end
- end
- require 'action_pack'
- require 'active_support/rails'
- require 'active_support/core_ext/module/attr_internal'
- require 'active_support/core_ext/module/anonymous'
- require 'active_support/i18n'
- module AbstractController
- extend ActiveSupport::Autoload
- autoload :Base
- autoload :Callbacks
- autoload :Collector
- autoload :Helpers
- autoload :Layouts
- autoload :Logger
- autoload :Rendering
- autoload :Translation
- autoload :AssetPaths
- autoload :ViewPaths
- autoload :UrlFor
- end
- require "action_controller/log_subscriber"
- require "action_controller/metal/params_wrapper"
- module ActionController
- # 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 render a template or redirect 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 <tt>ActionController::Base</tt>. All other
- # controllers in turn 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
- # end
- #
- # def create
- # @post = Post.create params[:post]
- # redirect_to posts_path
- # 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.
- #
- # 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["SERVER_ADDR"]
- # render text: "This server hosted at #{location}"
- # end
- #
- # == Parameters
- #
- # All request parameters, whether they come from a GET or POST request, or from the URL, are available through the params 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 params.
- #
- # 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 stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
- # If the address input had been named "post[address][street]", the params 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)
- #
- # And retrieved again through the same hash:
- #
- # Hello #{session[:person]}
- #
- # For removing objects from the session, you can either assign a single key to +nil+:
- #
- # # removes :person from session
- # session[:person] = nil
- #
- # or you can remove the entire session with +reset_session+.
- #
- # Sessions are stored by default in a browser cookie that's cryptographically signed, but unencrypted.
- # This prevents the user from tampering with the session but also allows him to see its contents.
- #
- # Do not put secret information in cookie-based sessions!
- #
- # == 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.
- #
- # == 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:
- #
- # def show
- # @post = Post.find(params[:id])
- # end
- #
- # Which are then automatically available to the view:
- #
- # 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:
- #
- # 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
- #
- # Read more about writing ERB and Builder templates in ActionView::Base.
- #
- # == 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:
- #
- # 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
- #
- # 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.
- #
- # Learn more about <tt>redirect_to</tt> and what options you have in ActionController::Redirecting.
- #
- # == 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:
- #
- # 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.
- #
- # 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!
- # We document the request and response methods here because albeit they are
- # implemented in ActionController::Metal, the type of the returned objects
- # is unknown at that level.
- ##
- # :method: request
- #
- # Returns an ActionDispatch::Request instance that represents the
- # current request.
- ##
- # :method: response
- #
- # Returns an ActionDispatch::Response that represents the current
- # response.
- # Shortcut helper that returns all the modules included in
- # ActionController::Base except the ones passed as arguments:
- #
- # class MetalController
- # ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
- # include left
- # 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.
- def self.without_modules(*modules)
- modules = modules.map do |m|
- m.is_a?(Symbol) ? ActionController.const_get(m) : m
- end
- MODULES - modules
- end
- MODULES = [
- AbstractController::Layouts,
- AbstractController::Translation,
- AbstractController::AssetPaths,
- Helpers,
- HideActions,
- UrlFor,
- Redirecting,
- Rendering,
- Renderers::All,
- ConditionalGet,
- RackDelegation,
- Caching,
- MimeResponds,
- ImplicitRender,
- StrongParameters,
- Cookies,
- Flash,
- RequestForgeryProtection,
- ForceSSL,
- Streaming,
- DataStreaming,
- RecordIdentifier,
- HttpAuthentication::Basic::ControllerMethods,
- HttpAuthentication::Digest::ControllerMethods,
- HttpAuthentication::Token::ControllerMethods,
- # Before callbacks should also be executed the earliest 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.
- Instrumentation,
- # Params wrapper should come before instrumentation so they are
- # properly showed in logs
- ParamsWrapper
- ]
- MODULES.each do |mod|
- include mod
- end
- # Define some internal variables that should not be propagated to the view.
- self.protected_instance_variables = [
- :@_status, :@_headers, :@_params, :@_env, :@_response, :@_request,
- :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout
- ]
- ActiveSupport.run_load_hooks(:action_controller, self)
- end
- end
- module ActionController
- module Caching
- # 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:
- #
- # expire_fragment('name_of_cache')
- module Fragments
- # Given a key (as described in +expire_fragment+), returns
- # a key suitable for use in reading, writing, or expiring a
- # cached fragment. All keys are prefixed with <tt>views/</tt> and uses
- # ActiveSupport::Cache.expand_cache_key for the expansion.
- def fragment_cache_key(key)
- ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
- end
- # 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?
- key = fragment_cache_key(key)
- instrument_fragment_cache :write_fragment, key do
- content = content.to_str
- cache_store.write(key, content, options)
- end
- content
- end
- # 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?
- key = fragment_cache_key(key)
- instrument_fragment_cache :read_fragment, key do
- result = cache_store.read(key, options)
- result.respond_to?(:html_safe) ? result.html_safe : result
- end
- end
- # 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 = fragment_cache_key(key)
- instrument_fragment_cache :exist_fragment?, key do
- cache_store.exist?(key, options)
- end
- end
- # Removes fragments from the cache.
- #
- # +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).
- #
- # +options+ is passed through to the cache store's +delete+
- # method (or <tt>delete_matched</tt>, for Regexp keys).
- def expire_fragment(key, options = nil)
- return unless cache_configured?
- key = fragment_cache_key(key) unless key.is_a?(Regexp)
- instrument_fragment_cache :expire_fragment, key do
- if key.is_a?(Regexp)
- cache_store.delete_matched(key, options)
- else
- cache_store.delete(key, options)
- end
- end
- end
- def instrument_fragment_cache(name, key) # :nodoc:
- ActiveSupport::Notifications.instrument("#{name}.action_controller", :key => key){ yield }
- end
- end
- end
- end
- require 'fileutils'
- require 'uri'
- require 'set'
- module ActionController
- # \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, set
- # config.action_controller.perform_caching = false.
- #
- # == \Caching stores
- #
- # All the caching stores from ActiveSupport::Cache are available to be used as backends
- # for Action Controller caching.
- #
- # Configuration examples (MemoryStore 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')
- module Caching
- extend ActiveSupport::Concern
- extend ActiveSupport::Autoload
- eager_autoload do
- autoload :Fragments
- end
- module ConfigMethods
- def cache_store
- config.cache_store
- end
- def cache_store=(store)
- config.cache_store = ActiveSupport::Cache.lookup_store(store)
- end
- private
- def cache_configured?
- perform_caching && cache_store
- end
- end
- include RackDelegation
- include AbstractController::Callbacks
- include ConfigMethods
- include Fragments
- included do
- extend ConfigMethods
- config_accessor :default_static_extension
- self.default_static_extension ||= '.html'
- def self.page_cache_extension=(extension)
- ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension)
- self.default_static_extension = extension
- end
- def self.page_cache_extension
- ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension)
- default_static_extension
- end
- config_accessor :perform_caching
- self.perform_caching = true if perform_caching.nil?
- class_attribute :_view_cache_dependencies
- self._view_cache_dependencies = []
- helper_method :view_cache_dependencies if respond_to?(:helper_method)
- end
- module ClassMethods
- def view_cache_dependency(&dependency)
- self._view_cache_dependencies += [dependency]
- end
- end
- def view_cache_dependencies
- self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact
- end
- protected
- # Convenience accessor.
- def cache(key, options = {}, &block)
- if cache_configured?
- cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block)
- else
- yield
- end
- end
- end
- end
- ActionController::Integration = ActionDispatch::Integration
- ActionController::IntegrationTest = ActionDispatch::IntegrationTest
- ActiveSupport::Deprecation.warn 'ActionController::Integration is deprecated and will be removed, use ActionDispatch::Integration instead.'
- ActiveSupport::Deprecation.warn 'ActionController::IntegrationTest is deprecated and will be removed, use ActionDispatch::IntegrationTest instead.'
- ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request
- ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response
- ActionController::Routing = ActionDispatch::Routing
- ActiveSupport::Deprecation.warn 'ActionController::AbstractRequest and ActionController::Request are deprecated and will be removed, use ActionDispatch::Request instead.'
- ActiveSupport::Deprecation.warn 'ActionController::AbstractResponse and ActionController::Response are deprecated and will be removed, use ActionDispatch::Response instead.'
- ActiveSupport::Deprecation.warn 'ActionController::Routing is deprecated and will be removed, use ActionDispatch::Routing instead.'
- module ActionController
- class LogSubscriber < ActiveSupport::LogSubscriber
- INTERNAL_PARAMS = %w(controller action format _method only_path)
- def start_processing(event)
- return unless logger.info?
- payload = event.payload
- params = payload[:params].except(*INTERNAL_PARAMS)
- format = payload[:format]
- format = format.to_s.upcase if format.is_a?(Symbol)
- info "Processing by #{payload[:controller]}##{payload[:action]} as #{format}"
- info " Parameters: #{params.inspect}" unless params.empty?
- end
- def process_action(event)
- return unless logger.info?
- payload = event.payload
- additions = ActionController::Base.log_process_action(payload)
- status = payload[:status]
- if status.nil? && payload[:exception].present?
- exception_class_name = payload[:exception].first
- status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
- end
- message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
- message << " (#{additions.join(" | ")})" unless additions.blank?
- info(message)
- end
- def halted_callback(event)
- info("Filter chain halted as #{event.payload[:filter]} rendered or redirected")
- end
- def send_file(event)
- info("Sent file #{event.payload[:path]} (#{event.duration.round(1)}ms)")
- end
- def redirect_to(event)
- info("Redirected to #{event.payload[:location]}")
- end
- def send_data(event)
- info("Sent data #{event.payload[:filename]} (#{event.duration.round(1)}ms)")
- end
- %w(write_fragment read_fragment exist_fragment?
- expire_fragment expire_page write_page).each do |method|
- class_eval <<-METHOD, __FILE__, __LINE__ + 1
- def #{method}(event)
- return unless logger.info?
- key_or_path = event.payload[:key] || event.payload[:path]
- human_name = #{method.to_s.humanize.inspect}
- info("\#{human_name} \#{key_or_path} (\#{event.duration.round(1)}ms)")
- end
- METHOD
- end
- def logger
- ActionController::Base.logger
- end
- end
- end
- ActionController::LogSubscriber.attach_to :action_controller
- require 'active_support/core_ext/hash/keys'
- module ActionController
- module ConditionalGet
- extend ActiveSupport::Concern
- include RackDelegation
- include Head
- included do
- class_attribute :etaggers
- self.etaggers = []
- 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 authorized displaying
- # of cached pages.
- #
- # class InvoicesController < ApplicationController
- # etag { current_user.try :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(@invoice)
- # 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.
- #
- # === Parameters:
- #
- # * <tt>:etag</tt>.
- # * <tt>:last_modified</tt>.
- # * <tt>:public</tt> By default the Cache-Control header is private, set this to
- # +true+ if you want your application to be cachable by other devices (proxy caches).
- #
- # === Example:
- #
- # def show
- # @article = Article.find(params[:id])
- # fresh_when(etag: @article, last_modified: @article.created_at, public: true)
- # end
- #
- # This will render the show template if the request isn't sending a matching etag or
- # If-Modified-Since header and just a <tt>304 Not Modified</tt> response if there's a match.
- #
- # You can also just pass a record where +last_modified+ will be set by calling
- # +updated_at+ and the etag by passing the object itself.
- #
- # def show
- # @article = Article.find(params[:id])
- # fresh_when(@article)
- # end
- #
- # When passing a record, you can still set whether the public header:
- #
- # def show
- # @article = Article.find(params[:id])
- # fresh_when(@article, public: true)
- # end
- def fresh_when(record_or_options, additional_options = {})
- if record_or_options.is_a? Hash
- options = record_or_options
- options.assert_valid_keys(:etag, :last_modified, :public)
- else
- record = record_or_options
- options = { etag: record, last_modified: record.try(:updated_at) }.merge!(additional_options)
- end
- response.etag = combine_etags(options[:etag]) if options[:etag]
- response.last_modified = options[:last_modified] if options[:last_modified]
- response.cache_control[:public] = true if options[:public]
- head :not_modified if request.fresh?(response)
- end
- # Sets the +etag+ and/or +last_modified+ on the response and checks it against
- # the client request. If the request doesn't match the options provided, the
- # request is considered stale and should be generated from scratch. Otherwise,
- # it's fresh and we don't need to generate anything and a reply of <tt>304 Not Modified</tt> is sent.
- #
- # === Parameters:
- #
- # * <tt>:etag</tt>.
- # * <tt>:last_modified</tt>.
- # * <tt>:public</tt> By default the Cache-Control header is private, set this to
- # +true+ if you want your application to be cachable by other devices (proxy caches).
- #
- # === Example:
- #
- # def show
- # @article = Article.find(params[:id])
- #
- # if stale?(etag: @article, last_modified: @article.created_at)
- # @statistics = @article.really_expensive_call
- # respond_to do |format|
- # # all the supported formats
- # end
- # end
- # end
- #
- # You can also just pass a record where +last_modified+ will be set by calling
- # updated_at and the etag by passing the object itself.
- #
- # def show
- # @article = Article.find(params[:id])
- #
- # if stale?(@article)
- # @statistics = @article.really_expensive_call
- # respond_to do |format|
- # # all the supported formats
- # end
- # end
- # end
- #
- # When passing a record, you can still set whether the public header:
- #
- # def show
- # @article = Article.find(params[:id])
- #
- # if stale?(@article, public: true)
- # @statistics = @article.really_expensive_call
- # respond_to do |format|
- # # all the supported formats
- # end
- # end
- # end
- def stale?(record_or_options, additional_options = {})
- fresh_when(record_or_options, additional_options)
- !request.fresh?(response)
- end
- # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a +private+
- # instruction, so that intermediate caches must not cache the response.
- #
- # expires_in 20.minutes
- # expires_in 3.hours, public: true
- # expires_in 3.hours, public: true, must_revalidate: true
- #
- # This method will overwrite an existing Cache-Control header.
- # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
- #
- # The method will also ensure a HTTP Date header for client compatibility.
- def expires_in(seconds, options = {})
- response.cache_control.merge!(
- :max_age => seconds,
- :public => options.delete(:public),
- :must_revalidate => options.delete(:must_revalidate)
- )
- options.delete(:private)
- response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"}
- response.date = Time.now unless response.date?
- end
- # Sets a HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should
- # occur by the browser or intermediate caches (like caching proxy servers).
- def expires_now
- response.cache_control.replace(:no_cache => true)
- end
- private
- def combine_etags(etag)
- [ etag, *etaggers.map { |etagger| instance_exec(&etagger) }.compact ]
- end
- end
- end
- module ActionController #:nodoc:
- module Cookies
- extend ActiveSupport::Concern
- include RackDelegation
- included do
- helper_method :cookies
- end
- private
- def cookies
- request.cookie_jar
- end
- end
- end
- require 'action_controller/metal/exceptions'
- module ActionController #:nodoc:
- # Methods for sending arbitrary data and for streaming files to the browser,
- # instead of rendering.
- module DataStreaming
- extend ActiveSupport::Concern
- include ActionController::Rendering
- DEFAULT_SEND_FILE_TYPE = 'application/octet-stream'.freeze #:nodoc:
- DEFAULT_SEND_FILE_DISPOSITION = 'attachment'.freeze #:nodoc:
- protected
- # 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.
- #
- # 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 register with
- # <tt>Mime::Type.register</tt>, for example :json
- # If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>.
- # If no content type is registered for the extension, 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).
- # * <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 guess the filename from
- # the URL, which is necessary for i18n filenames on certain browsers
- # (setting <tt>:filename</tt> 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).
- #
- # Simple download:
- #
- # send_file '/path/to.zip'
- #
- # Show a JPEG in the browser:
- #
- # 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', status: 404
- #
- # Read about the other Content-* HTTP headers if you'd like to
- # provide the user with more information (such as Content-Description) in
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11.
- #
- # 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
- # http://www.mnot.net/cache_docs/ for an overview of web caching and
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
- # for the Cache-Control header spec.
- def send_file(path, options = {}) #:doc:
- raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
- options[:filename] ||= File.basename(path) unless options[:url_based_filename]
- send_file_headers! options
- self.status = options[:status] || 200
- self.content_type = options[:content_type] if options.key?(:content_type)
- self.response_body = FileBody.new(path)
- 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.
- class FileBody #:nodoc:
- attr_reader :to_path
- def initialize(path)
- @to_path = path
- end
- # Stream the file's contents if Rack::Sendfile isn't present.
- def each
- File.open(to_path, 'rb') do |file|
- while chunk = file.read(16384)
- yield chunk
- end
- end
- end
- end
- # Sends the given binary data to the browser. This method is similar to
- # <tt>render text: 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 apparent 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 register with <tt>Mime::Type.register</tt>, for example :json
- # If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>.
- # If no content type is registered for the extension, 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).
- # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
- #
- # Generic data download:
- #
- # send_data buffer
- #
- # Download a dynamically-generated tarball:
- #
- # 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'
- #
- # See +send_file+ for more information on HTTP Content-* headers and caching.
- def send_data(data, options = {}) #:doc:
- send_file_headers! options
- render options.slice(:status, :content_type).merge(:text => data)
- end
- private
- def send_file_headers!(options)
- type_provided = options.has_key?(:type)
- content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE)
- raise ArgumentError, ":type option required" if content_type.nil?
- if content_type.is_a?(Symbol)
- extension = Mime[content_type]
- raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension
- self.content_type = extension
- else
- if !type_provided && options[:filename]
- # If type wasn't provided, try guessing from file extension.
- content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete('.')) || content_type
- end
- self.content_type = content_type
- end
- disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION)
- unless disposition.nil?
- disposition = disposition.to_s
- disposition += %(; filename="#{options[:filename]}") if options[:filename]
- headers['Content-Disposition'] = disposition
- end
- headers['Content-Transfer-Encoding'] = 'binary'
- response.sending_file = true
- # Fix a problem with IE 6.0 on opening downloaded files:
- # If Cache-Control: no-cache is set (which Rails does by default),
- # IE removes the file it just downloaded from its cache immediately
- # after it displays the "open/save" dialog, which means that if you
- # hit "open" the file isn't there anymore when the application that
- # is called for handling the download is run, so let's workaround that
- response.cache_control[:public] ||= false
- end
- end
- end
- module ActionController
- class ActionControllerError < StandardError #:nodoc:
- end
- class BadRequest < ActionControllerError #:nodoc:
- attr_reader :original_exception
- def initialize(type = nil, e = nil)
- return super() unless type && e
- super("Invalid #{type} parameters: #{e.message}")
- @original_exception = e
- set_backtrace e.backtrace
- end
- end
- class RenderError < ActionControllerError #:nodoc:
- end
- class RoutingError < ActionControllerError #:nodoc:
- attr_reader :failures
- def initialize(message, failures=[])
- super(message)
- @failures = failures
- end
- end
- class ActionController::UrlGenerationError < RoutingError #:nodoc:
- end
- class MethodNotAllowed < ActionControllerError #:nodoc:
- def initialize(*allowed_methods)
- super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.")
- end
- end
- class NotImplemented < MethodNotAllowed #:nodoc:
- end
- class UnknownController < ActionControllerError #:nodoc:
- end
- class MissingFile < ActionControllerError #:nodoc:
- end
- class SessionOverflowError < ActionControllerError #:nodoc:
- DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.'
- def initialize(message = nil)
- super(message || DEFAULT_MESSAGE)
- end
- end
- class UnknownHttpMethod < ActionControllerError #:nodoc:
- end
- class UnknownFormat < ActionControllerError #:nodoc:
- end
- end
- module ActionController #:nodoc:
- module Flash
- extend ActiveSupport::Concern
- included do
- class_attribute :_flash_types, instance_accessor: false
- self._flash_types = []
- delegate :flash, to: :request
- add_flash_types(:alert, :notice)
- end
- module ClassMethods
- def add_flash_types(*types)
- types.each do |type|
- next if _flash_types.include?(type)
- define_method(type) do
- request.flash[type]
- end
- helper_method type
- _flash_types << type
- end
- end
- end
- protected
- def redirect_to(options = {}, response_status_and_flash = {}) #:doc:
- self.class._flash_types.each do |flash_type|
- if type = response_status_and_flash.delete(flash_type)
- flash[flash_type] = type
- end
- end
- if other_flashes = response_status_and_flash.delete(:flash)
- flash.update(other_flashes)
- end
- super(options, response_status_and_flash)
- end
- end
- end
- module ActionController
- # This module provides a method which will redirect browser to use HTTPS
- # protocol. This will ensure that user's sensitive information will be
- # transferred safely over the internet. You _should_ always force browser
- # to use HTTPS when you're transferring sensitive information such as
- # user authentication, account information, or credit card information.
- #
- # Note that if you are really concerned about your application security,
- # you might consider using +config.force_ssl+ in your config file instead.
- # That will ensure all the data transferred via HTTPS protocol and prevent
- # user from getting session hijacked when accessing the site under unsecured
- # HTTP protocol.
- module ForceSSL
- extend ActiveSupport::Concern
- include AbstractController::Callbacks
- module ClassMethods
- # Force the request to this particular controller or specified actions to be
- # under HTTPS protocol.
- #
- # If you need to disable this for any reason (e.g. development) then you can use
- # an +:if+ or +:unless+ condition.
- #
- # class AccountsController < ApplicationController
- # force_ssl if: :ssl_configured?
- #
- # def ssl_configured?
- # !Rails.env.development?
- # end
- # end
- #
- # ==== Options
- # * <tt>host</tt> - Redirect to a different host name
- # * <tt>only</tt> - The callback should be run only for this action
- # * <tt>except</tt> - The callback should be run for all actions except this action
- # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback
- # will be called only when it returns a true value.
- # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback
- # will be called only when it returns a false value.
- def force_ssl(options = {})
- host = options.delete(:host)
- before_action(options) do
- force_ssl_redirect(host)
- end
- end
- end
- # Redirect the existing request to use the HTTPS protocol.
- #
- # ==== Parameters
- # * <tt>host</tt> - Redirect to a different host name
- def force_ssl_redirect(host = nil)
- unless request.ssl?
- redirect_options = {:protocol => 'https://', :status => :moved_permanently}
- redirect_options.merge!(:host => host) if host
- redirect_options.merge!(:params => request.query_parameters)
- flash.keep if respond_to?(:flash)
- redirect_to redirect_options
- end
- end
- end
- end
- module ActionController
- module Head
- extend ActiveSupport::Concern
- # Return 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
- #
- # 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
- def head(status, options = {})
- options, status = status, nil if status.is_a?(Hash)
- status ||= options.delete(:status) || :ok
- location = options.delete(:location)
- content_type = options.delete(:content_type)
- options.each do |key, value|
- headers[key.to_s.dasherize.split('-').each { |v| v[0] = v[0].chr.upcase }.join('-')] = value.to_s
- end
- self.status = status
- self.location = url_for(location) if location
- if include_content?(self.status)
- self.content_type = content_type || (Mime[formats.first] if formats)
- self.response.charset = false if self.response
- self.response_body = " "
- else
- headers.delete('Content-Type')
- headers.delete('Content-Length')
- self.response_body = ""
- end
- end
- private
- # :nodoc:
- def include_content?(status)
- case status
- when 100..199
- false
- when 204, 205, 304
- false
- else
- true
- end
- end
- end
- end
- module ActionController
- # 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.
- #
- # In previous versions of \Rails the controller will include a helper whose
- # name matches that of the controller, e.g., <tt>MyController</tt> will automatically
- # include <tt>MyHelper</tt>. To return old behavior set +config.action_controller.include_all_helpers+ to +false+.
- #
- # 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=" ")
- # time.blank? ? blank_message : time.to_s(format)
- # end
- # end
- #
- # FormattedTimeHelper can now be included in a controller, using the +helper+ class method:
- #
- # class EventsController < ActionController::Base
- # helper FormattedTimeHelper
- # def index
- # @events = Event.all
- # end
- # end
- #
- # Then, in any view rendered by <tt>EventController</tt>, the <tt>format_time</tt> method can be called:
- #
- # <% @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 Railhaws Training Workshop
- #
- module Helpers
- extend ActiveSupport::Concern
- class << self; attr_accessor :helpers_path; end
- include AbstractController::Helpers
- included do
- class_attribute :helpers_path, :include_all_helpers
- self.helpers_path ||= []
- self.include_all_helpers = true
- 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
- #
- # ==== 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 helpers methods from outside the view.
- def helpers
- @helper_proxy ||= ActionView::Base.new.extend(_helpers)
- end
- # Overwrite modules_for_helpers to accept :all as argument, which loads
- # all helpers in helpers_path.
- #
- # ==== Parameters
- # * <tt>args</tt> - A list of helpers
- #
- # ==== 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
- def all_helpers_from_path(path)
- helpers = Array(path).flat_map do |_path|
- extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
- names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
- names.sort!
- names
- end
- helpers.uniq!
- helpers
- end
- private
- # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt>
- def all_application_helpers
- all_helpers_from_path(helpers_path)
- end
- end
- end
- end
- module ActionController
- # Adds the ability to prevent public methods on a controller to be called as actions.
- module HideActions
- extend ActiveSupport::Concern
- included do
- class_attribute :hidden_actions
- self.hidden_actions = Set.new.freeze
- end
- private
- # Overrides AbstractController::Base#action_method? to return false if the
- # action name is in the list of hidden actions.
- def method_for_action(action_name)
- self.class.visible_action?(action_name) && super
- end
- module ClassMethods
- # Sets all of the actions passed in as hidden actions.
- #
- # ==== Parameters
- # * <tt>args</tt> - A list of actions
- def hide_action(*args)
- self.hidden_actions = hidden_actions.dup.merge(args.map(&:to_s)).freeze
- end
- def visible_action?(action_name)
- action_methods.include?(action_name)
- end
- # Overrides AbstractController::Base#action_methods to remove any methods
- # that are listed as hidden methods.
- def action_methods
- @action_methods ||= Set.new(super.reject { |name| hidden_actions.include?(name) }).freeze
- end
- end
- end
- end
- require 'base64'
- module ActionController
- # Makes it dead easy to do HTTP Basic, Digest and Token authentication.
- module HttpAuthentication
- # Makes it dead easy to do HTTP \Basic authentication.
- #
- # === Simple \Basic example
- #
- # class PostsController < ApplicationController
- # http_basic_authenticate_with name: "dhh", password: "secret", except: :index
- #
- # def index
- # render text: "Everyone can see me!"
- # end
- #
- # def edit
- # render text: "I'm only accessible if you know the password"
- # end
- # end
- #
- # === Advanced \Basic example
- #
- # Here is a more advanced \Basic example where only Atom feeds and the XML API is protected by HTTP authentication,
- # the regular HTML interface is protected by a session approach:
- #
- # class ApplicationController < ActionController::Base
- # before_action :set_account, :authenticate
- #
- # protected
- # def set_account
- # @account = Account.find_by_url_name(request.subdomains.first)
- # end
- #
- # def authenticate
- # case request.format
- # when Mime::XML, Mime::ATOM
- # if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
- # @current_user = user
- # 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
- # end
- # end
- # end
- # end
- #
- # In your integration tests, you can do something like this:
- #
- # def test_access_granted_from_xml
- # get(
- # "/notes/1.xml", nil,
- # 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
- # )
- #
- # assert_equal 200, status
- # end
- module Basic
- extend self
- module ControllerMethods
- extend ActiveSupport::Concern
- module ClassMethods
- def http_basic_authenticate_with(options = {})
- before_action(options.except(:name, :password, :realm)) do
- authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
- name == options[:name] && password == options[:password]
- end
- end
- end
- end
- def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
- authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
- end
- def authenticate_with_http_basic(&login_procedure)
- HttpAuthentication::Basic.authenticate(request, &login_procedure)
- end
- def request_http_basic_authentication(realm = "Application")
- HttpAuthentication::Basic.authentication_request(self, realm)
- end
- end
- def authenticate(request, &login_procedure)
- unless request.authorization.blank?
- login_procedure.call(*user_name_and_password(request))
- end
- end
- def user_name_and_password(request)
- decode_credentials(request).split(/:/, 2)
- end
- def decode_credentials(request)
- ::Base64.decode64(request.authorization.split(' ', 2).last || '')
- end
- def encode_credentials(user_name, password)
- "Basic #{::Base64.strict_encode64("#{user_name}:#{password}")}"
- end
- def authentication_request(controller, realm)
- controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
- controller.response_body = "HTTP Basic: Access denied.\n"
- controller.status = 401
- end
- end
- # Makes it dead easy to do HTTP \Digest authentication.
- #
- # === Simple \Digest example
- #
- # require 'digest/md5'
- # class PostsController < ApplicationController
- # REALM = "SuperSecret"
- # USERS = {"dhh" => "secret", #plain text password
- # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
- #
- # before_action :authenticate, except: [:index]
- #
- # def index
- # render text: "Everyone can see me!"
- # end
- #
- # def edit
- # render text: "I'm only accessible if you know the password"
- # end
- #
- # private
- # def authenticate
- # authenticate_or_request_with_http_digest(REALM) do |username|
- # USERS[username]
- # end
- # end
- # end
- #
- # === Notes
- #
- # 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
- def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure)
- authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm)
- end
- # Authenticate with HTTP Digest, returns true or false
- def authenticate_with_http_digest(realm = "Application", &password_procedure)
- HttpAuthentication::Digest.authenticate(request, realm, &password_procedure)
- end
- # Render output including the HTTP Digest authentication header
- def request_http_digest_authentication(realm = "Application", message = nil)
- HttpAuthentication::Digest.authentication_request(self, realm, message)
- end
- end
- # Returns false on a valid response, true otherwise
- def authenticate(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.
- def validate_digest_response(request, realm, &password_procedure)
- secret_key = secret_token(request)
- credentials = decode_credentials_header(request)
- valid_nonce = validate_nonce(secret_key, request, credentials[:nonce])
- if valid_nonce && realm == credentials[:realm] && opaque(secret_key) == credentials[:opaque]
- password = password_procedure.call(credentials[:username])
- return false unless password
- method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
- uri = credentials[:uri]
- [true, false].any? do |trailing_question_mark|
- [true, false].any? do |password_is_ha1|
- _uri = trailing_question_mark ? uri + "?" : uri
- expected = expected_response(method, _uri, credentials, password, password_is_ha1)
- expected == credentials[:response]
- end
- end
- 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.
- def expected_response(http_method, uri, credentials, password, password_is_ha1=true)
- ha1 = password_is_ha1 ? password : ha1(credentials, password)
- ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':'))
- ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':'))
- end
- def ha1(credentials, password)
- ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':'))
- end
- def encode_credentials(http_method, credentials, password, password_is_ha1)
- credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1)
- "Digest " + credentials.sort_by {|x| x[0].to_s }.map {|v| "#{v[0]}='#{v[1]}'" }.join(', ')
- end
- def decode_credentials_header(request)
- decode_credentials(request.authorization)
- end
- def decode_credentials(header)
- ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, '').split(',').map do |pair|
- key, value = pair.split('=', 2)
- [key.strip, value.to_s.gsub(/^"|"$/,'').delete('\'')]
- end]
- end
- def authentication_header(controller, realm)
- secret_key = secret_token(controller.request)
- nonce = self.nonce(secret_key)
- opaque = opaque(secret_key)
- controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}")
- end
- def authentication_request(controller, realm, message = nil)
- message ||= "HTTP Digest: Access denied.\n"
- authentication_header(controller, realm)
- controller.response_body = message
- controller.status = 401
- end
- def secret_token(request)
- key_generator = request.env["action_dispatch.key_generator"]
- http_auth_salt = request.env["action_dispatch.http_auth_salt"]
- key_generator.generate_key(http_auth_salt)
- end
- # Uses an MD5 digest based on time to generate a value to be used only once.
- #
- # A server-specified data string which should be uniquely generated each time a 401 response is made.
- # It is recommended that this string be base64 or hexadecimal data.
- # Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed.
- #
- # The contents of the nonce are implementation dependent.
- # The quality of the implementation depends on a good choice.
- # A nonce might, for example, be constructed as the base 64 encoding of
- #
- # time-stamp H(time-stamp ":" ETag ":" private-key)
- #
- # where time-stamp is a server-generated time or other non-repeating value,
- # ETag is the value of the HTTP ETag header associated with the requested entity,
- # and private-key is data known only to the server.
- # With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and
- # reject the request if it did not match the nonce from that header or
- # if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity.
- # The inclusion of the ETag prevents a replay request for an updated version of the resource.
- # (Note: including the IP address of the client in the nonce would appear to offer the server the ability
- # to limit the reuse of the nonce to the same client that originally got it.
- # However, that would break proxy farms, where requests from a single user often go through different proxies in the farm.
- # Also, IP address spoofing is not that hard.)
- #
- # An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
- # protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
- # POST, 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.
- def nonce(secret_key, time = Time.now)
- t = time.to_i
- hashed = [t, secret_key]
- digest = ::Digest::MD5.hexdigest(hashed.join(":"))
- ::Base64.strict_encode64("#{t}:#{digest}")
- end
- # Might want a shorter timeout depending on whether the request
- # is a PATCH, PUT, or POST, and if client is 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 user again for their
- # username and password.
- def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
- t = ::Base64.decode64(value).split(":").first.to_i
- nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
- end
- # Opaque based on random generation - but changing each request?
- def opaque(secret_key)
- ::Digest::MD5.hexdigest(secret_key)
- end
- end
- # Makes it dead easy to do HTTP Token authentication.
- #
- # Simple Token example:
- #
- # class PostsController < ApplicationController
- # TOKEN = "secret"
- #
- # before_action :authenticate, except: [ :index ]
- #
- # def index
- # render text: "Everyone can see me!"
- # end
- #
- # def edit
- # render text: "I'm only accessible if you know the password"
- # end
- #
- # private
- # def authenticate
- # authenticate_or_request_with_http_token do |token, options|
- # token == TOKEN
- # end
- # end
- # end
- #
- #
- # Here is a more advanced Token example where only Atom feeds and the XML API is protected by HTTP token authentication,
- # the regular HTML interface is protected by a session approach:
- #
- # class ApplicationController < ActionController::Base
- # before_action :set_account, :authenticate
- #
- # protected
- # def set_account
- # @account = Account.find_by_url_name(request.subdomains.first)
- # end
- #
- # def authenticate
- # case request.format
- # when Mime::XML, Mime::ATOM
- # 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
- # get(
- # "/notes/1.xml", nil,
- # 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
- # )
- #
- # 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
- # authenticate, try this rule in your Apache setup:
- #
- # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
- module Token
- TOKEN_REGEX = /^Token /
- AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
- extend self
- module ControllerMethods
- def authenticate_or_request_with_http_token(realm = "Application", &login_procedure)
- authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm)
- end
- def authenticate_with_http_token(&login_procedure)
- Token.authenticate(self, &login_procedure)
- end
- def request_http_token_authentication(realm = "Application")
- Token.authentication_request(self, realm)
- end
- end
- # If token Authorization header is present, call the login
- # procedure with the present token and options.
- #
- # [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| ... }
- #
- # Returns the return value of <tt>login_procedure</tt> if a
- # token is found. Returns <tt>nil</tt> if no token is found.
- def authenticate(controller, &login_procedure)
- token, options = token_and_options(controller.request)
- unless token.blank?
- login_procedure.call(token, options)
- end
- end
- # Parses the token and options out of the token authorization header. If
- # the header looks like this:
- # Authorization: Token token="abc", nonce="def"
- # Then the returned token is "abc", and the options is {nonce: "def"}
- #
- # request - ActionDispatch::Request instance with the current headers.
- #
- # Returns an Array of [String, Hash] if a token is present.
- # Returns nil if no token is found.
- def token_and_options(request)
- authorization_request = request.authorization.to_s
- if authorization_request[TOKEN_REGEX]
- params = token_params_from authorization_request
- [params.shift.last, Hash[params].with_indifferent_access]
- end
- end
- 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
- def params_array_from(raw_params)
- raw_params.map { |param| param.split %r/=(.+)?/ }
- end
- # This removes the `"` characters wrapping the value.
- def rewrite_param_values(array_params)
- array_params.each { |param| param.last.gsub! %r/^"|"$/, '' }
- end
- # 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)
- auth.sub(TOKEN_REGEX, '').split(/"\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
- end
- # Encodes the given token and options into an Authorization header value.
- #
- # token - String token.
- # options - optional Hash of the options.
- #
- # Returns String.
- def encode_credentials(token, options = {})
- values = ["token=#{token.to_s.inspect}"] + options.map do |key, value|
- "#{key}=#{value.to_s.inspect}"
- end
- "Token #{values * ", "}"
- end
- # Sets a WWW-Authenticate to let the client know a token is desired.
- #
- # controller - ActionController::Base instance for the outgoing response.
- # realm - String realm to use in the header.
- #
- # Returns nothing.
- def authentication_request(controller, realm)
- controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.gsub(/"/, "")}")
- controller.__send__ :render, :text => "HTTP Token: Access denied.\n", :status => :unauthorized
- end
- end
- end
- end
- module ActionController
- module ImplicitRender
- def send_action(method, *args)
- ret = super
- default_render unless performed?
- ret
- end
- def default_render(*args)
- render(*args)
- end
- def method_for_action(action_name)
- super || if template_exists?(action_name.to_s, _prefixes)
- "default_render"
- end
- end
- end
- end
- require 'benchmark'
- require 'abstract_controller/logger'
- module ActionController
- # 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
- extend ActiveSupport::Concern
- include AbstractController::Logger
- include ActionController::RackDelegation
- attr_internal :view_runtime
- def process_action(*args)
- raw_payload = {
- :controller => self.class.name,
- :action => self.action_name,
- :params => request.filtered_parameters,
- :format => request.format.try(:ref),
- :method => request.method,
- :path => (request.fullpath rescue "unknown")
- }
- ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
- ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
- result = super
- payload[:status] = response.status
- append_info_to_payload(payload)
- result
- end
- end
- def render(*args)
- render_output = nil
- self.view_runtime = cleanup_view_runtime do
- Benchmark.ms { render_output = super }
- end
- render_output
- end
- def send_file(path, options={})
- ActiveSupport::Notifications.instrument("send_file.action_controller",
- options.merge(:path => path)) do
- super
- end
- end
- def send_data(data, options = {})
- ActiveSupport::Notifications.instrument("send_data.action_controller", options) do
- super
- end
- end
- def redirect_to(*args)
- ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload|
- result = super
- payload[:status] = response.status
- payload[:location] = response.filtered_location
- result
- end
- end
- private
- # A hook invoked everytime a before callback is halted.
- def halted_callback_hook(filter)
- ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter)
- end
- # A hook which allows you to clean up any time taken into account in
- # views wrongly, like database querying time.
- #
- # def cleanup_view_runtime
- # super - time_taken_in_something_expensive
- # end
- #
- # :api: plugin
- def cleanup_view_runtime #:nodoc:
- yield
- end
- # Every time after an action is processed, this method is invoked
- # with the payload, so you can add more information.
- # :api: plugin
- def append_info_to_payload(payload) #:nodoc:
- 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.
- # :api: plugin
- def log_process_action(payload) #:nodoc:
- messages, view_runtime = [], payload[:view_runtime]
- messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
- messages
- end
- end
- end
- end
- require 'action_dispatch/http/response'
- require 'delegate'
- module ActionController
- # Mix this module in to 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
- #
- # def stream
- # response.headers['Content-Type'] = 'text/event-stream'
- # 100.times {
- # response.stream.write "hello world\n"
- # sleep 1
- # }
- # response.stream.close
- # end
- # end
- #
- # There are a few caveats with this use. 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
- # 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).
- module Live
- class Buffer < ActionDispatch::Response::Buffer #:nodoc:
- def initialize(response)
- super(response, SizedQueue.new(10))
- end
- def write(string)
- unless @response.committed?
- @response.headers["Cache-Control"] = "no-cache"
- @response.headers.delete "Content-Length"
- end
- super
- end
- def each
- while str = @buf.pop
- yield str
- end
- end
- def close
- super
- @buf.push nil
- end
- end
- class Response < ActionDispatch::Response #:nodoc: all
- class Header < DelegateClass(Hash)
- def initialize(response, header)
- @response = response
- super(header)
- end
- def []=(k,v)
- if @response.committed?
- raise ActionDispatch::IllegalStateError, 'header already sent'
- end
- super
- end
- def merge(other)
- self.class.new @response, __getobj__.merge(other)
- end
- def to_hash
- __getobj__.dup
- end
- end
- def commit!
- headers.freeze
- super
- end
- private
- def build_buffer(response, body)
- buf = Live::Buffer.new response
- body.each { |part| buf.write part }
- buf
- end
- def merge_default_headers(original, default)
- Header.new self, super
- end
- end
- def process(name)
- t1 = Thread.current
- locals = t1.keys.map { |key| [key, t1[key]] }
- # 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
- Thread.new {
- t2 = Thread.current
- t2.abort_on_exception = true
- # 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 }
- begin
- super(name)
- ensure
- @_response.commit!
- end
- }
- @_response.await_commit
- end
- def response_body=(body)
- super
- response.stream.close if response
- end
- def set_response!(request)
- if request.env["HTTP_VERSION"] == "HTTP/1.0"
- super
- else
- @_response = Live::Response.new
- @_response.request = request
- end
- end
- end
- end
- require 'active_support/core_ext/array/extract_options'
- require 'abstract_controller/collector'
- module ActionController #:nodoc:
- module MimeResponds
- extend ActiveSupport::Concern
- included do
- class_attribute :responder, :mimes_for_respond_to
- self.responder = ActionController::Responder
- clear_respond_to
- end
- module ClassMethods
- # Defines mime types that are rendered by default when invoking
- # <tt>respond_with</tt>.
- #
- # respond_to :html, :xml, :json
- #
- # Specifies that all actions in the controller respond to requests
- # for <tt>:html</tt>, <tt>:xml</tt> and <tt>:json</tt>.
- #
- # To specify on per-action basis, use <tt>:only</tt> and
- # <tt>:except</tt> with an array of actions or a single action:
- #
- # respond_to :html
- # respond_to :xml, :json, except: [ :edit ]
- #
- # This specifies that all actions respond to <tt>:html</tt>
- # and all actions except <tt>:edit</tt> respond to <tt>:xml</tt> and
- # <tt>:json</tt>.
- #
- # respond_to :json, only: :create
- #
- # This specifies that the <tt>:create</tt> action and no other responds
- # to <tt>:json</tt>.
- def respond_to(*mimes)
- options = mimes.extract_options!
- only_actions = Array(options.delete(:only)).map(&:to_s)
- except_actions = Array(options.delete(:except)).map(&:to_s)
- new = mimes_for_respond_to.dup
- mimes.each do |mime|
- mime = mime.to_sym
- new[mime] = {}
- new[mime][:only] = only_actions unless only_actions.empty?
- new[mime][:except] = except_actions unless except_actions.empty?
- end
- self.mimes_for_respond_to = new.freeze
- end
- # Clear all mime types in <tt>respond_to</tt>.
- #
- def clear_respond_to
- self.mimes_for_respond_to = Hash.new.freeze
- end
- end
- # 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
- #
- # Here's the same action, with web-service support baked in:
- #
- # def index
- # @people = Person.all
- #
- # respond_to do |format|
- # format.html
- # format.xml { render xml: @people }
- # end
- # end
- #
- # What that says is, "if the client wants HTML 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:
- #
- # def create
- # @company = Company.find_or_create_by(name: params[:company][:name])
- # @person = @company.people.create(params[:person])
- #
- # 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])
- #
- # respond_to do |format|
- # format.html { redirect_to(person_list_url) }
- # format.js
- # format.xml { render xml: @person.to_xml(include: @company) }
- # 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:
- #
- # <person>
- # <id>...</id>
- # ...
- # <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])
- #
- # 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]=...&...
- #
- # And, like this (xml-encoded):
- #
- # <person>
- # <name>...</name>
- # <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.
- #
- # 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.
- #
- # Mime::Type.register "image/jpg", :jpg
- #
- # Respond to also allows you to specify a common block for different formats by using any:
- #
- # def index
- # @people = Person.all
- #
- # respond_to do |format|
- # format.html
- # format.any(:xml, :json) { render request.format.to_sym => @people }
- # end
- # end
- #
- # In the example above, if the format is xml, it will render:
- #
- # render xml: @people
- #
- # Or if the format is json:
- #
- # render json: @people
- #
- # Since this is a common pattern, you can use the class method respond_to
- # with the respond_with method to have the same results:
- #
- # class PeopleController < ApplicationController
- # respond_to :html, :xml, :json
- #
- # def index
- # @people = Person.all
- # respond_with(@people)
- # end
- # end
- #
- # Be sure to check the documentation of +respond_with+ and
- # <tt>ActionController::MimeResponds.respond_to</tt> for more examples.
- def respond_to(*mimes, &block)
- raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
- if collector = retrieve_collector_from_mimes(mimes, &block)
- response = collector.response
- response ? response.call : render({})
- end
- end
- # For a given controller action, respond_with generates an appropriate
- # response based on the mime-type requested by the client.
- #
- # If the method is called with just a resource, as in this example -
- #
- # class PeopleController < ApplicationController
- # respond_to :html, :xml, :json
- #
- # def index
- # @people = Person.all
- # respond_with @people
- # end
- # end
- #
- # then the mime-type of the response is typically selected based on the
- # request's Accept header and the set of available formats declared
- # by previous calls to the controller's class method +respond_to+. Alternatively
- # the mime-type can be selected by explicitly setting <tt>request.format</tt> in
- # the controller.
- #
- # If an acceptable format is not identified, the application returns a
- # '406 - not acceptable' status. Otherwise, the default response is to render
- # a template named after the current action and the selected format,
- # e.g. <tt>index.html.erb</tt>. If no template is available, the behavior
- # depends on the selected format:
- #
- # * for an html response - if the request method is +get+, an exception
- # is raised but for other requests such as +post+ the response
- # depends on whether the resource has any validation errors (i.e.
- # assuming that an attempt has been made to save the resource,
- # e.g. by a +create+ action) -
- # 1. If there are no errors, i.e. the resource
- # was saved successfully, the response +redirect+'s to the resource
- # i.e. its +show+ action.
- # 2. If there are validation errors, the response
- # renders a default action, which is <tt>:new</tt> for a
- # +post+ request or <tt>:edit</tt> for +patch+ or +put+.
- # Thus an example like this -
- #
- # respond_to :html, :xml
- #
- # def create
- # @user = User.new(params[:user])
- # flash[:notice] = 'User was successfully created.' if @user.save
- # respond_with(@user)
- # end
- #
- # is equivalent, in the absence of <tt>create.html.erb</tt>, to -
- #
- # def create
- # @user = User.new(params[:user])
- # respond_to do |format|
- # if @user.save
- # flash[:notice] = 'User was successfully created.'
- # format.html { redirect_to(@user) }
- # format.xml { render xml: @user }
- # else
- # format.html { render action: "new" }
- # format.xml { render xml: @user }
- # end
- # end
- # end
- #
- # * for a javascript request - if the template isn't found, an exception is
- # raised.
- # * for other requests - i.e. data formats such as xml, json, csv etc, if
- # the resource passed to +respond_with+ responds to <code>to_<format></code>,
- # the method attempts to render the resource in the requested format
- # directly, e.g. for an xml request, the response is equivalent to calling
- # <code>render xml: resource</code>.
- #
- # === Nested resources
- #
- # As outlined above, the +resources+ argument passed to +respond_with+
- # can play two roles. It can be used to generate the redirect url
- # for successful html requests (e.g. for +create+ actions when
- # no template exists), while for formats other than html and javascript
- # it is the object that gets rendered, by being converted directly to the
- # required format (again assuming no template exists).
- #
- # For redirecting successful html requests, +respond_with+ also supports
- # the use of nested resources, which are supplied in the same way as
- # in <code>form_for</code> and <code>polymorphic_url</code>. For example -
- #
- # def create
- # @project = Project.find(params[:project_id])
- # @task = @project.comments.build(params[:task])
- # flash[:notice] = 'Task was successfully created.' if @task.save
- # respond_with(@project, @task)
- # end
- #
- # This would cause +respond_with+ to redirect to <code>project_task_url</code>
- # instead of <code>task_url</code>. For request formats other than html or
- # javascript, if multiple resources are passed in this way, it is the last
- # one specified that is rendered.
- #
- # === Customizing response behavior
- #
- # Like +respond_to+, +respond_with+ may also be called with a block that
- # can be used to overwrite any of the default responses, e.g. -
- #
- # def create
- # @user = User.new(params[:user])
- # flash[:notice] = "User was successfully created." if @user.save
- #
- # respond_with(@user) do |format|
- # format.html { render }
- # end
- # end
- #
- # The argument passed to the block is an ActionController::MimeResponds::Collector
- # object which stores the responses for the formats defined within the
- # block. Note that formats with responses defined explicitly in this way
- # do not have to first be declared using the class method +respond_to+.
- #
- # Also, a hash passed to +respond_with+ immediately after the specified
- # resource(s) is interpreted as a set of options relevant to all
- # formats. Any option accepted by +render+ can be used, e.g.
- # respond_with @people, status: 200
- # However, note that these options are ignored after an unsuccessful attempt
- # to save a resource, e.g. when automatically rendering <tt>:new</tt>
- # after a post request.
- #
- # Two additional options are relevant specifically to +respond_with+ -
- # 1. <tt>:location</tt> - overwrites the default redirect location used after
- # a successful html +post+ request.
- # 2. <tt>:action</tt> - overwrites the default render action used after an
- # unsuccessful html +post+ request.
- def respond_with(*resources, &block)
- raise "In order to use respond_with, first you need to declare the formats your " \
- "controller responds to in the class level" if self.class.mimes_for_respond_to.empty?
- if collector = retrieve_collector_from_mimes(&block)
- options = resources.size == 1 ? {} : resources.extract_options!
- options[:default_response] = collector.response
- (options.delete(:responder) || self.class.responder).call(self, resources, options)
- end
- end
- protected
- # Collect mimes declared in the class method respond_to valid for the
- # current action.
- def collect_mimes_from_class_level #:nodoc:
- action = action_name.to_s
- self.class.mimes_for_respond_to.keys.select do |mime|
- config = self.class.mimes_for_respond_to[mime]
- if config[:except]
- !config[:except].include?(action)
- elsif config[:only]
- config[:only].include?(action)
- else
- true
- end
- end
- end
- # Returns a Collector object containing the appropriate mime-type response
- # for the current request, based on the available responses defined by a block.
- # In typical usage this is the block passed to +respond_with+ or +respond_to+.
- #
- # Sends :not_acceptable to the client and returns nil if no suitable format
- # is available.
- def retrieve_collector_from_mimes(mimes=nil, &block) #:nodoc:
- mimes ||= collect_mimes_from_class_level
- collector = Collector.new(mimes)
- block.call(collector) if block_given?
- format = collector.negotiate_format(request)
- if format
- self.content_type ||= format.to_s
- lookup_context.formats = [format.to_sym]
- lookup_context.rendered_format = lookup_context.formats.first
- collector
- else
- raise ActionController::UnknownFormat
- end
- end
- # 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_with+ and +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
- #
- # 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
- # request, with this response then being accessible by calling #response.
- class Collector
- include AbstractController::Collector
- attr_accessor :order, :format
- def initialize(mimes)
- @order, @responses = [], {}
- mimes.each { |mime| send(mime) }
- end
- def any(*args, &block)
- if args.any?
- args.each { |type| send(type, &block) }
- else
- custom(Mime::ALL, &block)
- end
- end
- alias :all :any
- def custom(mime_type, &block)
- mime_type = Mime::Type.lookup(mime_type.to_s) unless mime_type.is_a?(Mime::Type)
- @order << mime_type
- @responses[mime_type] ||= block
- end
- def response
- @responses[format] || @responses[Mime::ALL]
- end
- def negotiate_format(request)
- @format = request.negotiate_mime(order)
- end
- end
- end
- end
- require 'active_support/core_ext/hash/slice'
- require 'active_support/core_ext/hash/except'
- require 'active_support/core_ext/module/anonymous'
- require 'active_support/core_ext/struct'
- require 'action_dispatch/http/mime_type'
- module ActionController
- # Wraps the parameters hash into a nested hash. This will allow clients to submit
- # POST requests without having to specify any root elements.
- #
- # This functionality is enabled in +config/initializers/wrap_parameters.rb+
- # and can be customized. If you are upgrading to \Rails 3.1, this file will
- # need to be created for the functionality to be enabled.
- #
- # You could also turn it on per controller by setting the format array to
- # a non-empty array:
- #
- # class UsersController < ApplicationController
- # wrap_parameters format: [:json, :xml]
- # end
- #
- # If you enable +ParamsWrapper+ for +:json+ format, instead of having to
- # send JSON parameters like this:
- #
- # {"user": {"name": "Konata"}}
- #
- # You can send parameters like this:
- #
- # {"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:
- #
- # {"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:
- #
- # class UsersController < ApplicationController
- # wrap_parameters :person, include: [:username, :password]
- # end
- #
- # On ActiveRecord models with no +:include+ or +:exclude+ option set,
- # it will only wrap the parameters returned by the class method
- # <tt>attribute_names</tt>.
- #
- # 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:
- #
- # class UsersController < ApplicationController
- # wrap_parameters Person
- # end
- #
- # 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:
- #
- # class Admin::UsersController < ApplicationController
- # end
- #
- # will try to check if <tt>Admin::User</tt> or +User+ model exists, and use it to
- # determine the wrapper key respectively. If both models don't exist,
- # it will then fallback to use +user+ as the key.
- module ParamsWrapper
- extend ActiveSupport::Concern
- EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8)
- require 'mutex_m'
- class Options < Struct.new(:name, :format, :include, :exclude, :klass, :model) # :nodoc:
- include Mutex_m
- def self.from_hash(hash)
- name = hash[:name]
- format = Array(hash[:format])
- include = hash[:include] && Array(hash[:include]).collect(&:to_s)
- exclude = hash[:exclude] && Array(hash[:exclude]).collect(&:to_s)
- new name, format, include, exclude, nil, nil
- end
- def initialize(name, format, include, exclude, klass, model) # nodoc
- super
- @include_set = include
- @name_set = name
- end
- def model
- super || synchronize { super || self.model = _default_wrap_model }
- end
- def include
- return super if @include_set
- m = model
- synchronize do
- return super if @include_set
- @include_set = true
- unless super || exclude
- if m.respond_to?(:attribute_names) && m.attribute_names.any?
- self.include = m.attribute_names
- end
- end
- end
- end
- def name
- return super if @name_set
- m = model
- synchronize do
- return super if @name_set
- @name_set = true
- unless super || klass.anonymous?
- self.name = m ? m.to_s.demodulize.underscore :
- klass.controller_name.singularize
- end
- end
- 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 singularize 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.
- def _default_wrap_model #:nodoc:
- return nil if klass.anonymous?
- model_name = klass.name.sub(/Controller$/, '').classify
- begin
- if model_klass = model_name.safe_constantize
- model_klass
- else
- namespaces = model_name.split("::")
- namespaces.delete_at(-2)
- break if namespaces.last == model_name
- model_name = namespaces.join("::")
- end
- end until model_klass
- model_klass
- end
- end
- included do
- class_attribute :_wrapper_options
- self._wrapper_options = Options.from_hash(format: [])
- end
- module ClassMethods
- 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.
- #
- # ==== 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 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 false
- # # disables parameters wrapping for this controller altogether.
- #
- # ==== 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
- case name_or_model_or_options
- when Hash
- options = name_or_model_or_options
- when false
- options = options.merge(:format => [])
- when Symbol, String
- options = options.merge(:name => name_or_model_or_options)
- else
- model = name_or_model_or_options
- end
- opts = Options.from_hash _wrapper_options.to_h.slice(:format).merge(options)
- opts.model = model
- opts.klass = self
- self._wrapper_options = opts
- end
- # Sets the default wrapper key or model which will be used to determine
- # wrapper key and attribute names. Will be called automatically when the
- # module is inherited.
- def inherited(klass)
- if klass._wrapper_options.format.any?
- params = klass._wrapper_options.dup
- params.klass = klass
- klass._wrapper_options = params
- end
- super
- end
- end
- # Performs parameters wrapping upon the request. Will be called automatically
- # by the metal call stack.
- def process_action(*args)
- if _wrapper_enabled?
- wrapped_hash = _wrap_parameters request.request_parameters
- wrapped_keys = request.request_parameters.keys
- wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
- # This will make the wrapped hash accessible from controller and view
- request.parameters.merge! wrapped_hash
- request.request_parameters.merge! wrapped_hash
- # This will make the wrapped hash displayed in the log file
- request.filtered_parameters.merge! wrapped_filtered_hash
- end
- super
- end
- private
- # Returns the wrapper key which will use to stored wrapped parameters.
- def _wrapper_key
- _wrapper_options.name
- end
- # Returns the list of enabled formats.
- def _wrapper_formats
- _wrapper_options.format
- end
- # Returns the list of parameters which will be selected for wrapped.
- def _wrap_parameters(parameters)
- value = if include_only = _wrapper_options.include
- parameters.slice(*include_only)
- else
- exclude = _wrapper_options.exclude || []
- parameters.except(*(exclude + EXCLUDE_PARAMETERS))
- end
- { _wrapper_key => value }
- end
- # Checks if we should perform parameters wrapping.
- def _wrapper_enabled?
- ref = request.content_mime_type.try(:ref)
- _wrapper_formats.include?(ref) && _wrapper_key && !request.request_parameters[_wrapper_key]
- end
- end
- end
- require 'action_dispatch/http/request'
- require 'action_dispatch/http/response'
- module ActionController
- module RackDelegation
- extend ActiveSupport::Concern
- delegate :headers, :status=, :location=, :content_type=,
- :status, :location, :content_type, :to => "@_response"
- def dispatch(action, request)
- set_response!(request)
- super(action, request)
- end
- def response_body=(body)
- response.body = body if response
- super
- end
- def reset_session
- @_request.reset_session
- end
- private
- def set_response!(request)
- @_response = ActionDispatch::Response.new
- @_response.request = request
- end
- end
- end
- module ActionController
- class RedirectBackError < AbstractController::Error #:nodoc:
- DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].'
- def initialize(message = nil)
- super(message || DEFAULT_MESSAGE)
- end
- end
- module Redirecting
- extend ActiveSupport::Concern
- include AbstractController::Logger
- include ActionController::RackDelegation
- include ActionController::UrlFor
- # Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
- #
- # * <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+.
- # * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.
- # Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt>
- #
- # redirect_to action: "show", id: 5
- # redirect_to post
- # redirect_to "http://www.rubyonrails.org"
- # redirect_to "/images/screenshot.jpg"
- # redirect_to articles_url
- # redirect_to :back
- # redirect_to proc { edit_post_url(@post) }
- #
- # The redirection happens as a "302 Found" header unless otherwise specified.
- #
- # 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}[http://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
- # followed using a GET request.
- #
- # 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.
- #
- # 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"
- #
- # When using <tt>redirect_to :back</tt>, if there is no referrer, ActionController::RedirectBackError will be raised. You may specify some fallback
- # behavior for this case by rescuing ActionController::RedirectBackError.
- def redirect_to(options = {}, response_status = {}) #:doc:
- raise ActionControllerError.new("Cannot redirect to nil!") unless options
- raise AbstractController::DoubleRenderError if response_body
- self.status = _extract_redirect_to_status(options, response_status)
- self.location = _compute_redirect_to_location(options)
- self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>"
- end
- private
- def _extract_redirect_to_status(options, response_status)
- if options.is_a?(Hash) && options.key?(:status)
- Rack::Utils.status_code(options.delete(:status))
- elsif response_status.key?(:status)
- Rack::Utils.status_code(response_status[:status])
- else
- 302
- end
- end
- def _compute_redirect_to_location(options)
- 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 (":").
- # The protocol relative scheme starts with a double slash "//"
- when %r{\A(\w[\w+.-]*:|//).*}
- options
- when String
- request.protocol + request.host_with_port + options
- when :back
- request.headers["Referer"] or raise RedirectBackError
- when Proc
- _compute_redirect_to_location options.call
- else
- url_for(options)
- end.delete("\0\r\n")
- end
- end
- end
- require 'set'
- module ActionController
- # See <tt>Renderers.add</tt>
- def self.add_renderer(key, &block)
- Renderers.add(key, &block)
- end
- module Renderers
- extend ActiveSupport::Concern
- included do
- class_attribute :_renderers
- self._renderers = Set.new.freeze
- end
- module ClassMethods
- def use_renderers(*args)
- renderers = _renderers + args
- self._renderers = renderers.freeze
- end
- alias use_renderer use_renderers
- end
- def render_to_body(options)
- _handle_render_options(options) || super
- end
- def _handle_render_options(options)
- _renderers.each do |name|
- if options.key?(name)
- _process_options(options)
- return send("_render_option_#{name}", options.delete(name), options)
- end
- end
- nil
- end
- # Hash of available renderers, mapping a renderer name to its proc.
- # Default keys are :json, :js, :xml.
- RENDERERS = Set.new
- # Adds a new renderer to call within controller actions.
- # A renderer is invoked by passing its name as an option to
- # <tt>AbstractController::Rendering#render</tt>. 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
- #
- # 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>.
- #
- # 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 }
- # }
- # end
- # To use renderers and their mime types in more concise ways, see
- # <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt> and
- # <tt>ActionController::MimeResponds#respond_with</tt>
- def self.add(key, &block)
- define_method("_render_option_#{key}", &block)
- RENDERERS << key.to_sym
- end
- module All
- extend ActiveSupport::Concern
- include Renderers
- included do
- self._renderers = RENDERERS
- end
- end
- add :json do |json, options|
- json = json.to_json(options) unless json.kind_of?(String)
- if options[:callback].present?
- self.content_type ||= Mime::JS
- "#{options[:callback]}(#{json})"
- else
- self.content_type ||= Mime::JSON
- json
- end
- end
- add :js do |js, options|
- self.content_type ||= Mime::JS
- js.respond_to?(:to_js) ? js.to_js(options) : js
- end
- add :xml do |xml, options|
- self.content_type ||= Mime::XML
- xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
- end
- end
- end
- module ActionController
- module Rendering
- extend ActiveSupport::Concern
- include AbstractController::Rendering
- # Before processing, set the request formats in current controller formats.
- def process_action(*) #:nodoc:
- self.formats = request.formats.map { |x| x.ref }
- super
- end
- # Check for double render errors and set the content_type after rendering.
- def render(*args) #:nodoc:
- raise ::AbstractController::DoubleRenderError if response_body
- super
- self.content_type ||= Mime[lookup_context.rendered_format].to_s
- response_body
- end
- # Overwrite render_to_string because body can now be set to a rack body.
- def render_to_string(*)
- if self.response_body = super
- string = ""
- response_body.each { |r| string << r }
- string
- end
- ensure
- self.response_body = nil
- end
- def render_to_body(*)
- super || " "
- end
- private
- # Normalize arguments by catching blocks and setting them on :update.
- def _normalize_args(action=nil, options={}, &blk) #:nodoc:
- options = super
- options[:update] = blk if block_given?
- options
- end
- # Normalize both text and status options.
- def _normalize_options(options) #:nodoc:
- if options.key?(:text) && options[:text].respond_to?(:to_text)
- options[:text] = options[:text].to_text
- end
- if options.delete(:nothing) || (options.key?(:text) && options[:text].nil?)
- options[:text] = " "
- end
- if options[:status]
- options[:status] = Rack::Utils.status_code(options[:status])
- end
- super
- end
- # Process controller specific options, as status, content-type and location.
- def _process_options(options) #:nodoc:
- status, content_type, location = options.values_at(:status, :content_type, :location)
- self.status = status if status
- self.content_type = content_type if content_type
- self.headers["Location"] = url_for(location) if location
- super
- end
- end
- end
- require 'rack/session/abstract/id'
- require 'action_controller/metal/exceptions'
- module ActionController #:nodoc:
- class InvalidAuthenticityToken < ActionControllerError #:nodoc:
- end
- # Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
- # by including a token in the rendered html for your application. This token is
- # stored as a random string in the session, to which an attacker does not have
- # access. When a request reaches your application, \Rails verifies the received
- # token with the token in the session. Only HTML and JavaScript requests are checked,
- # so this will not protect your XML API (presumably you'll have a different
- # authentication scheme there anyway). Also, GET requests are not protected as these
- # should be idempotent.
- #
- # It's important to remember that XML or JSON requests are also affected and if
- # you're building an API you'll need something like:
- #
- # class ApplicationController < ActionController::Base
- # protect_from_forgery
- # skip_before_action :verify_authenticity_token, if: :json_request?
- #
- # protected
- #
- # def json_request?
- # request.format.json?
- # end
- # end
- #
- # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
- # which checks the token and resets the session if it doesn't match what was expected.
- # A call to this method is generated for new \Rails applications by default.
- #
- # 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+.
- #
- # Learn more about CSRF attacks and securing your application in the
- # {Ruby on Rails Security Guide}[http://guides.rubyonrails.org/security.html].
- module RequestForgeryProtection
- extend ActiveSupport::Concern
- include AbstractController::Helpers
- 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.
- config_accessor :request_forgery_protection_token
- self.request_forgery_protection_token ||= :authenticity_token
- # 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?
- helper_method :form_authenticity_token
- helper_method :protect_against_forgery?
- end
- module ClassMethods
- # Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
- #
- # class FooController < ApplicationController
- # protect_from_forgery except: :index
- #
- # You can disable csrf protection on controller-by-controller basis:
- #
- # skip_before_action :verify_authenticity_token
- #
- # It can also be disabled for specific controller actions:
- #
- # skip_before_action :verify_authenticity_token, except: [:create]
- #
- # Valid Options:
- #
- # * <tt>:only/:except</tt> - Passed to the <tt>before_action</tt> call. Set which actions are verified.
- # * <tt>:with</tt> - Set the method to handle unverified request.
- #
- # Valid 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.
- def protect_from_forgery(options = {})
- include protection_method_module(options[:with] || :null_session)
- self.request_forgery_protection_token ||= :authenticity_token
- prepend_before_action :verify_authenticity_token, options
- end
- private
- def protection_method_module(name)
- ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify)
- rescue NameError
- raise ArgumentError, 'Invalid request forgery protection method, use :null_session, :exception, or :reset_session'
- end
- end
- module ProtectionMethods
- module NullSession
- protected
- # This is the method that defines the application behavior when a request is found to be unverified.
- def handle_unverified_request
- request.session = NullSessionHash.new(request.env)
- request.env['action_dispatch.request.flash_hash'] = nil
- request.env['rack.session.options'] = { skip: true }
- request.env['action_dispatch.cookies'] = NullCookieJar.build(request)
- end
- class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
- def initialize(env)
- super(nil, env)
- @data = {}
- @loaded = true
- end
- def exists?
- true
- end
- end
- class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
- def self.build(request)
- key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY]
- host = request.host
- secure = request.ssl?
- new(key_generator, host, secure, options_for_env({}))
- end
- def write(*)
- # nothing
- end
- end
- end
- module ResetSession
- protected
- def handle_unverified_request
- reset_session
- end
- end
- module Exception
- protected
- def handle_unverified_request
- raise ActionController::InvalidAuthenticityToken
- end
- end
- end
- protected
- # The actual before_action that is used. Modify this to change how you handle unverified requests.
- def verify_authenticity_token
- unless verified_request?
- logger.warn "Can't verify CSRF token authenticity" if logger
- handle_unverified_request
- end
- end
- # 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
- def verified_request?
- !protect_against_forgery? || request.get? || request.head? ||
- form_authenticity_token == params[request_forgery_protection_token] ||
- form_authenticity_token == request.headers['X-CSRF-Token']
- end
- # Sets the token value for the current session.
- def form_authenticity_token
- session[:_csrf_token] ||= SecureRandom.base64(32)
- end
- # The form's authenticity parameter. Override to provide your own.
- def form_authenticity_param
- params[request_forgery_protection_token]
- end
- def protect_against_forgery?
- allow_forgery_protection
- end
- end
- end
- module ActionController #:nodoc:
- # This module is responsible to provide `rescue_from` helpers
- # to controllers and configure when detailed exceptions must be
- # shown.
- module Rescue
- extend ActiveSupport::Concern
- include ActiveSupport::Rescuable
- def rescue_with_handler(exception)
- if (exception.respond_to?(:original_exception) &&
- (orig_exception = exception.original_exception) &&
- handler_for_rescue(orig_exception))
- exception = orig_exception
- end
- super(exception)
- end
- # 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 shows the detailed exception pages.
- def show_detailed_exceptions?
- false
- end
- private
- def process_action(*args)
- super
- rescue Exception => exception
- request.env['action_dispatch.show_detailed_exceptions'] ||= show_detailed_exceptions?
- rescue_with_handler(exception) || raise(exception)
- end
- end
- end
- require 'active_support/json'
- module ActionController #:nodoc:
- # Responsible for exposing a resource to different mime requests,
- # usually depending on the HTTP verb. The responder is triggered when
- # <code>respond_with</code> is called. The simplest case to study is a GET request:
- #
- # class PeopleController < ApplicationController
- # respond_to :html, :xml, :json
- #
- # def index
- # @people = Person.all
- # respond_with(@people)
- # end
- # end
- #
- # When a request comes in, for example for an XML response, three steps happen:
- #
- # 1) the responder searches for a template at people/index.xml;
- #
- # 2) if the template is not available, it will invoke <code>#to_xml</code> on the given resource;
- #
- # 3) if the responder does not <code>respond_to :to_xml</code>, call <code>#to_format</code> on it.
- #
- # === Builtin HTTP verb semantics
- #
- # The default \Rails responder holds semantics for each HTTP verb. Depending on the
- # content type, verb and the resource status, it will behave differently.
- #
- # Using \Rails default responder, a POST request for creating an object could
- # be written as:
- #
- # def create
- # @user = User.new(params[:user])
- # flash[:notice] = 'User was successfully created.' if @user.save
- # respond_with(@user)
- # end
- #
- # Which is exactly the same as:
- #
- # def create
- # @user = User.new(params[:user])
- #
- # respond_to do |format|
- # if @user.save
- # flash[:notice] = 'User was successfully created.'
- # format.html { redirect_to(@user) }
- # format.xml { render xml: @user, status: :created, location: @user }
- # else
- # format.html { render action: "new" }
- # format.xml { render xml: @user.errors, status: :unprocessable_entity }
- # end
- # end
- # end
- #
- # The same happens for PATCH/PUT and DELETE requests.
- #
- # === Nested resources
- #
- # You can supply nested resources as you do in <code>form_for</code> and <code>polymorphic_url</code>.
- # Consider the project has many tasks example. The create action for
- # TasksController would be like:
- #
- # def create
- # @project = Project.find(params[:project_id])
- # @task = @project.tasks.build(params[:task])
- # flash[:notice] = 'Task was successfully created.' if @task.save
- # respond_with(@project, @task)
- # end
- #
- # Giving several resources ensures that the responder will redirect to
- # <code>project_task_url</code> instead of <code>task_url</code>.
- #
- # Namespaced and singleton resources require a symbol to be given, as in
- # polymorphic urls. If a project has one manager which has many tasks, it
- # should be invoked as:
- #
- # respond_with(@project, :manager, @task)
- #
- # Note that if you give an array, it will be treated as a collection,
- # so the following is not equivalent:
- #
- # respond_with [@project, :manager, @task]
- #
- # === Custom options
- #
- # <code>respond_with</code> also allows you to pass options that are forwarded
- # to the underlying render call. Those options are only applied for success
- # scenarios. For instance, you can do the following in the create method above:
- #
- # def create
- # @project = Project.find(params[:project_id])
- # @task = @project.tasks.build(params[:task])
- # flash[:notice] = 'Task was successfully created.' if @task.save
- # respond_with(@project, @task, status: 201)
- # end
- #
- # This will return status 201 if the task was saved successfully. If not,
- # it will simply ignore the given options and return status 422 and the
- # resource errors. To customize the failure scenario, you can pass a
- # a block to <code>respond_with</code>:
- #
- # def create
- # @project = Project.find(params[:project_id])
- # @task = @project.tasks.build(params[:task])
- # respond_with(@project, @task, status: 201) do |format|
- # if @task.save
- # flash[:notice] = 'Task was successfully created.'
- # else
- # format.html { render "some_special_template" }
- # end
- # end
- # end
- #
- # Using <code>respond_with</code> with a block follows the same syntax as <code>respond_to</code>.
- class Responder
- attr_reader :controller, :request, :format, :resource, :resources, :options
- DEFAULT_ACTIONS_FOR_VERBS = {
- :post => :new,
- :patch => :edit,
- :put => :edit
- }
- def initialize(controller, resources, options={})
- @controller = controller
- @request = @controller.request
- @format = @controller.formats.first
- @resource = resources.last
- @resources = resources
- @options = options
- @action = options.delete(:action)
- @default_response = options.delete(:default_response)
- end
- delegate :head, :render, :redirect_to, :to => :controller
- delegate :get?, :post?, :patch?, :put?, :delete?, :to => :request
- # Undefine :to_json and :to_yaml since it's defined on Object
- undef_method(:to_json) if method_defined?(:to_json)
- undef_method(:to_yaml) if method_defined?(:to_yaml)
- # Initializes a new responder an invoke the proper format. If the format is
- # not defined, call to_format.
- #
- def self.call(*args)
- new(*args).respond
- end
- # Main entry point for responder responsible to dispatch to the proper format.
- #
- def respond
- method = "to_#{format}"
- respond_to?(method) ? send(method) : to_format
- end
- # HTML format does not render the resource, it always attempt to render a
- # template.
- #
- def to_html
- default_render
- rescue ActionView::MissingTemplate => e
- navigation_behavior(e)
- end
- # to_js simply tries to render a template. If no template is found, raises the error.
- def to_js
- default_render
- end
- # All other formats follow the procedure below. First we try to render a
- # template, if the template is not available, we verify if the resource
- # responds to :to_format and display it.
- #
- def to_format
- if get? || !has_errors? || response_overridden?
- default_render
- else
- display_errors
- end
- rescue ActionView::MissingTemplate => e
- api_behavior(e)
- end
- protected
- # This is the common behavior for formats associated with browsing, like :html, :iphone and so forth.
- def navigation_behavior(error)
- if get?
- raise error
- elsif has_errors? && default_action
- render :action => default_action
- else
- redirect_to navigation_location
- end
- end
- # This is the common behavior for formats associated with APIs, such as :xml and :json.
- def api_behavior(error)
- raise error unless resourceful?
- if get?
- display resource
- elsif post?
- display resource, :status => :created, :location => api_location
- else
- head :no_content
- end
- end
- # Checks whether the resource responds to the current format or not.
- #
- def resourceful?
- resource.respond_to?("to_#{format}")
- end
- # Returns the resource location by retrieving it from the options or
- # returning the resources array.
- #
- def resource_location
- options[:location] || resources
- end
- alias :navigation_location :resource_location
- alias :api_location :resource_location
- # If a response block was given, use it, otherwise call render on
- # controller.
- #
- def default_render
- if @default_response
- @default_response.call(options)
- else
- controller.default_render(options)
- end
- end
- # Display is just a shortcut to render a resource with the current format.
- #
- # display @user, status: :ok
- #
- # For XML requests it's equivalent to:
- #
- # render xml: @user, status: :ok
- #
- # Options sent by the user are also used:
- #
- # respond_with(@user, status: :created)
- # display(@user, status: :ok)
- #
- # Results in:
- #
- # render xml: @user, status: :created
- #
- def display(resource, given_options={})
- controller.render given_options.merge!(options).merge!(format => resource)
- end
- def display_errors
- controller.render format => resource_errors, :status => :unprocessable_entity
- end
- # Check whether the resource has errors.
- #
- def has_errors?
- resource.respond_to?(:errors) && !resource.errors.empty?
- end
- # By default, render the <code>:edit</code> action for HTML requests with errors, unless
- # the verb was POST.
- #
- def default_action
- @action ||= DEFAULT_ACTIONS_FOR_VERBS[request.request_method_symbol]
- end
- def resource_errors
- respond_to?("#{format}_resource_errors", true) ? send("#{format}_resource_errors") : resource.errors
- end
- def json_resource_errors
- {:errors => resource.errors}
- end
- def response_overridden?
- @default_response.present?
- end
- end
- end
- require 'rack/chunked'
- module ActionController #:nodoc:
- # Allows views to be streamed back to the client as they are rendered.
- #
- # The default way Rails renders views is 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 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, allowing JavaScripts and stylesheets to be loaded
- # earlier than usual.
- #
- # This approach was introduced in Rails 3.1 and is still improving. Several
- # Rack middlewares may not work and you need to be careful when streaming.
- # Those points are going to be addressed soon.
- #
- # In order to use streaming, you will need to use a Ruby version that
- # supports fibers (fibers are supported since version 1.9.2 of the main
- # Ruby implementation).
- #
- # Streaming can be added to a given template easily, all you need to do is
- # to pass the :stream option.
- #
- # class PostsController
- # def index
- # @posts = Post.all
- # render stream: true
- # end
- # end
- #
- # == 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.
- #
- # 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
- #
- # 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
- #
- # Notice that :stream only works with templates. Rendering :json
- # or :xml with :stream won't work.
- #
- # == 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.
- #
- # 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:
- #
- # <html>
- # <head><title><%= yield :title %></title></head>
- # <body><%= yield %></body>
- # </html>
- #
- # You would use +content_for+ in your template to specify the title:
- #
- # <%= content_for :title, "Main" %>
- # Hello
- #
- # And the final result would be:
- #
- # <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:
- #
- # <%= 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>
- #
- # 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 kills the purpose of streaming. For this reason Rails 3.1
- # introduces a new 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:
- #
- # <%= provide :title, "Main" %>
- # Hello
- # <%= content_for :title, " page" %>
- #
- # Giving:
- #
- # <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+.
- #
- # == 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.
- #
- # == 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, <tt>Rack::Bug</tt> won't work when streaming as it
- # needs to inject contents in the HTML body.
- #
- # Also <tt>Rack::Cache</tt> won't work with streaming as it does not support
- # streaming bodies yet. Whenever streaming Cache-Control is automatically
- # set to "no-cache".
- #
- # == 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.
- #
- # Currently, when an exception happens in development or production, Rails
- # will automatically stream to the client:
- #
- # "><script>window.location = "/500.html"</script></html>
- #
- # 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
- #
- # Not all web servers support streaming out-of-the-box. You need to check
- # the instructions for each of them.
- #
- # ==== Unicorn
- #
- # 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
- #
- # And use it on initialization:
- #
- # unicorn_rails --config-file unicorn.config.rb
- #
- # You may also want to configure other parameters like <tt>:tcp_nodelay</tt>.
- # Please check its documentation for more information: http://unicorn.bogomips.org/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.
- #
- # ==== Passenger
- #
- # To be described.
- #
- module Streaming
- extend ActiveSupport::Concern
- include AbstractController::Rendering
- protected
- # Set proper cache control and transfer encoding when streaming
- def _process_options(options) #:nodoc:
- super
- if options[:stream]
- if env["HTTP_VERSION"] == "HTTP/1.0"
- options.delete(:stream)
- else
- headers["Cache-Control"] ||= "no-cache"
- headers["Transfer-Encoding"] = "chunked"
- headers.delete("Content-Length")
- end
- end
- end
- # Call render_body if we are streaming instead of usual +render+.
- def _render_template(options) #:nodoc:
- if options.delete(:stream)
- Rack::Chunked::Body.new view_renderer.render_body(view_context, options)
- else
- super
- end
- end
- end
- end
- require 'active_support/core_ext/hash/indifferent_access'
- require 'active_support/core_ext/array/wrap'
- require 'active_support/rescuable'
- require 'action_dispatch/http/upload'
- module ActionController
- # Raised when a required parameter is missing.
- #
- # params = ActionController::Parameters.new(a: {})
- # params.fetch(:b)
- # # => ActionController::ParameterMissing: param not found: b
- # params.require(:a)
- # # => ActionController::ParameterMissing: param not found: a
- class ParameterMissing < KeyError
- attr_reader :param # :nodoc:
- def initialize(param) # :nodoc:
- @param = param
- super("param not found: #{param}")
- end
- end
- # Raised when a supplied parameter is not expected.
- #
- # params = ActionController::Parameters.new(a: "123", b: "456")
- # params.permit(:c)
- # # => ActionController::UnpermittedParameters: found unexpected keys: a, b
- class UnpermittedParameters < IndexError
- attr_reader :params # :nodoc:
- def initialize(params) # :nodoc:
- @params = params
- super("found unpermitted parameters: #{params.join(", ")}")
- end
- end
- # == Action Controller \Parameters
- #
- # Allows to choose which attributes should be whitelisted for mass updating
- # and thus prevent accidentally exposing that which shouldn’t be exposed.
- # Provides two methods for this purpose: #require and #permit. The former is
- # used to mark parameters as required. The latter is used to set the parameter
- # as permitted and limit which attributes should be allowed for mass updating.
- #
- # params = ActionController::Parameters.new({
- # person: {
- # name: 'Francesco',
- # age: 22,
- # role: 'admin'
- # }
- # })
- #
- # permitted = params.require(:person).permit(:name, :age)
- # permitted # => {"name"=>"Francesco", "age"=>22}
- # permitted.class # => ActionController::Parameters
- # permitted.permitted? # => true
- #
- # Person.first.update!(permitted)
- # # => #<Person id: 1, name: "Francesco", age: 22, role: "user">
- #
- # It provides two options that controls the top-level behavior of new instances:
- #
- # * +permit_all_parameters+ - If it's +true+, all the parameters will be
- # permitted by default. The default is +false+.
- # * +action_on_unpermitted_parameters+ - Allow to control the behavior when parameters
- # that are not explicitly permitted are found. The values can be <tt>:log</tt> to
- # write a message on the logger or <tt>:raise</tt> to raise
- # ActionController::UnpermittedParameters exception. The default value is <tt>:log</tt>
- # in test and development environments, +false+ otherwise.
- #
- # params = ActionController::Parameters.new
- # params.permitted? # => false
- #
- # ActionController::Parameters.permit_all_parameters = true
- #
- # params = ActionController::Parameters.new
- # params.permitted? # => true
- #
- # params = ActionController::Parameters.new(a: "123", b: "456")
- # params.permit(:c)
- # # => {}
- #
- # ActionController::Parameters.action_on_unpermitted_parameters = :raise
- #
- # params = ActionController::Parameters.new(a: "123", b: "456")
- # params.permit(:c)
- # # => ActionController::UnpermittedParameters: found unpermitted keys: a, b
- #
- # <tt>ActionController::Parameters</tt> is inherited from
- # <tt>ActiveSupport::HashWithIndifferentAccess</tt>, this means
- # that you can fetch values using either <tt>:key</tt> or <tt>"key"</tt>.
- #
- # params = ActionController::Parameters.new(key: 'value')
- # params[:key] # => "value"
- # params["key"] # => "value"
- class Parameters < ActiveSupport::HashWithIndifferentAccess
- cattr_accessor :permit_all_parameters, instance_accessor: false
- cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
- # Never raise an UnpermittedParameters exception because of these params
- # are present. They are added by Rails and it's of no concern.
- NEVER_UNPERMITTED_PARAMS = %w( controller action )
- # Returns a new instance of <tt>ActionController::Parameters</tt>.
- # Also, sets the +permitted+ attribute to the default value of
- # <tt>ActionController::Parameters.permit_all_parameters</tt>.
- #
- # class Person < ActiveRecord::Base
- # end
- #
- # params = ActionController::Parameters.new(name: 'Francesco')
- # params.permitted? # => false
- # Person.new(params) # => ActiveModel::ForbiddenAttributesError
- #
- # ActionController::Parameters.permit_all_parameters = true
- #
- # params = ActionController::Parameters.new(name: 'Francesco')
- # params.permitted? # => true
- # Person.new(params) # => #<Person id: nil, name: "Francesco">
- def initialize(attributes = nil)
- super(attributes)
- @permitted = self.class.permit_all_parameters
- end
- # Returns +true+ if the parameter is permitted, +false+ otherwise.
- #
- # params = ActionController::Parameters.new
- # params.permitted? # => false
- # params.permit!
- # params.permitted? # => true
- def permitted?
- @permitted
- end
- # Sets the +permitted+ attribute to +true+. This can be used to pass
- # mass assignment. Returns +self+.
- #
- # class Person < ActiveRecord::Base
- # end
- #
- # params = ActionController::Parameters.new(name: 'Francesco')
- # params.permitted? # => false
- # Person.new(params) # => ActiveModel::ForbiddenAttributesError
- # params.permit!
- # params.permitted? # => true
- # Person.new(params) # => #<Person id: nil, name: "Francesco">
- def permit!
- each_pair do |key, value|
- convert_hashes_to_parameters(key, value)
- self[key].permit! if self[key].respond_to? :permit!
- end
- @permitted = true
- self
- end
- # Ensures that a parameter is present. If it's present, returns
- # the parameter at the given +key+, otherwise raises an
- # <tt>ActionController::ParameterMissing</tt> error.
- #
- # ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
- # # => {"name"=>"Francesco"}
- #
- # ActionController::Parameters.new(person: nil).require(:person)
- # # => ActionController::ParameterMissing: param not found: person
- #
- # ActionController::Parameters.new(person: {}).require(:person)
- # # => ActionController::ParameterMissing: param not found: person
- def require(key)
- self[key].presence || raise(ParameterMissing.new(key))
- end
- # Alias of #require.
- alias :required :require
- # Returns a new <tt>ActionController::Parameters</tt> instance that
- # includes only the given +filters+ and sets the +permitted+ attribute
- # for the object to +true+. This is useful for limiting which attributes
- # should be allowed for mass updating.
- #
- # params = ActionController::Parameters.new(user: { name: 'Francesco', age: 22, role: 'admin' })
- # permitted = params.require(:user).permit(:name, :age)
- # permitted.permitted? # => true
- # permitted.has_key?(:name) # => true
- # permitted.has_key?(:age) # => true
- # permitted.has_key?(:role) # => false
- #
- # Only permitted scalars pass the filter. For example, given
- #
- # params.permit(:name)
- #
- # +:name+ passes it is a key of +params+ whose associated value is of type
- # +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+,
- # +Date+, +Time+, +DateTime+, +StringIO+, +IO+, or
- # +ActionDispatch::Http::UploadedFile+. Otherwise, the key +:name+ is
- # filtered out.
- #
- # You may declare that the parameter should be an array of permitted scalars
- # by mapping it to an empty array:
- #
- # params.permit(tags: [])
- #
- # You can also use +permit+ on nested parameters, like:
- #
- # params = ActionController::Parameters.new({
- # person: {
- # name: 'Francesco',
- # age: 22,
- # pets: [{
- # name: 'Purplish',
- # category: 'dogs'
- # }]
- # }
- # })
- #
- # permitted = params.permit(person: [ :name, { pets: :name } ])
- # permitted.permitted? # => true
- # permitted[:person][:name] # => "Francesco"
- # permitted[:person][:age] # => nil
- # permitted[:person][:pets][0][:name] # => "Purplish"
- # permitted[:person][:pets][0][:category] # => nil
- #
- # Note that if you use +permit+ in a key that points to a hash,
- # it won't allow all the hash. You also need to specify which
- # attributes inside the hash should be whitelisted.
- #
- # params = ActionController::Parameters.new({
- # person: {
- # contact: {
- # email: 'none@test.com'
- # phone: '555-1234'
- # }
- # }
- # })
- #
- # params.require(:person).permit(:contact)
- # # => {}
- #
- # params.require(:person).permit(contact: :phone)
- # # => {"contact"=>{"phone"=>"555-1234"}}
- #
- # params.require(:person).permit(contact: [ :email, :phone ])
- # # => {"contact"=>{"email"=>"none@test.com", "phone"=>"555-1234"}}
- def permit(*filters)
- params = self.class.new
- filters.flatten.each do |filter|
- case filter
- when Symbol, String
- permitted_scalar_filter(params, filter)
- when Hash then
- hash_filter(params, filter)
- end
- end
- unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters
- params.permit!
- end
- # Returns a parameter for the given +key+. If not found,
- # returns +nil+.
- #
- # params = ActionController::Parameters.new(person: { name: 'Francesco' })
- # params[:person] # => {"name"=>"Francesco"}
- # params[:none] # => nil
- def [](key)
- convert_hashes_to_parameters(key, super)
- end
- # Returns a parameter for the given +key+. If the +key+
- # can't be found, there are several options: With no other arguments,
- # it will raise an <tt>ActionController::ParameterMissing</tt> error;
- # if more arguments are given, then that will be returned; if a block
- # is given, then that will be run and its result returned.
- #
- # params = ActionController::Parameters.new(person: { name: 'Francesco' })
- # params.fetch(:person) # => {"name"=>"Francesco"}
- # params.fetch(:none) # => ActionController::ParameterMissing: param not found: none
- # params.fetch(:none, 'Francesco') # => "Francesco"
- # params.fetch(:none) { 'Francesco' } # => "Francesco"
- def fetch(key, *args)
- convert_hashes_to_parameters(key, super)
- rescue KeyError
- raise ActionController::ParameterMissing.new(key)
- end
- # Returns a new <tt>ActionController::Parameters</tt> instance that
- # includes only the given +keys+. If the given +keys+
- # don't exist, returns an empty hash.
- #
- # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
- # params.slice(:a, :b) # => {"a"=>1, "b"=>2}
- # params.slice(:d) # => {}
- def slice(*keys)
- self.class.new(super).tap do |new_instance|
- new_instance.instance_variable_set :@permitted, @permitted
- end
- end
- # Returns an exact copy of the <tt>ActionController::Parameters</tt>
- # instance. +permitted+ state is kept on the duped object.
- #
- # params = ActionController::Parameters.new(a: 1)
- # params.permit!
- # params.permitted? # => true
- # copy_params = params.dup # => {"a"=>1}
- # copy_params.permitted? # => true
- def dup
- super.tap do |duplicate|
- duplicate.instance_variable_set :@permitted, @permitted
- end
- end
- private
- def convert_hashes_to_parameters(key, value)
- if value.is_a?(Parameters) || !value.is_a?(Hash)
- value
- else
- # Convert to Parameters on first access
- self[key] = self.class.new(value)
- end
- end
- def each_element(object)
- if object.is_a?(Array)
- object.map { |el| yield el }.compact
- elsif object.is_a?(Hash) && object.keys.all? { |k| k =~ /\A-?\d+\z/ }
- hash = object.class.new
- object.each { |k,v| hash[k] = yield v }
- hash
- else
- yield object
- end
- end
- def unpermitted_parameters!(params)
- unpermitted_keys = unpermitted_keys(params)
- if unpermitted_keys.any?
- case self.class.action_on_unpermitted_parameters
- when :log
- ActionController::Base.logger.debug "Unpermitted parameters: #{unpermitted_keys.join(", ")}"
- when :raise
- raise ActionController::UnpermittedParameters.new(unpermitted_keys)
- end
- end
- end
- def unpermitted_keys(params)
- self.keys - params.keys - NEVER_UNPERMITTED_PARAMS
- end
- #
- # --- Filtering ----------------------------------------------------------
- #
- # This is a white list of permitted scalar types that includes the ones
- # supported in XML and JSON requests.
- #
- # This list is in particular used to filter ordinary requests, String goes
- # as first element to quickly short-circuit the common case.
- #
- # If you modify this collection please update the API of +permit+ above.
- PERMITTED_SCALAR_TYPES = [
- String,
- Symbol,
- NilClass,
- Numeric,
- TrueClass,
- FalseClass,
- Date,
- Time,
- # DateTimes are Dates, we document the type but avoid the redundant check.
- StringIO,
- IO,
- ActionDispatch::Http::UploadedFile,
- ]
- def permitted_scalar?(value)
- PERMITTED_SCALAR_TYPES.any? {|type| value.is_a?(type)}
- end
- def permitted_scalar_filter(params, key)
- if has_key?(key) && permitted_scalar?(self[key])
- params[key] = self[key]
- end
- keys.grep(/\A#{Regexp.escape(key)}\(\d+[if]?\)\z/) do |k|
- if permitted_scalar?(self[k])
- params[k] = self[k]
- end
- end
- end
- def array_of_permitted_scalars?(value)
- if value.is_a?(Array)
- value.all? {|element| permitted_scalar?(element)}
- end
- end
- def array_of_permitted_scalars_filter(params, key)
- if has_key?(key) && array_of_permitted_scalars?(self[key])
- params[key] = self[key]
- end
- end
- EMPTY_ARRAY = []
- def hash_filter(params, filter)
- filter = filter.with_indifferent_access
- # Slicing filters out non-declared keys.
- slice(*filter.keys).each do |key, value|
- return unless value
- if filter[key] == EMPTY_ARRAY
- # Declaration { comment_ids: [] }.
- array_of_permitted_scalars_filter(params, key)
- else
- # Declaration { user: :name } or { user: [:name, :age, { adress: ... }] }.
- params[key] = each_element(value) do |element|
- if element.is_a?(Hash)
- element = self.class.new(element) unless element.respond_to?(:permit)
- element.permit(*Array.wrap(filter[key]))
- end
- end
- end
- end
- end
- end
- # == Strong \Parameters
- #
- # It provides an interface for protecting attributes from end-user
- # assignment. This makes Action Controller parameters forbidden
- # to be used in Active Model mass assignment until they have been
- # whitelisted.
- #
- # In addition, parameters can be marked as required and flow through a
- # predefined raise/rescue flow to end up as a 400 Bad Request with no
- # effort.
- #
- # class PeopleController < ActionController::Base
- # # Using "Person.create(params[:person])" would raise an
- # # ActiveModel::ForbiddenAttributes exception because it'd
- # # be using mass assignment without an explicit permit step.
- # # This is the recommended form:
- # def create
- # Person.create(person_params)
- # end
- #
- # # This will pass with flying colors as long as there's a person key in the
- # # parameters, otherwise it'll raise an ActionController::MissingParameter
- # # exception, which will get caught by ActionController::Base and turned
- # # into a 400 Bad Request reply.
- # def update
- # redirect_to current_account.people.find(params[:id]).tap { |person|
- # person.update!(person_params)
- # }
- # end
- #
- # private
- # # Using a private method to encapsulate the permissible parameters is
- # # just a good pattern since you'll be able to reuse the same permit
- # # list between create and update. Also, you can specialize this method
- # # with per-user checking of permissible attributes.
- # def person_params
- # params.require(:person).permit(:name, :age)
- # end
- # end
- #
- # In order to use <tt>accepts_nested_attribute_for</tt> with Strong \Parameters, you
- # will need to specify which nested attributes should be whitelisted.
- #
- # class Person
- # has_many :pets
- # accepts_nested_attributes_for :pets
- # end
- #
- # class PeopleController < ActionController::Base
- # def create
- # Person.create(person_params)
- # end
- #
- # ...
- #
- # private
- #
- # def person_params
- # # It's mandatory to specify the nested attributes that should be whitelisted.
- # # If you use `permit` with just the key that points to the nested attributes hash,
- # # it will return an empty hash.
- # params.require(:person).permit(:name, :age, pets_attributes: [ :name, :category ])
- # end
- # end
- #
- # See ActionController::Parameters.require and ActionController::Parameters.permit
- # for more information.
- module StrongParameters
- extend ActiveSupport::Concern
- include ActiveSupport::Rescuable
- # Returns a new ActionController::Parameters object that
- # has been instantiated with the <tt>request.parameters</tt>.
- def params
- @_params ||= Parameters.new(request.parameters)
- end
- # Assigns the given +value+ to the +params+ hash. If +value+
- # is a Hash, this will create an ActionController::Parameters
- # object that has been instantiated with the given +value+ hash.
- def params=(value)
- @_params = value.is_a?(Hash) ? Parameters.new(value) : value
- end
- end
- end
- module ActionController
- module Testing
- extend ActiveSupport::Concern
- include RackDelegation
- # TODO : Rewrite tests using controller.headers= to use Rack env
- def headers=(new_headers)
- @_response ||= ActionDispatch::Response.new
- @_response.headers.replace(new_headers)
- end
- # Behavior specific to functional tests
- module Functional # :nodoc:
- def set_response!(request)
- end
- def recycle!
- @_url_options = nil
- self.response_body = nil
- self.formats = nil
- self.params = nil
- end
- end
- module ClassMethods
- def before_filters
- _process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name}
- end
- end
- end
- end
- module ActionController
- # 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.
- #
- # In addition to <tt>AbstractController::UrlFor</tt>, 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+ and +request+, which need to be a Rack-compatible.
- #
- # class RootUrl
- # include ActionController::UrlFor
- # include Rails.application.routes.url_helpers
- #
- # delegate :env, :request, to: :controller
- #
- # def initialize(controller)
- # @controller = controller
- # @url = root_path # named route from the application.
- # end
- # end
- module UrlFor
- extend ActiveSupport::Concern
- include AbstractController::UrlFor
- def url_options
- @_url_options ||= super.reverse_merge(
- :host => request.host,
- :port => request.optional_port,
- :protocol => request.protocol,
- :_recall => request.symbolized_path_parameters
- ).freeze
- if (same_origin = _routes.equal?(env["action_dispatch.routes"])) ||
- (script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) ||
- (original_script_name = env['SCRIPT_NAME'])
- @_url_options.dup.tap do |options|
- if original_script_name
- options[:original_script_name] = original_script_name
- else
- options[:script_name] = same_origin ? request.script_name.dup : script_name
- end
- options.freeze
- end
- else
- @_url_options
- end
- end
- end
- end
- require 'active_support/core_ext/array/extract_options'
- require 'action_dispatch/middleware/stack'
- module ActionController
- # 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 MiddlewareStack < ActionDispatch::MiddlewareStack #:nodoc:
- class Middleware < ActionDispatch::MiddlewareStack::Middleware #:nodoc:
- def initialize(klass, *args, &block)
- options = args.extract_options!
- @only = Array(options.delete(:only)).map(&:to_s)
- @except = Array(options.delete(:except)).map(&:to_s)
- args << options unless options.empty?
- super
- end
- def valid?(action)
- if @only.present?
- @only.include?(action)
- elsif @except.present?
- !@except.include?(action)
- else
- true
- end
- end
- end
- def build(action, app=nil, &block)
- app ||= block
- action = action.to_s
- raise "MiddlewareStack#build requires an app" unless app
- middlewares.reverse.inject(app) do |a, middleware|
- middleware.valid?(action) ?
- middleware.build(a) : a
- end
- end
- end
- # <tt>ActionController::Metal</tt> is the simplest possible controller, providing a
- # valid Rack interface without the additional niceties provided by
- # <tt>ActionController::Base</tt>.
- #
- # A sample metal controller might look like this:
- #
- # class HelloController < ActionController::Metal
- # def index
- # self.response_body = "Hello World!"
- # end
- # end
- #
- # And then to route requests to your metal controller, you would add
- # something like this to <tt>config/routes.rb</tt>:
- #
- # match 'hello', to: HelloController.action(:index)
- #
- # The +action+ method returns a valid Rack application for the \Rails
- # router to dispatch to.
- #
- # == Rendering Helpers
- #
- # <tt>ActionController::Metal</tt> 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:
- #
- # class HelloController < ActionController::Metal
- # include ActionController::Rendering
- # append_view_path "#{Rails.root}/app/views"
- #
- # def index
- # render "hello/index"
- # end
- # end
- #
- # == 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
- #
- # def index
- # redirect_to root_url
- # end
- # end
- #
- # == Other Helpers
- #
- # You can refer to the modules included in <tt>ActionController::Base</tt> to see
- # other features you can bring into your metal controller.
- #
- class Metal < AbstractController::Base
- abstract!
- attr_internal_writer :env
- def env
- @_env ||= {}
- end
- # 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
- # * <tt>string</tt>
- def self.controller_name
- @controller_name ||= name.demodulize.sub(/Controller$/, '').underscore
- end
- # Delegates to the class' <tt>controller_name</tt>
- def controller_name
- self.class.controller_name
- end
- # The details below can be overridden to support a specific
- # Request and Response object. The default ActionController::Base
- # implementation includes RackDelegation, which makes a request
- # and response object available. You might wish to control the
- # environment and response manually for performance reasons.
- attr_internal :headers, :response, :request
- delegate :session, :to => "@_request"
- def initialize
- @_headers = {"Content-Type" => "text/html"}
- @_status = 200
- @_request = nil
- @_response = nil
- @_routes = nil
- super
- end
- def params
- @_params ||= request.parameters
- end
- def params=(val)
- @_params = val
- end
- # Basic implementations for content_type=, location=, and headers are
- # provided to reduce the dependency on the RackDelegation module
- # in Renderer and Redirector.
- def content_type=(type)
- headers["Content-Type"] = type.to_s
- end
- def content_type
- headers["Content-Type"]
- end
- def location
- headers["Location"]
- end
- def location=(url)
- headers["Location"] = url
- end
- # basic url_for that can be overridden for more robust functionality
- def url_for(string)
- string
- end
- def status
- @_status
- end
- def status=(status)
- @_status = Rack::Utils.status_code(status)
- end
- def response_body=(body)
- body = [body] unless body.nil? || body.respond_to?(:each)
- super
- end
- def performed?
- response_body || (response && response.committed?)
- end
- def dispatch(name, request) #:nodoc:
- @_request = request
- @_env = request.env
- @_env['action_controller.instance'] = self
- process(name)
- to_a
- end
- def to_a #:nodoc:
- response ? response.to_a : [status, headers, response_body]
- end
- class_attribute :middleware_stack
- self.middleware_stack = ActionController::MiddlewareStack.new
- def self.inherited(base) # :nodoc:
- base.middleware_stack = middleware_stack.dup
- super
- end
- # Pushes the given Rack middleware and its arguments to the bottom of the
- # middleware stack.
- def self.use(*args, &block)
- middleware_stack.use(*args, &block)
- end
- # Alias for +middleware_stack+.
- def self.middleware
- middleware_stack
- end
- # Makes the controller a Rack endpoint that runs the action in the given
- # +env+'s +action_dispatch.request.path_parameters+ key.
- def self.call(env)
- action(env['action_dispatch.request.path_parameters'][:action]).call(env)
- end
- # Returns a Rack endpoint for the given action name.
- def self.action(name, klass = ActionDispatch::Request)
- middleware_stack.build(name.to_s) do |env|
- new.dispatch(name, klass.new(env))
- end
- end
- end
- end
- module ActionController
- class Middleware < Metal
- class ActionMiddleware
- def initialize(controller, app)
- @controller, @app = controller, app
- end
- def call(env)
- request = ActionDispatch::Request.new(env)
- @controller.build(@app).dispatch(:index, request)
- end
- end
- class << self
- alias build new
- def new(app)
- ActionMiddleware.new(self, app)
- end
- end
- attr_internal :app
- def process(action)
- response = super
- self.status, self.headers, self.response_body = response if response.is_a?(Array)
- response
- end
- def initialize(app)
- super()
- @_app = app
- end
- def index
- call(env)
- end
- end
- endmodule ActionController
- module ModelNaming
- # Converts the given object to an ActiveModel compliant one.
- def convert_to_model(object)
- object.respond_to?(:to_model) ? object.to_model : object
- end
- def model_name_from_record_or_class(record_or_class)
- (record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name
- end
- end
- end
- require "rails"
- require "action_controller"
- require "action_dispatch/railtie"
- require "action_view/railtie"
- require "abstract_controller/railties/routes_helpers"
- require "action_controller/railties/helpers"
- module ActionController
- class Railtie < Rails::Railtie #:nodoc:
- config.action_controller = ActiveSupport::OrderedOptions.new
- config.eager_load_namespaces << ActionController
- initializer "action_controller.assets_config", :group => :all do |app|
- app.config.action_controller.assets_dir ||= app.config.paths["public"].first
- end
- initializer "action_controller.set_helpers_path" do |app|
- ActionController::Helpers.helpers_path = app.helpers_paths
- end
- initializer "action_controller.parameters_config" do |app|
- options = app.config.action_controller
- ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false }
- ActionController::Parameters.action_on_unpermitted_parameters = options.delete(:action_on_unpermitted_parameters) do
- (Rails.env.test? || Rails.env.development?) ? :log : false
- end
- end
- initializer "action_controller.set_configs" do |app|
- paths = app.config.paths
- options = app.config.action_controller
- options.logger ||= Rails.logger
- options.cache_store ||= Rails.cache
- options.javascripts_dir ||= paths["public/javascripts"].first
- options.stylesheets_dir ||= paths["public/stylesheets"].first
- # Ensure readers methods get compiled
- options.asset_host ||= app.config.asset_host
- options.relative_url_root ||= app.config.relative_url_root
- ActiveSupport.on_load(:action_controller) do
- include app.routes.mounted_helpers
- extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
- extend ::ActionController::Railties::Helpers
- options.each do |k,v|
- k = "#{k}="
- if respond_to?(k)
- send(k, v)
- elsif !Base.respond_to?(k)
- raise "Invalid option key: #{k}"
- end
- end
- end
- end
- initializer "action_controller.compile_config_methods" do
- ActiveSupport.on_load(:action_controller) do
- config.compile_methods! if config.respond_to?(:compile_methods!)
- end
- end
- end
- end
- module ActionController
- module Railties
- module Helpers
- def inherited(klass)
- super
- return unless klass.respond_to?(:helpers_path=)
- if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_helpers_paths) }
- paths = namespace.railtie_helpers_paths
- else
- paths = ActionController::Helpers.helpers_path
- end
- klass.helpers_path = paths
- if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
- klass.helper :all
- end
- end
- end
- end
- end
- require 'action_view/record_identifier'
- module ActionController
- module RecordIdentifier
- MODULE_MESSAGE = 'Calling ActionController::RecordIdentifier.%s is deprecated and ' \
- 'will be removed in Rails 4.1, please call using ActionView::RecordIdentifier instead.'
- INSTANCE_MESSAGE = '%s method will no longer be included by default in controllers ' \
- 'since Rails 4.1. If you would like to use it in controllers, please include ' \
- 'ActionView::RecordIdentifier module.'
- def dom_id(record, prefix = nil)
- ActiveSupport::Deprecation.warn(INSTANCE_MESSAGE % 'dom_id')
- ActionView::RecordIdentifier.dom_id(record, prefix)
- end
- def dom_class(record, prefix = nil)
- ActiveSupport::Deprecation.warn(INSTANCE_MESSAGE % 'dom_class')
- ActionView::RecordIdentifier.dom_class(record, prefix)
- end
- def self.dom_id(record, prefix = nil)
- ActiveSupport::Deprecation.warn(MODULE_MESSAGE % 'dom_id')
- ActionView::RecordIdentifier.dom_id(record, prefix)
- end
- def self.dom_class(record, prefix = nil)
- ActiveSupport::Deprecation.warn(MODULE_MESSAGE % 'dom_class')
- ActionView::RecordIdentifier.dom_class(record, prefix)
- end
- end
- end
- require 'rack/session/abstract/id'
- require 'active_support/core_ext/object/to_query'
- require 'active_support/core_ext/module/anonymous'
- require 'active_support/core_ext/hash/keys'
- module ActionController
- module TemplateAssertions
- extend ActiveSupport::Concern
- included do
- setup :setup_subscriptions
- teardown :teardown_subscriptions
- end
- def setup_subscriptions
- @_partials = Hash.new(0)
- @_templates = Hash.new(0)
- @_layouts = Hash.new(0)
- ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload|
- path = payload[:layout]
- if path
- @_layouts[path] += 1
- if path =~ /^layouts\/(.*)/
- @_layouts[$1] += 1
- end
- end
- end
- ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload|
- path = payload[:virtual_path]
- next unless path
- partial = path =~ /^.*\/_[^\/]*$/
- if partial
- @_partials[path] += 1
- @_partials[path.split("/").last] += 1
- end
- @_templates[path] += 1
- end
- end
- def teardown_subscriptions
- ActiveSupport::Notifications.unsubscribe("render_template.action_view")
- ActiveSupport::Notifications.unsubscribe("!render_template.action_view")
- end
- def process(*args)
- @_partials = Hash.new(0)
- @_templates = Hash.new(0)
- @_layouts = Hash.new(0)
- super
- end
- # Asserts that the request was rendered with the appropriate template file or partials.
- #
- # # assert that the "new" view template was rendered
- # assert_template "new"
- #
- # # assert that the exact template "admin/posts/new" was rendered
- # assert_template %r{\Aadmin/posts/new\Z}
- #
- # # assert that the layout 'admin' was rendered
- # assert_template layout: 'admin'
- # assert_template layout: 'layouts/admin'
- # assert_template layout: :admin
- #
- # # assert that no layout was rendered
- # assert_template layout: nil
- # assert_template layout: false
- #
- # # assert that the "_customer" partial was rendered twice
- # assert_template partial: '_customer', count: 2
- #
- # # assert that no partials were rendered
- # assert_template partial: false
- #
- # In a view test case, you can also assert that specific locals are passed
- # to partials:
- #
- # # assert that the "_customer" partial was rendered with a specific object
- # assert_template partial: '_customer', locals: { customer: @customer }
- def assert_template(options = {}, message = nil)
- # Force body to be read in case the template is being streamed.
- response.body
- case options
- when NilClass, Regexp, String, Symbol
- options = options.to_s if Symbol === options
- rendered = @_templates
- msg = message || sprintf("expecting <%s> but rendering with <%s>",
- options.inspect, rendered.keys)
- matches_template =
- case options
- when String
- !options.empty? && rendered.any? do |t, num|
- options_splited = options.split(File::SEPARATOR)
- t_splited = t.split(File::SEPARATOR)
- t_splited.last(options_splited.size) == options_splited
- end
- when Regexp
- rendered.any? { |t,num| t.match(options) }
- when NilClass
- rendered.blank?
- end
- assert matches_template, msg
- when Hash
- options.assert_valid_keys(:layout, :partial, :locals, :count)
- if options.key?(:layout)
- expected_layout = options[:layout]
- msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
- expected_layout, @_layouts.keys)
- case expected_layout
- when String, Symbol
- assert_includes @_layouts.keys, expected_layout.to_s, msg
- when Regexp
- assert(@_layouts.keys.any? {|l| l =~ expected_layout }, msg)
- when nil, false
- assert(@_layouts.empty?, msg)
- end
- end
- if expected_partial = options[:partial]
- if expected_locals = options[:locals]
- if defined?(@_rendered_views)
- view = expected_partial.to_s.sub(/^_/, '').sub(/\/_(?=[^\/]+\z)/, '/')
- partial_was_not_rendered_msg = "expected %s to be rendered but it was not." % view
- assert_includes @_rendered_views.rendered_views, view, partial_was_not_rendered_msg
- msg = 'expecting %s to be rendered with %s but was with %s' % [expected_partial,
- expected_locals,
- @_rendered_views.locals_for(view)]
- assert(@_rendered_views.view_rendered?(view, options[:locals]), msg)
- else
- warn "the :locals option to #assert_template is only supported in a ActionView::TestCase"
- end
- elsif expected_count = options[:count]
- actual_count = @_partials[expected_partial]
- msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)",
- expected_partial, expected_count, actual_count)
- assert(actual_count == expected_count.to_i, msg)
- else
- msg = message || sprintf("expecting partial <%s> but action rendered <%s>",
- options[:partial], @_partials.keys)
- assert_includes @_partials, expected_partial, msg
- end
- elsif options.key?(:partial)
- assert @_partials.empty?,
- "Expected no partials to be rendered"
- end
- else
- raise ArgumentError, "assert_template only accepts a String, Symbol, Hash, Regexp, or nil"
- end
- end
- end
- class TestRequest < ActionDispatch::TestRequest #:nodoc:
- DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
- DEFAULT_ENV.delete 'PATH_INFO'
- def initialize(env = {})
- super
- self.session = TestSession.new
- self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => SecureRandom.hex(16))
- end
- def assign_parameters(routes, controller_path, action, parameters = {})
- parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
- extra_keys = routes.extra_keys(parameters)
- non_path_parameters = get? ? query_parameters : request_parameters
- parameters.each do |key, value|
- if value.is_a?(Array) && (value.frozen? || value.any?(&:frozen?))
- value = value.map{ |v| v.duplicable? ? v.dup : v }
- elsif value.is_a?(Hash) && (value.frozen? || value.any?{ |k,v| v.frozen? })
- value = Hash[value.map{ |k,v| [k, v.duplicable? ? v.dup : v] }]
- elsif value.frozen? && value.duplicable?
- value = value.dup
- end
- if extra_keys.include?(key.to_sym)
- non_path_parameters[key] = value
- else
- if value.is_a?(Array)
- value = value.map(&:to_param)
- else
- value = value.to_param
- end
- path_parameters[key.to_s] = value
- end
- end
- # Clear the combined params hash in case it was already referenced.
- @env.delete("action_dispatch.request.parameters")
- params = self.request_parameters.dup
- %w(controller action only_path).each do |k|
- params.delete(k)
- params.delete(k.to_sym)
- end
- data = params.to_query
- @env['CONTENT_LENGTH'] = data.length.to_s
- @env['rack.input'] = StringIO.new(data)
- end
- def recycle!
- @formats = nil
- @env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
- @env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
- @symbolized_path_params = nil
- @method = @request_method = nil
- @fullpath = @ip = @remote_ip = @protocol = nil
- @env['action_dispatch.request.query_parameters'] = {}
- @set_cookies ||= {}
- @set_cookies.update(Hash[cookie_jar.instance_variable_get("@set_cookies").map{ |k,o| [k,o[:value]] }])
- deleted_cookies = cookie_jar.instance_variable_get("@delete_cookies")
- @set_cookies.reject!{ |k,v| deleted_cookies.include?(k) }
- cookie_jar.update(rack_cookies)
- cookie_jar.update(cookies)
- cookie_jar.update(@set_cookies)
- cookie_jar.recycle!
- end
- private
- def default_env
- DEFAULT_ENV
- end
- end
- class TestResponse < ActionDispatch::TestResponse
- def recycle!
- initialize
- end
- end
- # Methods #destroy and #load! are overridden to avoid calling methods on the
- # @store object, which does not exist for the TestSession class.
- class TestSession < Rack::Session::Abstract::SessionHash #:nodoc:
- DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS
- def initialize(session = {})
- super(nil, nil)
- @id = SecureRandom.hex(16)
- @data = stringify_keys(session)
- @loaded = true
- end
- def exists?
- true
- end
- def keys
- @data.keys
- end
- def values
- @data.values
- end
- def destroy
- clear
- end
- private
- def load!
- @id
- end
- end
- # Superclass for ActionController functional tests. Functional tests allow you to
- # test a single controller action per test method. This should not be confused with
- # integration tests (see ActionDispatch::IntegrationTest), which are more like
- # "stories" that can involve multiple controllers and multiple actions (i.e. multiple
- # different HTTP requests).
- #
- # == 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.
- #
- # For example:
- #
- # class BooksControllerTest < ActionController::TestCase
- # def test_create
- # # Simulate a POST response with the given HTTP parameters.
- # post(:create, book: { title: "Love Hina" })
- #
- # # Assert that the controller tried to redirect us to
- # # the created book's URI.
- # assert_response :found
- #
- # # Assert that the controller really put the book in the database.
- # assert_not_nil Book.find_by_title("Love Hina")
- # 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, json
- # end
- #
- # == Special instance variables
- #
- # 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 ActionController::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.
- #
- # (Earlier versions of \Rails required each functional test to subclass
- # Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
- #
- # == 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 SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
- # tests WidgetController
- # end
- #
- # == \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:
- #
- # * assigns: Instance variables assigned in the action that are available for the view.
- # * 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_not_nil assigns(:person) # makes sure that a @person instance variable was set
- # 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
- #
- # For historic reasons, the assigns hash uses string-based keys. So <tt>assigns[:person]</tt> won't work, but <tt>assigns["person"]</tt> will. To
- # appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing.
- # So <tt>assigns(:person)</tt> will work just like <tt>assigns["person"]</tt>, but again, <tt>assigns[:person]</tt> will not work.
- #
- # On top of the collections, you have the complete url that a given action redirected to available in <tt>redirect_to_url</tt>.
- #
- # 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
- #
- # 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"
- #
- # To clear the cookies for a test just clear the cookie collection:
- #
- # cookies.clear
- #
- # == \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.
- #
- # assert_redirected_to page_url(title: 'foo')
- class TestCase < ActiveSupport::TestCase
- module Behavior
- extend ActiveSupport::Concern
- include ActionDispatch::TestProcess
- include ActiveSupport::Testing::ConstantLookup
- 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.
- #
- # tests WidgetController
- # tests :widget
- # tests 'widget'
- def tests(controller_class)
- case controller_class
- when String, Symbol
- self.controller_class = "#{controller_class.to_s.camelize}Controller".constantize
- when Class
- self.controller_class = controller_class
- else
- raise ArgumentError, "controller class must be a String, Symbol, or Class"
- end
- end
- def controller_class=(new_class)
- prepare_controller_class(new_class) if new_class
- self._controller_class = new_class
- end
- def controller_class
- if current_controller_class = self._controller_class
- current_controller_class
- else
- self.controller_class = determine_default_controller_class(name)
- end
- end
- def determine_default_controller_class(name)
- determine_constant_from_test_name(name) do |constant|
- Class === constant && constant < ActionController::Metal
- end
- end
- def prepare_controller_class(new_class)
- new_class.send :include, ActionController::TestCase::RaiseActionExceptions
- end
- end
- # Executes a request simulating GET HTTP method and set/volley the response
- def get(action, *args)
- process(action, "GET", *args)
- end
- # Executes a request simulating POST HTTP method and set/volley the response
- def post(action, *args)
- process(action, "POST", *args)
- end
- # Executes a request simulating PATCH HTTP method and set/volley the response
- def patch(action, *args)
- process(action, "PATCH", *args)
- end
- # Executes a request simulating PUT HTTP method and set/volley the response
- def put(action, *args)
- process(action, "PUT", *args)
- end
- # Executes a request simulating DELETE HTTP method and set/volley the response
- def delete(action, *args)
- process(action, "DELETE", *args)
- end
- # Executes a request simulating HEAD HTTP method and set/volley the response
- def head(action, *args)
- process(action, "HEAD", *args)
- end
- # Executes a request simulating OPTIONS HTTP method and set/volley the response
- def options(action, *args)
- process(action, "OPTIONS", *args)
- end
- def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
- @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
- @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
- __send__(request_method, action, parameters, session, flash).tap do
- @request.env.delete 'HTTP_X_REQUESTED_WITH'
- @request.env.delete 'HTTP_ACCEPT'
- end
- end
- alias xhr :xml_http_request
- def paramify_values(hash_or_array_or_value)
- case hash_or_array_or_value
- when Hash
- Hash[hash_or_array_or_value.map{|key, value| [key, paramify_values(value)] }]
- when Array
- hash_or_array_or_value.map {|i| paramify_values(i)}
- when Rack::Test::UploadedFile, ActionDispatch::Http::UploadedFile
- hash_or_array_or_value
- else
- hash_or_array_or_value.to_param
- end
- end
- def process(action, http_method = 'GET', *args)
- check_required_ivars
- http_method, args = handle_old_process_api(http_method, args, caller)
- if args.first.is_a?(String) && http_method != 'HEAD'
- @request.env['RAW_POST_DATA'] = args.shift
- end
- parameters, session, flash = args
- # Ensure that numbers and symbols passed as params are converted to
- # proper params, as is the case when engaging rack.
- parameters = paramify_values(parameters) if html_format?(parameters)
- @html_document = nil
- unless @controller.respond_to?(:recycle!)
- @controller.extend(Testing::Functional)
- @controller.class.class_eval { include Testing }
- end
- @request.recycle!
- @response.recycle!
- @controller.recycle!
- @request.env['REQUEST_METHOD'] = http_method
- parameters ||= {}
- controller_class_name = @controller.class.anonymous? ?
- "anonymous" :
- @controller.class.name.underscore.sub(/_controller$/, '')
- @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
- @request.session.update(session) if session
- @request.flash.update(flash || {})
- @controller.request = @request
- @controller.response = @response
- build_request_uri(action, parameters)
- name = @request.parameters[:action]
- @controller.process(name)
- if cookies = @request.env['action_dispatch.cookies']
- cookies.write(@response)
- end
- @response.prepare!
- @assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
- @request.session['flash'] = @request.flash.to_session_value
- @request.session.delete('flash') if @request.session['flash'].blank?
- @response
- end
- def setup_controller_request_and_response
- @request = build_request
- @response = build_response
- @response.request = @request
- @controller = nil unless defined? @controller
- if klass = self.class.controller_class
- unless @controller
- begin
- @controller = klass.new
- rescue
- warn "could not construct controller #{klass}" if $VERBOSE
- end
- end
- end
- if @controller
- @controller.request = @request
- @controller.params = {}
- end
- end
- def build_request
- TestRequest.new
- end
- def build_response
- TestResponse.new
- end
- included do
- include ActionController::TemplateAssertions
- include ActionDispatch::Assertions
- class_attribute :_controller_class
- setup :setup_controller_request_and_response
- end
- private
- def check_required_ivars
- # Sanity 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."
- end
- end
- end
- def handle_old_process_api(http_method, args, callstack)
- # 4.0: Remove this method.
- if http_method.is_a?(Hash)
- ActiveSupport::Deprecation.warn("TestCase#process now expects the HTTP method as second argument: process(action, http_method, params, session, flash)", callstack)
- args.unshift(http_method)
- http_method = args.last.is_a?(String) ? args.last : "GET"
- end
- [http_method, args]
- end
- def build_request_uri(action, parameters)
- unless @request.env["PATH_INFO"]
- options = @controller.respond_to?(:url_options) ? @controller.__send__(:url_options).merge(parameters) : parameters
- options.update(
- :only_path => true,
- :action => action,
- :relative_url_root => nil,
- :_recall => @request.symbolized_path_parameters)
- url, query_string = @routes.url_for(options).split("?", 2)
- @request.env["SCRIPT_NAME"] = @controller.config.relative_url_root
- @request.env["PATH_INFO"] = url
- @request.env["QUERY_STRING"] = query_string || ""
- end
- end
- def html_format?(parameters)
- return true unless parameters.is_a?(Hash)
- Mime.fetch(parameters[:format]) { Mime['html'] }.html?
- end
- end
- # When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
- # (skipping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
- # rescue_action process takes place. This means you can test your rescue_action code by setting remote_addr to something else
- # than 0.0.0.0.
- #
- # The exception is stored in the exception accessor for further inspection.
- module RaiseActionExceptions
- def self.included(base) #:nodoc:
- unless base.method_defined?(:exception) && base.method_defined?(:exception=)
- base.class_eval do
- attr_accessor :exception
- protected :exception, :exception=
- end
- end
- end
- protected
- def rescue_action_without_handler(e)
- self.exception = e
- if request.remote_addr == "0.0.0.0"
- raise(e)
- else
- super(e)
- end
- end
- end
- include Behavior
- end
- end
- require 'action_view/vendor/html-scanner'
- require 'active_support/deprecation'
- ActiveSupport::Deprecation.warn 'Vendored html-scanner was moved to action_view, please require "action_view/vendor/html-scanner" instead. ' +
- 'This file will be removed in Rails 4.1'
- require 'active_support/rails'
- require 'abstract_controller'
- require 'action_dispatch'
- require 'action_controller/metal/live'
- require 'action_controller/metal/strong_parameters'
- module ActionController
- extend ActiveSupport::Autoload
- autoload :Base
- autoload :Caching
- autoload :Metal
- autoload :Middleware
- autoload_under "metal" do
- autoload :Compatibility
- autoload :ConditionalGet
- autoload :Cookies
- autoload :DataStreaming
- autoload :Flash
- autoload :ForceSSL
- autoload :Head
- autoload :Helpers
- autoload :HideActions
- autoload :HttpAuthentication
- autoload :ImplicitRender
- autoload :Instrumentation
- autoload :MimeResponds
- autoload :ParamsWrapper
- autoload :RackDelegation
- autoload :Redirecting
- autoload :Renderers
- autoload :Rendering
- autoload :RequestForgeryProtection
- autoload :Rescue
- autoload :Responder
- autoload :Streaming
- autoload :StrongParameters
- autoload :Testing
- autoload :UrlFor
- end
- autoload :Integration, 'action_controller/deprecated/integration_test'
- autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
- autoload :Routing, 'action_controller/deprecated'
- autoload :TestCase, 'action_controller/test_case'
- autoload :TemplateAssertions, 'action_controller/test_case'
- eager_autoload do
- autoload :RecordIdentifier
- end
- def self.eager_load!
- super
- ActionController::Caching.eager_load!
- HTML.eager_load!
- end
- end
- # All of these simply register additional autoloads
- require 'action_view'
- require 'action_view/vendor/html-scanner'
- ActiveSupport.on_load(:action_view) do
- ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor)
- end
- # Common Active Support usage in Action Controller
- require 'active_support/core_ext/class/attribute_accessors'
- require 'active_support/core_ext/load_error'
- require 'active_support/core_ext/module/attr_internal'
- require 'active_support/core_ext/name_error'
- require 'active_support/core_ext/uri'
- require 'active_support/inflector'
- module ActionDispatch
- module Http
- module Cache
- module Request
- HTTP_IF_MODIFIED_SINCE = 'HTTP_IF_MODIFIED_SINCE'.freeze
- HTTP_IF_NONE_MATCH = 'HTTP_IF_NONE_MATCH'.freeze
- def if_modified_since
- if since = env[HTTP_IF_MODIFIED_SINCE]
- Time.rfc2822(since) rescue nil
- end
- end
- def if_none_match
- env[HTTP_IF_NONE_MATCH]
- end
- def if_none_match_etags
- (if_none_match ? if_none_match.split(/\s*,\s*/) : []).collect do |etag|
- etag.gsub(/^\"|\"$/, "")
- end
- end
- def not_modified?(modified_at)
- if_modified_since && modified_at && if_modified_since >= modified_at
- end
- def etag_matches?(etag)
- if etag
- etag = etag.gsub(/^\"|\"$/, "")
- if_none_match_etags.include?(etag)
- end
- end
- # 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
- etag = if_none_match
- return false unless last_modified || etag
- success = true
- success &&= not_modified?(response.last_modified) if last_modified
- success &&= etag_matches?(response.etag) if etag
- success
- end
- end
- module Response
- attr_reader :cache_control, :etag
- alias :etag? :etag
- def last_modified
- if last = headers[LAST_MODIFIED]
- Time.httpdate(last)
- end
- end
- def last_modified?
- headers.include?(LAST_MODIFIED)
- end
- def last_modified=(utc_time)
- headers[LAST_MODIFIED] = utc_time.httpdate
- end
- def date
- if date_header = headers['Date']
- Time.httpdate(date_header)
- end
- end
- def date?
- headers.include?('Date')
- end
- def date=(utc_time)
- headers['Date'] = utc_time.httpdate
- end
- def etag=(etag)
- key = ActiveSupport::Cache.expand_cache_key(etag)
- @etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}")
- end
- private
- LAST_MODIFIED = "Last-Modified".freeze
- ETAG = "ETag".freeze
- CACHE_CONTROL = "Cache-Control".freeze
- SPESHUL_KEYS = %w[extras no-cache max-age public must-revalidate]
- def cache_control_segments
- if cache_control = self[CACHE_CONTROL]
- cache_control.delete(' ').split(',')
- else
- []
- end
- end
- def cache_control_headers
- cache_control = {}
- cache_control_segments.each do |segment|
- directive, argument = segment.split('=', 2)
- if SPESHUL_KEYS.include? directive
- key = directive.tr('-', '_')
- cache_control[key.to_sym] = argument || true
- else
- cache_control[:extras] ||= []
- cache_control[:extras] << segment
- end
- end
- cache_control
- end
- def prepare_cache_control!
- @cache_control = cache_control_headers
- @etag = self[ETAG]
- end
- def handle_conditional_get!
- if etag? || last_modified? || !@cache_control.empty?
- set_conditional_cache_control!
- end
- end
- DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
- NO_CACHE = "no-cache".freeze
- PUBLIC = "public".freeze
- PRIVATE = "private".freeze
- MUST_REVALIDATE = "must-revalidate".freeze
- def set_conditional_cache_control!
- control = {}
- cc_headers = cache_control_headers
- if extras = cc_headers.delete(:extras)
- @cache_control[:extras] ||= []
- @cache_control[:extras] += extras
- @cache_control[:extras].uniq!
- end
- control.merge! cc_headers
- control.merge! @cache_control
- if control.empty?
- headers[CACHE_CONTROL] = DEFAULT_CACHE_CONTROL
- elsif control[:no_cache]
- headers[CACHE_CONTROL] = NO_CACHE
- if control[:extras]
- headers[CACHE_CONTROL] += ", #{control[:extras].join(', ')}"
- end
- else
- extras = control[:extras]
- max_age = control[:max_age]
- options = []
- options << "max-age=#{max_age.to_i}" if max_age
- options << (control[:public] ? PUBLIC : PRIVATE)
- options << MUST_REVALIDATE if control[:must_revalidate]
- options.concat(extras) if extras
- headers[CACHE_CONTROL] = options.join(", ")
- end
- end
- end
- end
- end
- end
- require 'active_support/core_ext/hash/keys'
- require 'active_support/core_ext/object/duplicable'
- require 'action_dispatch/http/parameter_filter'
- module ActionDispatch
- module Http
- # Allows you to specify sensitive parameters which will be replaced from
- # the request log by looking in the query string of the request and all
- # subhashes of the params hash to filter. If a block is given, each key and
- # value of the params hash and all subhashes is passed to it, the value
- # or key can be replaced using String#replace or similar method.
- #
- # env["action_dispatch.parameter_filter"] = [:password]
- # => replaces the value to all keys matching /password/i with "[FILTERED]"
- #
- # env["action_dispatch.parameter_filter"] = [:foo, "bar"]
- # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
- #
- # env["action_dispatch.parameter_filter"] = lambda do |k,v|
- # v.reverse! if k =~ /secret/i
- # end
- # => reverses the value to all keys matching /secret/i
- module FilterParameters
- ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
- NULL_PARAM_FILTER = ParameterFilter.new # :nodoc:
- NULL_ENV_FILTER = ParameterFilter.new ENV_MATCH # :nodoc:
- def initialize(env)
- super
- @filtered_parameters = nil
- @filtered_env = nil
- @filtered_path = nil
- end
- # Return a hash of parameters with all sensitive data replaced.
- def filtered_parameters
- @filtered_parameters ||= parameter_filter.filter(parameters)
- end
- # Return a hash of request.env with all sensitive data replaced.
- def filtered_env
- @filtered_env ||= env_filter.filter(@env)
- end
- # Reconstructed a path with all sensitive GET parameters replaced.
- def filtered_path
- @filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
- end
- protected
- def parameter_filter
- parameter_filter_for @env.fetch("action_dispatch.parameter_filter") {
- return NULL_PARAM_FILTER
- }
- end
- def env_filter
- user_key = @env.fetch("action_dispatch.parameter_filter") {
- return NULL_ENV_FILTER
- }
- parameter_filter_for(Array(user_key) + ENV_MATCH)
- end
- def parameter_filter_for(filters)
- ParameterFilter.new(filters)
- end
- KV_RE = '[^&;=]+'
- PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
- def filtered_query_string
- query_string.gsub(PAIR_RE) do |_|
- parameter_filter.filter([[$1, $2]]).first.join("=")
- end
- end
- end
- end
- end
- module ActionDispatch
- module Http
- module FilterRedirect
- FILTERED = '[FILTERED]'.freeze # :nodoc:
- def filtered_location
- if !location_filter.empty? && location_filter_match?
- FILTERED
- else
- location
- end
- end
- private
- def location_filter
- if request.present?
- request.env['action_dispatch.redirect_filter'] || []
- else
- []
- end
- end
- def location_filter_match?
- location_filter.any? do |filter|
- if String === filter
- location.include?(filter)
- elsif Regexp === filter
- location.match(filter)
- end
- end
- end
- end
- end
- end
- module ActionDispatch
- module Http
- class Headers
- include Enumerable
- def initialize(env = {})
- @headers = env
- end
- def [](header_name)
- @headers[env_name(header_name)]
- end
- def []=(k,v); @headers[k] = v; end
- def key?(k); @headers.key? k; end
- alias :include? :key?
- def fetch(header_name, *args, &block)
- @headers.fetch env_name(header_name), *args, &block
- end
- def each(&block)
- @headers.each(&block)
- end
- private
- # Converts a HTTP header name to an environment variable name if it is
- # not contained within the headers hash.
- def env_name(header_name)
- @headers.include?(header_name) ? header_name : cgi_name(header_name)
- end
- def cgi_name(k)
- "HTTP_#{k.upcase.gsub(/-/, '_')}"
- end
- end
- end
- end
- require 'active_support/core_ext/module/attribute_accessors'
- module ActionDispatch
- module Http
- module MimeNegotiation
- extend ActiveSupport::Concern
- included do
- mattr_accessor :ignore_accept_header
- self.ignore_accept_header = false
- end
- # The MIME type of the HTTP request, such as Mime::XML.
- #
- # For backward compatibility, the post \format is extracted from the
- # X-Post-Data-Format HTTP header if present.
- def content_mime_type
- @env["action_dispatch.request.content_type"] ||= begin
- if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
- Mime::Type.lookup($1.strip.downcase)
- else
- nil
- end
- end
- end
- def content_type
- content_mime_type && content_mime_type.to_s
- end
- # Returns the accepted MIME type for the request.
- def accepts
- @env["action_dispatch.request.accepts"] ||= begin
- header = @env['HTTP_ACCEPT'].to_s.strip
- if header.empty?
- [content_mime_type]
- else
- Mime::Type.parse(header)
- end
- end
- end
- # 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
- #
- def format(view_path = [])
- formats.first
- end
- def formats
- @env["action_dispatch.request.formats"] ||=
- if parameters[:format]
- Array(Mime[parameters[:format]])
- elsif use_accept_header && valid_accept_header
- accepts
- elsif xhr?
- [Mime::JS]
- else
- [Mime::HTML]
- end
- end
- # 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
- #
- # 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
- @env["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.
- #
- # In this example, the :iphone format will be used if it's available, otherwise it'll fallback
- # to the :html format.
- #
- # 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
- def formats=(extensions)
- parameters[:format] = extensions.first.to_s
- @env["action_dispatch.request.formats"] = extensions.collect do |extension|
- Mime::Type.lookup_by_extension(extension)
- end
- end
- # Receives an array of mimes and return the first user sent mime that
- # matches the order array.
- #
- def negotiate_mime(order)
- formats.each do |priority|
- if priority == Mime::ALL
- return order.first
- elsif order.include?(priority)
- return priority
- end
- end
- order.include?(Mime::ALL) ? formats.first : nil
- end
- protected
- BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
- def valid_accept_header
- (xhr? && (accept || content_mime_type)) ||
- (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS)
- end
- def use_accept_header
- !self.class.ignore_accept_header
- end
- end
- end
- end
- require 'set'
- require 'active_support/core_ext/class/attribute_accessors'
- require 'active_support/core_ext/string/starts_ends_with'
- module Mime
- class Mimes < Array
- def symbols
- @symbols ||= map { |m| m.to_sym }
- end
- %w(<< concat shift unshift push pop []= clear compact! collect!
- delete delete_at delete_if flatten! map! insert reject! reverse!
- replace slice! sort! uniq!).each do |method|
- module_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{method}(*)
- @symbols = nil
- super
- end
- CODE
- end
- end
- SET = Mimes.new
- EXTENSION_LOOKUP = {}
- LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
- class << self
- def [](type)
- return type if type.is_a?(Type)
- Type.lookup_by_extension(type) || NullType.new
- end
- def fetch(type)
- return type if type.is_a?(Type)
- EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k }
- end
- end
- # 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])
- #
- # respond_to do |format|
- # format.html
- # format.ics { render text: post.to_ics, mime_type: Mime::Type["text/calendar"] }
- # format.xml { render xml: @people }
- # end
- # end
- # end
- class Type
- @@html_types = Set.new [:html, :all]
- cattr_reader :html_types
- # These are the content types which browsers can generate without using ajax, flash, etc
- # i.e. following a link, getting an image or posting a form. CSRF protection
- # only needs to protect against these types.
- @@browser_generated_types = Set.new [:html, :url_encoded_form, :multipart_form, :text]
- attr_reader :symbol
- @register_callbacks = []
- # A simple helper class used in parsing the accept header
- class AcceptItem #:nodoc:
- attr_accessor :index, :name, :q
- alias :to_s :name
- def initialize(index, name, q = nil)
- @index = index
- @name = name
- q ||= 0.0 if @name == Mime::ALL.to_s # default wildcard match to end of list
- @q = ((q || 1.0).to_f * 100).to_i
- end
- def <=>(item)
- result = item.q <=> @q
- result = @index <=> item.index if result == 0
- result
- end
- def ==(item)
- @name == item.to_s
- end
- end
- class AcceptList < Array #:nodoc:
- def assort!
- sort!
- # Take care of the broken text/xml entry by renaming or deleting it
- if text_xml_idx && app_xml_idx
- app_xml.q = [text_xml.q, app_xml.q].max # set the q value to the max of the two
- exchange_xml_items if app_xml_idx > text_xml_idx # make sure app_xml is ahead of text_xml in the list
- delete_at(text_xml_idx) # delete text_xml from the list
- elsif text_xml_idx
- text_xml.name = Mime::XML.to_s
- end
- # Look for more specific XML-based types and sort them ahead of app/xml
- if app_xml_idx
- idx = app_xml_idx
- while idx < length
- type = self[idx]
- break if type.q < app_xml.q
- if type.name.ends_with? '+xml'
- self[app_xml_idx], self[idx] = self[idx], app_xml
- @app_xml_idx = idx
- end
- idx += 1
- end
- end
- map! { |i| Mime::Type.lookup(i.name) }.uniq!
- to_a
- end
- private
- def text_xml_idx
- @text_xml_idx ||= index('text/xml')
- end
- def app_xml_idx
- @app_xml_idx ||= index(Mime::XML.to_s)
- end
- def text_xml
- self[text_xml_idx]
- end
- def app_xml
- self[app_xml_idx]
- end
- def exchange_xml_items
- self[app_xml_idx], self[text_xml_idx] = text_xml, app_xml
- @app_xml_idx, @text_xml_idx = text_xml_idx, app_xml_idx
- end
- end
- class << self
- TRAILING_STAR_REGEXP = /(text|application)\/\*/
- PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/
- def register_callback(&block)
- @register_callbacks << block
- end
- def lookup(string)
- LOOKUP[string]
- end
- 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.
- def register_alias(string, symbol, extension_synonyms = [])
- register(string, symbol, [], extension_synonyms, true)
- end
- def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
- Mime.const_set(symbol.upcase, Type.new(string, symbol, mime_type_synonyms))
- new_mime = Mime.const_get(symbol.upcase)
- SET << new_mime
- ([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup
- ([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last }
- @register_callbacks.each do |callback|
- callback.call(new_mime)
- end
- end
- def parse(accept_header)
- if accept_header !~ /,/
- accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
- parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)]
- else
- list, index = AcceptList.new, 0
- accept_header.split(',').each do |header|
- params, q = header.split(PARAMETER_SEPARATOR_REGEXP)
- if params.present?
- params.strip!
- params = parse_trailing_star(params) || [params]
- params.each do |m|
- list << AcceptItem.new(index, m.to_s, q)
- index += 1
- end
- end
- end
- list.assort!
- end
- end
- 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 <tt>'application'</tt>, returns <tt>[Mime::HTML, Mime::JS,
- # Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::URL_ENCODED_FORM]</tt>.
- def parse_data_with_trailing_star(input)
- Mime::SET.select { |m| m =~ input }
- end
- # This method is opposite of register method.
- #
- # Usage:
- #
- # Mime::Type.unregister(:mobile)
- def unregister(symbol)
- symbol = symbol.upcase
- mime = Mime.const_get(symbol)
- Mime.instance_eval { remove_const(symbol) }
- SET.delete_if { |v| v.eql?(mime) }
- LOOKUP.delete_if { |k,v| v.eql?(mime) }
- EXTENSION_LOOKUP.delete_if { |k,v| v.eql?(mime) }
- end
- end
- def initialize(string, symbol = nil, synonyms = [])
- @symbol, @synonyms = symbol, synonyms
- @string = string
- end
- def to_s
- @string
- end
- def to_str
- to_s
- end
- def to_sym
- @symbol
- end
- def ref
- to_sym || to_s
- end
- def ===(list)
- if list.is_a?(Array)
- (@synonyms + [ self ]).any? { |synonym| list.include?(synonym) }
- else
- super
- end
- end
- def ==(mime_type)
- return false if mime_type.blank?
- (@synonyms + [ self ]).any? do |synonym|
- synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym
- end
- end
- def =~(mime_type)
- return false if mime_type.blank?
- regexp = Regexp.new(Regexp.quote(mime_type.to_s))
- (@synonyms + [ self ]).any? do |synonym|
- synonym.to_s =~ regexp
- end
- end
- # Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See
- # ActionController::RequestForgeryProtection.
- def verify_request?
- ActiveSupport::Deprecation.warn "Mime::Type#verify_request? is deprecated and will be removed in Rails 4.1"
- @@browser_generated_types.include?(to_sym)
- end
- def self.browser_generated_types
- ActiveSupport::Deprecation.warn "Mime::Type.browser_generated_types is deprecated and will be removed in Rails 4.1"
- @@browser_generated_types
- end
- def html?
- @@html_types.include?(to_sym) || @string =~ /html/
- end
- private
- def to_ary; end
- def to_a; end
- def method_missing(method, *args)
- if method.to_s.ends_with? '?'
- method[0..-2].downcase.to_sym == to_sym
- else
- super
- end
- end
- def respond_to_missing?(method, include_private = false) #:nodoc:
- method.to_s.ends_with? '?'
- end
- end
-
- class NullType
- def nil?
- true
- end
- private
- def method_missing(method, *args)
- false if method.to_s.ends_with? '?'
- end
- end
- end
- require 'action_dispatch/http/mime_types'
- # Build list of Mime types for HTTP responses
- # http://www.iana.org/assignments/media-types/
- 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 )
- Mime::Type.register "text/css", :css
- Mime::Type.register "text/calendar", :ics
- Mime::Type.register "text/csv", :csv
- Mime::Type.register "image/png", :png, [], %w(png)
- Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
- Mime::Type.register "image/gif", :gif, [], %w(gif)
- Mime::Type.register "image/bmp", :bmp, [], %w(bmp)
- Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff)
- Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe)
- Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml )
- Mime::Type.register "application/rss+xml", :rss
- Mime::Type.register "application/atom+xml", :atom
- Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml )
- Mime::Type.register "multipart/form-data", :multipart_form
- Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
- # http://www.ietf.org/rfc/rfc4627.txt
- # http://www.json.org/JSONRequest.html
- Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
- Mime::Type.register "application/pdf", :pdf, [], %w(pdf)
- Mime::Type.register "application/zip", :zip, [], %w(zip)
- # Create Mime::ALL but do not add it to the SET.
- Mime::ALL = Mime::Type.new("*/*", :all, [])
- module ActionDispatch
- module Http
- class ParameterFilter
- FILTERED = '[FILTERED]'.freeze # :nodoc:
- def initialize(filters = [])
- @filters = filters
- end
- def filter(params)
- compiled_filter.call(params)
- end
- private
- def compiled_filter
- @compiled_filter ||= CompiledFilter.compile(@filters)
- end
- class CompiledFilter # :nodoc:
- def self.compile(filters)
- return lambda { |params| params.dup } if filters.empty?
- strings, regexps, blocks = [], [], []
- filters.each do |item|
- case item
- when Proc
- blocks << item
- when Regexp
- regexps << item
- else
- strings << item.to_s
- end
- end
- regexps << Regexp.new(strings.join('|'), true) unless strings.empty?
- new regexps, blocks
- end
- attr_reader :regexps, :blocks
- def initialize(regexps, blocks)
- @regexps = regexps
- @blocks = blocks
- end
- def call(original_params)
- filtered_params = {}
- original_params.each do |key, value|
- if regexps.any? { |r| key =~ r }
- value = FILTERED
- elsif value.is_a?(Hash)
- value = call(value)
- elsif value.is_a?(Array)
- value = value.map { |v| v.is_a?(Hash) ? call(v) : v }
- elsif blocks.any?
- key = key.dup
- value = value.dup if value.duplicable?
- blocks.each { |b| b.call(key, value) }
- end
- filtered_params[key] = value
- end
- filtered_params
- end
- end
- end
- end
- end
- require 'active_support/core_ext/hash/keys'
- require 'active_support/core_ext/hash/indifferent_access'
- module ActionDispatch
- module Http
- module Parameters
- def initialize(env)
- super
- @symbolized_path_params = nil
- end
- # Returns both GET and POST \parameters in a single hash.
- def parameters
- @env["action_dispatch.request.parameters"] ||= begin
- params = begin
- request_parameters.merge(query_parameters)
- rescue EOFError
- query_parameters.dup
- end
- params.merge!(path_parameters)
- encode_params(params).with_indifferent_access
- end
- end
- alias :params :parameters
- def path_parameters=(parameters) #:nodoc:
- @symbolized_path_params = nil
- @env.delete("action_dispatch.request.parameters")
- @env["action_dispatch.request.path_parameters"] = parameters
- end
- # The same as <tt>path_parameters</tt> with explicitly symbolized keys.
- def symbolized_path_parameters
- @symbolized_path_params ||= path_parameters.symbolize_keys
- end
- # Returns a hash with the \parameters used to form the \path of the request.
- # Returned hash keys are strings:
- #
- # {'action' => 'my_action', 'controller' => 'my_controller'}
- #
- # See <tt>symbolized_path_parameters</tt> for symbolized keys.
- def path_parameters
- @env["action_dispatch.request.path_parameters"] ||= {}
- end
- def reset_parameters #:nodoc:
- @env.delete("action_dispatch.request.parameters")
- end
- private
- # TODO: Validate that the characters are UTF-8. If they aren't,
- # you'll get a weird error down the road, but our form handling
- # should really prevent that from happening
- def encode_params(params)
- if params.is_a?(String)
- return params.force_encoding(Encoding::UTF_8).encode!
- elsif !params.is_a?(Hash)
- return params
- end
- params.each do |k, v|
- case v
- when Hash
- encode_params(v)
- when Array
- v.map! {|el| encode_params(el) }
- else
- encode_params(v)
- end
- end
- end
- # Convert nested Hash to ActiveSupport::HashWithIndifferentAccess
- def normalize_parameters(value)
- case value
- when Hash
- h = {}
- value.each { |k, v| h[k] = normalize_parameters(v) }
- h.with_indifferent_access
- when Array
- value.map { |e| normalize_parameters(e) }
- else
- value
- end
- end
- end
- end
- end
- require "rack/cache"
- require "rack/cache/context"
- require "active_support/cache"
- module ActionDispatch
- class RailsMetaStore < Rack::Cache::MetaStore
- def self.resolve(uri)
- new
- end
- def initialize(store = Rails.cache)
- @store = store
- end
- def read(key)
- if data = @store.read(key)
- Marshal.load(data)
- else
- []
- end
- end
- def write(key, value)
- @store.write(key, Marshal.dump(value))
- end
- ::Rack::Cache::MetaStore::RAILS = self
- end
- class RailsEntityStore < Rack::Cache::EntityStore
- def self.resolve(uri)
- new
- end
- def initialize(store = Rails.cache)
- @store = store
- end
- def exist?(key)
- @store.exist?(key)
- end
- def open(key)
- @store.read(key)
- end
- def read(key)
- body = open(key)
- body.join if body
- end
- def write(body)
- buf = []
- key, size = slurp(body) { |part| buf << part }
- @store.write(key, buf)
- [key, size]
- end
- ::Rack::Cache::EntityStore::RAILS = self
- end
- end
- require 'stringio'
- require 'active_support/inflector'
- require 'action_dispatch/http/headers'
- require 'action_controller/metal/exceptions'
- require 'rack/request'
- require 'action_dispatch/http/cache'
- require 'action_dispatch/http/mime_negotiation'
- require 'action_dispatch/http/parameters'
- require 'action_dispatch/http/filter_parameters'
- require 'action_dispatch/http/upload'
- require 'action_dispatch/http/url'
- require 'active_support/core_ext/array/conversions'
- module ActionDispatch
- class Request < Rack::Request
- include ActionDispatch::Http::Cache::Request
- include ActionDispatch::Http::MimeNegotiation
- include ActionDispatch::Http::Parameters
- include ActionDispatch::Http::FilterParameters
- include ActionDispatch::Http::Upload
- include ActionDispatch::Http::URL
- autoload :Session, 'action_dispatch/request/session'
- LOCALHOST = Regexp.union [/^127\.0\.0\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/]
- ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE
- PATH_TRANSLATED REMOTE_HOST
- REMOTE_IDENT REMOTE_USER REMOTE_ADDR
- SERVER_NAME SERVER_PROTOCOL
- HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
- HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
- HTTP_NEGOTIATE HTTP_PRAGMA ].freeze
- ENV_METHODS.each do |env|
- class_eval <<-METHOD, __FILE__, __LINE__ + 1
- def #{env.sub(/^HTTP_/n, '').downcase} # def accept_charset
- @env["#{env}"] # @env["HTTP_ACCEPT_CHARSET"]
- end # end
- METHOD
- end
- def initialize(env)
- super
- @method = nil
- @request_method = nil
- @remote_ip = nil
- @original_fullpath = nil
- @fullpath = nil
- @ip = nil
- @uuid = nil
- end
- def key?(key)
- @env.key?(key)
- end
- # List of HTTP request methods from the following RFCs:
- # Hypertext Transfer Protocol -- HTTP/1.1 (http://www.ietf.org/rfc/rfc2616.txt)
- # HTTP Extensions for Distributed Authoring -- WEBDAV (http://www.ietf.org/rfc/rfc2518.txt)
- # Versioning Extensions to WebDAV (http://www.ietf.org/rfc/rfc3253.txt)
- # Ordered Collections Protocol (WebDAV) (http://www.ietf.org/rfc/rfc3648.txt)
- # Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol (http://www.ietf.org/rfc/rfc3744.txt)
- # Web Distributed Authoring and Versioning (WebDAV) SEARCH (http://www.ietf.org/rfc/rfc5323.txt)
- # PATCH Method for HTTP (http://www.ietf.org/rfc/rfc5789.txt)
- RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT)
- RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
- RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY)
- RFC3648 = %w(ORDERPATCH)
- RFC3744 = %w(ACL)
- RFC5323 = %w(SEARCH)
- RFC5789 = %w(PATCH)
- HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC5789
- HTTP_METHOD_LOOKUP = {}
- # Populate the HTTP method lookup cache
- HTTP_METHODS.each { |method|
- HTTP_METHOD_LOOKUP[method] = method.underscore.to_sym
- }
- # 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(env["REQUEST_METHOD"])
- end
- # Returns a symbol form of the #request_method
- 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.
- def method
- @method ||= check_method(env["rack.methodoverride.original_method"] || env['REQUEST_METHOD'])
- end
- # Returns a symbol form of the #method
- def method_symbol
- HTTP_METHOD_LOOKUP[method]
- end
- # Is this a GET (or HEAD) request?
- # Equivalent to <tt>request.request_method_symbol == :get</tt>.
- def get?
- HTTP_METHOD_LOOKUP[request_method] == :get
- end
- # Is this a POST request?
- # Equivalent to <tt>request.request_method_symbol == :post</tt>.
- def post?
- HTTP_METHOD_LOOKUP[request_method] == :post
- end
- # Is this a PATCH request?
- # Equivalent to <tt>request.request_method == :patch</tt>.
- def patch?
- HTTP_METHOD_LOOKUP[request_method] == :patch
- end
- # Is this a PUT request?
- # Equivalent to <tt>request.request_method_symbol == :put</tt>.
- def put?
- HTTP_METHOD_LOOKUP[request_method] == :put
- end
- # Is this a DELETE request?
- # Equivalent to <tt>request.request_method_symbol == :delete</tt>.
- def delete?
- HTTP_METHOD_LOOKUP[request_method] == :delete
- end
- # Is this a HEAD request?
- # Equivalent to <tt>request.request_method_symbol == :head</tt>.
- def head?
- HTTP_METHOD_LOOKUP[request_method] == :head
- end
- # Provides access to the request's HTTP headers, for example:
- #
- # request.headers["Content-Type"] # => "text/plain"
- def headers
- Http::Headers.new(@env)
- end
- def original_fullpath
- @original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath)
- end
- def fullpath
- @fullpath ||= super
- end
- def original_url
- base_url + original_fullpath
- end
- def media_type
- content_mime_type.to_s
- end
- # Returns the content length of the request as an integer.
- def content_length
- super.to_i
- end
- # Returns true if the "X-Requested-With" header contains "XMLHttpRequest"
- # (case-insensitive). All major JavaScript libraries send this header with
- # every Ajax request.
- def xml_http_request?
- @env['HTTP_X_REQUESTED_WITH'] =~ /XMLHttpRequest/i
- end
- alias :xhr? :xml_http_request?
- def ip
- @ip ||= super
- end
- # Originating IP address, usually set by the RemoteIp middleware.
- def remote_ip
- @remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
- end
- # Returns the unique request id, which is based off 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.
- def uuid
- @uuid ||= env["action_dispatch.request_id"]
- end
- # Returns the lowercase name of the HTTP server software.
- def server_software
- (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
- end
- # Read the request \body. This is useful for web services that need to
- # work with raw requests directly.
- def raw_post
- unless @env.include? 'RAW_POST_DATA'
- raw_post_body = body
- @env['RAW_POST_DATA'] = raw_post_body.read(@env['CONTENT_LENGTH'].to_i)
- raw_post_body.rewind if raw_post_body.respond_to?(:rewind)
- end
- @env['RAW_POST_DATA']
- end
- # The request body is an IO input stream. If the RAW_POST_DATA environment
- # variable is already set, wrap it in a StringIO.
- def body
- if raw_post = @env['RAW_POST_DATA']
- raw_post.force_encoding(Encoding::BINARY)
- StringIO.new(raw_post)
- else
- @env['rack.input']
- end
- end
- def form_data?
- FORM_DATA_MEDIA_TYPES.include?(content_mime_type.to_s)
- end
- def body_stream #:nodoc:
- @env['rack.input']
- end
- # TODO This should be broken apart into AD::Request::Session and probably
- # be included by the session middleware.
- def reset_session
- if session && session.respond_to?(:destroy)
- session.destroy
- else
- self.session = {}
- end
- @env['action_dispatch.request.flash_hash'] = nil
- end
- def session=(session) #:nodoc:
- Session.set @env, session
- end
- def session_options=(options)
- Session::Options.set @env, options
- end
- # Override Rack's GET method to support indifferent access
- def GET
- @env["action_dispatch.request.query_parameters"] ||= (normalize_parameters(super) || {})
- rescue TypeError => e
- raise ActionController::BadRequest.new(:query, e)
- end
- alias :query_parameters :GET
- # Override Rack's POST method to support indifferent access
- def POST
- @env["action_dispatch.request.request_parameters"] ||= (normalize_parameters(super) || {})
- rescue TypeError => e
- raise ActionController::BadRequest.new(:request, e)
- end
- alias :request_parameters :POST
- # Returns the authorization header regardless of whether it was specified directly or through one of the
- # proxy alternatives.
- def authorization
- @env['HTTP_AUTHORIZATION'] ||
- @env['X-HTTP_AUTHORIZATION'] ||
- @env['X_HTTP_AUTHORIZATION'] ||
- @env['REDIRECT_X_HTTP_AUTHORIZATION']
- end
- # True if the request came from localhost, 127.0.0.1.
- def local?
- LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
- end
- # Remove nils from the params hash
- def deep_munge(hash)
- hash.each do |k, v|
- case v
- when Array
- v.grep(Hash) { |x| deep_munge(x) }
- v.compact!
- hash[k] = nil if v.empty?
- when Hash
- deep_munge(v)
- end
- end
- hash
- end
- protected
- def parse_query(qs)
- deep_munge(super)
- end
- private
- def check_method(name)
- HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
- name
- end
- end
- end
- require 'active_support/core_ext/class/attribute_accessors'
- require 'monitor'
- module ActionDispatch # :nodoc:
- # 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).
- #
- # \Response is mostly a Ruby on \Rails framework implementation detail, and
- # should never be used directly in controllers. Controllers should use the
- # methods defined in ActionController::Base instead. For example, if you want
- # to set the HTTP response's content MIME type, then use
- # ActionControllerBase#headers instead of Response#headers.
- #
- # Nevertheless, integration tests may want to inspect controller responses in
- # more detail, and that's when \Response can be useful for application
- # developers. Integration test methods such as
- # ActionDispatch::Integration::Session#get and
- # ActionDispatch::Integration::Session#post return objects of type
- # TestResponse (which are of course also of type \Response).
- #
- # 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 Response
- attr_accessor :request, :header
- attr_reader :status
- attr_writer :sending_file
- alias_method :headers=, :header=
- alias_method :headers, :header
- delegate :[], :[]=, :to => :@header
- delegate :each, :to => :@stream
- # Sets the HTTP response's content MIME type. For example, in the controller
- # you could write this:
- #
- # 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
- # information.
- attr_accessor :charset
- attr_reader :content_type
- CONTENT_TYPE = "Content-Type".freeze
- SET_COOKIE = "Set-Cookie".freeze
- LOCATION = "Location".freeze
- cattr_accessor(:default_charset) { "utf-8" }
- cattr_accessor(:default_headers)
- include Rack::Response::Helpers
- include ActionDispatch::Http::FilterRedirect
- include ActionDispatch::Http::Cache::Response
- include MonitorMixin
- class Buffer # :nodoc:
- def initialize(response, buf)
- @response = response
- @buf = buf
- @closed = false
- end
- def write(string)
- raise IOError, "closed stream" if closed?
- @response.commit!
- @buf.push string
- end
- def each(&block)
- @buf.each(&block)
- end
- def close
- @response.commit!
- @closed = true
- end
- def closed?
- @closed
- end
- end
- attr_reader :stream
- def initialize(status = 200, header = {}, body = [])
- super()
- header = merge_default_headers(header, self.class.default_headers)
- self.body, self.header, self.status = body, header, status
- @sending_file = false
- @blank = false
- @cv = new_cond
- @committed = false
- @content_type = nil
- @charset = nil
- if content_type = self[CONTENT_TYPE]
- type, charset = content_type.split(/;\s*charset=/)
- @content_type = Mime::Type.lookup(type)
- @charset = charset || self.class.default_charset
- end
- prepare_cache_control!
- yield self if block_given?
- end
- def await_commit
- synchronize do
- @cv.wait_until { @committed }
- end
- end
- def commit!
- synchronize do
- @committed = true
- @cv.broadcast
- end
- end
- def committed?
- @committed
- end
- # Sets the HTTP status code.
- def status=(status)
- @status = Rack::Utils.status_code(status)
- end
- def content_type=(content_type)
- @content_type = content_type.to_s
- end
- # The response code of the request.
- def response_code
- @status
- end
- # Returns a string to ensure compatibility with <tt>Net::HTTPResponse</tt>.
- def code
- @status.to_s
- end
- # Returns the corresponding message for the current HTTP status code:
- #
- # response.status = 200
- # response.message # => "OK"
- #
- # response.status = 404
- # response.message # => "Not Found"
- #
- def message
- Rack::Utils::HTTP_STATUS_CODES[@status]
- end
- alias_method :status_message, :message
- def respond_to?(method)
- if method.to_s == 'to_path'
- stream.respond_to?(:to_path)
- else
- super
- end
- end
- def to_path
- stream.to_path
- end
- # Returns the content of the response as a string. This contains the contents
- # of any calls to <tt>render</tt>.
- def body
- strings = []
- each { |part| strings << part.to_s }
- strings.join
- end
- EMPTY = " "
- # Allows you to manually set or override the response body.
- def body=(body)
- @blank = true if body == EMPTY
- if body.respond_to?(:to_path)
- @stream = body
- else
- @stream = build_buffer self, munge_body_object(body)
- end
- end
- def body_parts
- parts = []
- @stream.each { |x| parts << x }
- parts
- end
- def set_cookie(key, value)
- ::Rack::Utils.set_cookie_header!(header, key, value)
- end
- def delete_cookie(key, value={})
- ::Rack::Utils.delete_cookie_header!(header, key, value)
- end
- def location
- headers[LOCATION]
- end
- alias_method :redirect_url, :location
- def location=(url)
- headers[LOCATION] = url
- end
- def close
- stream.close if stream.respond_to?(:close)
- end
- def to_a
- rack_response @status, @header.to_hash
- end
- alias prepare! to_a
- alias to_ary to_a # For implicit splat on 1.9.2
- # Returns the response cookies, converted to a Hash of (name => value) pairs
- #
- # assert_equal 'AuthorOfNewPage', r.cookies['author']
- def cookies
- cookies = {}
- if header = self[SET_COOKIE]
- header = header.split("\n") if header.respond_to?(:to_str)
- header.each do |cookie|
- if pair = cookie.split(';').first
- key, value = pair.split("=").map { |v| Rack::Utils.unescape(v) }
- cookies[key] = value
- end
- end
- end
- cookies
- end
- private
- def merge_default_headers(original, default)
- return original unless default.respond_to?(:merge)
- default.merge(original)
- end
- def build_buffer(response, body)
- Buffer.new response, body
- end
- def munge_body_object(body)
- body.respond_to?(:each) ? body : [body]
- end
- def assign_default_content_type_and_charset!(headers)
- return if headers[CONTENT_TYPE].present?
- @content_type ||= Mime::HTML
- @charset ||= self.class.default_charset unless @charset == false
- type = @content_type.to_s.dup
- type << "; charset=#{@charset}" if append_charset?
- headers[CONTENT_TYPE] = type
- end
- def append_charset?
- !@sending_file && @charset != false
- end
- def rack_response(status, header)
- assign_default_content_type_and_charset!(header)
- handle_conditional_get!
- header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join)
- if [204, 304].include?(@status)
- header.delete CONTENT_TYPE
- [status, header, []]
- else
- [status, header, self]
- end
- end
- end
- end
- module ActionDispatch
- module Http
- # Models uploaded files.
- #
- # 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 not need to
- # clean them with a separate maintenance task.
- class UploadedFile
- # The basename of the file in the client.
- attr_accessor :original_filename
- # 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.
- attr_accessor :tempfile
- # A string with the headers of the multipart request.
- attr_accessor :headers
- def initialize(hash) # :nodoc:
- @tempfile = hash[:tempfile]
- raise(ArgumentError, ':tempfile is required') unless @tempfile
- @original_filename = encode_filename(hash[:filename])
- @content_type = hash[:type]
- @headers = hash[:head]
- end
- # Shortcut for +tempfile.read+.
- def read(length=nil, buffer=nil)
- @tempfile.read(length, buffer)
- end
- # Shortcut for +tempfile.open+.
- def open
- @tempfile.open
- end
- # Shortcut for +tempfile.close+.
- def close(unlink_now=false)
- @tempfile.close(unlink_now)
- end
- # Shortcut for +tempfile.path+.
- def path
- @tempfile.path
- end
- # Shortcut for +tempfile.rewind+.
- def rewind
- @tempfile.rewind
- end
- # Shortcut for +tempfile.size+.
- def size
- @tempfile.size
- end
- # Shortcut for +tempfile.eof?+.
- def eof?
- @tempfile.eof?
- end
- private
- def encode_filename(filename)
- # Encode the filename in the utf8 encoding, unless it is nil
- filename.force_encoding(Encoding::UTF_8).encode! if filename
- end
- end
- module Upload # :nodoc:
- # Convert nested Hash to ActiveSupport::HashWithIndifferentAccess and replace
- # file upload hash with UploadedFile objects
- def normalize_parameters(value)
- if Hash === value && value.has_key?(:tempfile)
- UploadedFile.new(value)
- else
- super
- end
- end
- private :normalize_parameters
- end
- end
- end
- module ActionDispatch
- module Http
- module URL
- IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
- mattr_accessor :tld_length
- self.tld_length = 1
- class << self
- def extract_domain(host, tld_length = @@tld_length)
- host.split('.').last(1 + tld_length).join('.') if named_host?(host)
- end
- def extract_subdomains(host, tld_length = @@tld_length)
- if named_host?(host)
- parts = host.split('.')
- parts[0..-(tld_length + 2)]
- else
- []
- end
- end
- def extract_subdomain(host, tld_length = @@tld_length)
- extract_subdomains(host, tld_length).join('.')
- end
- def url_for(options = {})
- path = options.delete(:script_name).to_s.chomp("/")
- path << options.delete(:path).to_s
- params = options[:params].is_a?(Hash) ? options[:params] : options.slice(:params)
- params.reject! { |_,v| v.to_param.nil? }
- result = build_host_url(options)
- if options[:trailing_slash]
- if path.include?('?')
- result << path.sub(/\?/, '/\&')
- else
- result << path.sub(/[^\/]\z|\A\z/, '\&/')
- end
- else
- result << path
- end
- result << "?#{params.to_query}" unless params.empty?
- result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
- result
- end
- private
- def build_host_url(options)
- if options[:host].blank? && options[:only_path].blank?
- raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
- end
- result = ""
- unless options[:only_path]
- unless options[:protocol] == false
- result << (options[:protocol] || "http")
- result << ":" unless result.match(%r{:|//})
- end
- result << "//" unless result.match("//")
- result << rewrite_authentication(options)
- result << host_or_subdomain_and_domain(options)
- result << ":#{options.delete(:port)}" if options[:port]
- end
- result
- end
- def named_host?(host)
- host && IP_HOST_REGEXP !~ host
- end
- def rewrite_authentication(options)
- if options[:user] && options[:password]
- "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
- else
- ""
- end
- end
- def host_or_subdomain_and_domain(options)
- return options[:host] if !named_host?(options[:host]) || (options[:subdomain].nil? && options[:domain].nil?)
- tld_length = options[:tld_length] || @@tld_length
- host = ""
- unless options[:subdomain] == false
- host << (options[:subdomain] || extract_subdomain(options[:host], tld_length)).to_param
- host << "."
- end
- host << (options[:domain] || extract_domain(options[:host], tld_length))
- host
- end
- end
- def initialize(env)
- super
- @protocol = nil
- @port = nil
- end
- # Returns the complete URL used for this request.
- def url
- protocol + host_with_port + fullpath
- end
- # Returns 'https://' if this is an SSL request and 'http://' otherwise.
- def protocol
- @protocol ||= ssl? ? 'https://' : 'http://'
- end
- # Returns the \host for this request, such as "example.com".
- def raw_host_with_port
- if forwarded = env["HTTP_X_FORWARDED_HOST"]
- forwarded.split(/,\s?/).last
- else
- env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
- end
- end
- # Returns the host for this request, such as 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".
- def host_with_port
- "#{host}#{port_string}"
- end
- # Returns the port number of this request as an integer.
- def port
- @port ||= begin
- if raw_host_with_port =~ /:(\d+)$/
- $1.to_i
- else
- standard_port
- end
- end
- end
- # Returns the standard \port number for this request's protocol.
- def standard_port
- case protocol
- when 'https://' then 443
- else 80
- end
- end
- # Returns whether this request is using the standard port
- 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.
- 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.
- def port_string
- standard_port? ? '' : ":#{port}"
- end
- def server_port
- @env['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".
- 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".
- 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".
- def subdomain(tld_length = @@tld_length)
- ActionDispatch::Http::URL.extract_subdomain(host, tld_length)
- end
- end
- end
- end
- module Rack # :nodoc:
- Mount = ActionDispatch::Journey::Router
- Mount::RouteSet = ActionDispatch::Journey::Router
- Mount::RegexpWithNamedGroups = ActionDispatch::Journey::Path::Pattern
- end
- require 'action_controller/metal/exceptions'
- module ActionDispatch
- module Journey
- # The Formatter class is used for formatting URLs. For example, parameters
- # passed to +url_for+ in rails will eventually call Formatter#generate.
- class Formatter # :nodoc:
- attr_reader :routes
- def initialize(routes)
- @routes = routes
- @cache = nil
- end
- def generate(type, name, options, recall = {}, parameterize = nil)
- constraints = recall.merge(options)
- missing_keys = []
- match_route(name, constraints) do |route|
- parameterized_parts = extract_parameterized_parts(route, options, recall, parameterize)
- next if !name && route.requirements.empty? && route.parts.empty?
- missing_keys = missing_keys(route, parameterized_parts)
- next unless missing_keys.empty?
- params = options.dup.delete_if do |key, _|
- parameterized_parts.key?(key) || route.defaults.key?(key)
- end
- return [route.format(parameterized_parts), params]
- end
- message = "No route matches #{constraints.inspect}"
- message << " missing required keys: #{missing_keys.inspect}" if name
- raise ActionController::UrlGenerationError, message
- end
- def clear
- @cache = nil
- end
- private
- def extract_parameterized_parts(route, options, recall, parameterize = nil)
- parameterized_parts = recall.merge(options)
- keys_to_keep = route.parts.reverse.drop_while { |part|
- !options.key?(part) || (options[part] || recall[part]).nil?
- } | route.required_parts
- (parameterized_parts.keys - keys_to_keep).each do |bad_key|
- parameterized_parts.delete(bad_key)
- end
- if parameterize
- parameterized_parts.each do |k, v|
- parameterized_parts[k] = parameterize.call(k, v)
- end
- end
- parameterized_parts.keep_if { |_, v| v }
- parameterized_parts
- end
- def named_routes
- routes.named_routes
- end
- def match_route(name, options)
- if named_routes.key?(name)
- yield named_routes[name]
- else
- routes = non_recursive(cache, options.to_a)
- hash = routes.group_by { |_, r| r.score(options) }
- hash.keys.sort.reverse_each do |score|
- next if score < 0
- hash[score].sort_by { |i, _| i }.each do |_, route|
- yield route
- end
- end
- end
- end
- def non_recursive(cache, options)
- routes = []
- stack = [cache]
- while stack.any?
- c = stack.shift
- routes.concat(c[:___routes]) if c.key?(:___routes)
- options.each do |pair|
- stack << c[pair] if c.key?(pair)
- end
- end
- routes
- end
- # Returns an array populated with missing keys if any are present.
- def missing_keys(route, parts)
- missing_keys = []
- tests = route.path.requirements
- route.required_parts.each { |key|
- if tests.key?(key)
- missing_keys << key unless /\A#{tests[key]}\Z/ === parts[key]
- else
- missing_keys << key unless parts[key]
- end
- }
- missing_keys
- end
- def possibles(cache, options, depth = 0)
- cache.fetch(:___routes) { [] } + options.find_all { |pair|
- cache.key?(pair)
- }.map { |pair|
- possibles(cache[pair], options, depth + 1)
- }.flatten(1)
- end
- # Returns +true+ if no missing keys are present, otherwise +false+.
- def verify_required_parts!(route, parts)
- missing_keys(route, parts).empty?
- end
- def build_cache
- root = { ___routes: [] }
- routes.each_with_index do |route, i|
- leaf = route.required_defaults.inject(root) do |h, tuple|
- h[tuple] ||= {}
- end
- (leaf[:___routes] ||= []) << [i, route]
- end
- root
- end
- def cache
- @cache ||= build_cache
- end
- end
- end
- end
- require 'action_dispatch/journey/gtg/transition_table'
- module ActionDispatch
- module Journey # :nodoc:
- module GTG # :nodoc:
- class Builder # :nodoc:
- DUMMY = Nodes::Dummy.new
- attr_reader :root, :ast, :endpoints
- def initialize(root)
- @root = root
- @ast = Nodes::Cat.new root, DUMMY
- @followpos = nil
- end
- def transition_table
- dtrans = TransitionTable.new
- marked = {}
- state_id = Hash.new { |h,k| h[k] = h.length }
- start = firstpos(root)
- dstates = [start]
- until dstates.empty?
- s = dstates.shift
- next if marked[s]
- marked[s] = true # mark s
- s.group_by { |state| symbol(state) }.each do |sym, ps|
- u = ps.map { |l| followpos(l) }.flatten
- next if u.empty?
- if u.uniq == [DUMMY]
- from = state_id[s]
- to = state_id[Object.new]
- dtrans[from, to] = sym
- dtrans.add_accepting(to)
- ps.each { |state| dtrans.add_memo(to, state.memo) }
- else
- dtrans[state_id[s], state_id[u]] = sym
- if u.include?(DUMMY)
- to = state_id[u]
- accepting = ps.find_all { |l| followpos(l).include?(DUMMY) }
- accepting.each { |accepting_state|
- dtrans.add_memo(to, accepting_state.memo)
- }
- dtrans.add_accepting(state_id[u])
- end
- end
- dstates << u
- end
- end
- dtrans
- end
- def nullable?(node)
- case node
- when Nodes::Group
- true
- when Nodes::Star
- true
- when Nodes::Or
- node.children.any? { |c| nullable?(c) }
- when Nodes::Cat
- nullable?(node.left) && nullable?(node.right)
- when Nodes::Terminal
- !node.left
- when Nodes::Unary
- nullable?(node.left)
- else
- raise ArgumentError, 'unknown nullable: %s' % node.class.name
- end
- end
- def firstpos(node)
- case node
- when Nodes::Star
- firstpos(node.left)
- when Nodes::Cat
- if nullable?(node.left)
- firstpos(node.left) | firstpos(node.right)
- else
- firstpos(node.left)
- end
- when Nodes::Or
- node.children.map { |c| firstpos(c) }.flatten.uniq
- when Nodes::Unary
- firstpos(node.left)
- when Nodes::Terminal
- nullable?(node) ? [] : [node]
- else
- raise ArgumentError, 'unknown firstpos: %s' % node.class.name
- end
- end
- def lastpos(node)
- case node
- when Nodes::Star
- firstpos(node.left)
- when Nodes::Or
- node.children.map { |c| lastpos(c) }.flatten.uniq
- when Nodes::Cat
- if nullable?(node.right)
- lastpos(node.left) | lastpos(node.right)
- else
- lastpos(node.right)
- end
- when Nodes::Terminal
- nullable?(node) ? [] : [node]
- when Nodes::Unary
- lastpos(node.left)
- else
- raise ArgumentError, 'unknown lastpos: %s' % node.class.name
- end
- end
- def followpos(node)
- followpos_table[node]
- end
- private
- def followpos_table
- @followpos ||= build_followpos
- end
- def build_followpos
- table = Hash.new { |h, k| h[k] = [] }
- @ast.each do |n|
- case n
- when Nodes::Cat
- lastpos(n.left).each do |i|
- table[i] += firstpos(n.right)
- end
- when Nodes::Star
- lastpos(n).each do |i|
- table[i] += firstpos(n)
- end
- end
- end
- table
- end
- def symbol(edge)
- case edge
- when Journey::Nodes::Symbol
- edge.regexp
- else
- edge.left
- end
- end
- end
- end
- end
- end
- require 'strscan'
- module ActionDispatch
- module Journey # :nodoc:
- module GTG # :nodoc:
- class MatchData # :nodoc:
- attr_reader :memos
- def initialize(memos)
- @memos = memos
- end
- end
- class Simulator # :nodoc:
- attr_reader :tt
- def initialize(transition_table)
- @tt = transition_table
- end
- def simulate(string)
- input = StringScanner.new(string)
- state = [0]
- while sym = input.scan(%r([/.?]|[^/.?]+))
- state = tt.move(state, sym)
- end
- acceptance_states = state.find_all { |s|
- tt.accepting? s
- }
- return if acceptance_states.empty?
- memos = acceptance_states.map { |x| tt.memo(x) }.flatten.compact
- MatchData.new(memos)
- end
- alias :=~ :simulate
- alias :match :simulate
- end
- end
- end
- end
- require 'action_dispatch/journey/nfa/dot'
- module ActionDispatch
- module Journey # :nodoc:
- module GTG # :nodoc:
- class TransitionTable # :nodoc:
- include Journey::NFA::Dot
- attr_reader :memos
- def initialize
- @regexp_states = Hash.new { |h,k| h[k] = {} }
- @string_states = Hash.new { |h,k| h[k] = {} }
- @accepting = {}
- @memos = Hash.new { |h,k| h[k] = [] }
- end
- def add_accepting(state)
- @accepting[state] = true
- end
- def accepting_states
- @accepting.keys
- end
- def accepting?(state)
- @accepting[state]
- end
- def add_memo(idx, memo)
- @memos[idx] << memo
- end
- def memo(idx)
- @memos[idx]
- end
- def eclosure(t)
- Array(t)
- end
- def move(t, a)
- move_string(t, a).concat(move_regexp(t, a))
- end
- def to_json
- require 'json'
- simple_regexp = Hash.new { |h,k| h[k] = {} }
- @regexp_states.each do |from, hash|
- hash.each do |re, to|
- simple_regexp[from][re.source] = to
- end
- end
- JSON.dump({
- regexp_states: simple_regexp,
- string_states: @string_states,
- accepting: @accepting
- })
- end
- def to_svg
- svg = IO.popen('dot -Tsvg', 'w+') { |f|
- f.write(to_dot)
- f.close_write
- f.readlines
- }
- 3.times { svg.shift }
- svg.join.sub(/width="[^"]*"/, '').sub(/height="[^"]*"/, '')
- end
- def visualizer(paths, title = 'FSM')
- viz_dir = File.join File.dirname(__FILE__), '..', 'visualizer'
- fsm_js = File.read File.join(viz_dir, 'fsm.js')
- fsm_css = File.read File.join(viz_dir, 'fsm.css')
- erb = File.read File.join(viz_dir, 'index.html.erb')
- states = "function tt() { return #{to_json}; }"
- fun_routes = paths.shuffle.first(3).map do |ast|
- ast.map { |n|
- case n
- when Nodes::Symbol
- case n.left
- when ':id' then rand(100).to_s
- when ':format' then %w{ xml json }.shuffle.first
- else
- 'omg'
- end
- when Nodes::Terminal then n.symbol
- else
- nil
- end
- }.compact.join
- end
- stylesheets = [fsm_css]
- svg = to_svg
- javascripts = [states, fsm_js]
- # Annoying hack for 1.9 warnings
- fun_routes = fun_routes
- stylesheets = stylesheets
- svg = svg
- javascripts = javascripts
- require 'erb'
- template = ERB.new erb
- template.result(binding)
- end
- def []=(from, to, sym)
- case sym
- when String
- @string_states[from][sym] = to
- when Regexp
- @regexp_states[from][sym] = to
- else
- raise ArgumentError, 'unknown symbol: %s' % sym.class
- end
- end
- def states
- ss = @string_states.keys + @string_states.values.map(&:values).flatten
- rs = @regexp_states.keys + @regexp_states.values.map(&:values).flatten
- (ss + rs).uniq
- end
- def transitions
- @string_states.map { |from, hash|
- hash.map { |s, to| [from, s, to] }
- }.flatten(1) + @regexp_states.map { |from, hash|
- hash.map { |s, to| [from, s, to] }
- }.flatten(1)
- end
- private
- def move_regexp(t, a)
- return [] if t.empty?
- t.map { |s|
- @regexp_states[s].map { |re, v| re === a ? v : nil }
- }.flatten.compact.uniq
- end
- def move_string(t, a)
- return [] if t.empty?
- t.map { |s| @string_states[s][a] }.compact
- end
- end
- end
- end
- end
- require 'action_dispatch/journey/nfa/transition_table'
- require 'action_dispatch/journey/gtg/transition_table'
- module ActionDispatch
- module Journey # :nodoc:
- module NFA # :nodoc:
- class Visitor < Visitors::Visitor # :nodoc:
- def initialize(tt)
- @tt = tt
- @i = -1
- end
- def visit_CAT(node)
- left = visit(node.left)
- right = visit(node.right)
- @tt.merge(left.last, right.first)
- [left.first, right.last]
- end
- def visit_GROUP(node)
- from = @i += 1
- left = visit(node.left)
- to = @i += 1
- @tt.accepting = to
- @tt[from, left.first] = nil
- @tt[left.last, to] = nil
- @tt[from, to] = nil
- [from, to]
- end
- def visit_OR(node)
- from = @i += 1
- children = node.children.map { |c| visit(c) }
- to = @i += 1
- children.each do |child|
- @tt[from, child.first] = nil
- @tt[child.last, to] = nil
- end
- @tt.accepting = to
- [from, to]
- end
- def terminal(node)
- from_i = @i += 1 # new state
- to_i = @i += 1 # new state
- @tt[from_i, to_i] = node
- @tt.accepting = to_i
- @tt.add_memo(to_i, node.memo)
- [from_i, to_i]
- end
- end
- class Builder # :nodoc:
- def initialize(ast)
- @ast = ast
- end
- def transition_table
- tt = TransitionTable.new
- Visitor.new(tt).accept(@ast)
- tt
- end
- end
- end
- end
- end
- # encoding: utf-8
- module ActionDispatch
- module Journey # :nodoc:
- module NFA # :nodoc:
- module Dot # :nodoc:
- def to_dot
- edges = transitions.map { |from, sym, to|
- " #{from} -> #{to} [label=\"#{sym || 'ε'}\"];"
- }
- #memo_nodes = memos.values.flatten.map { |n|
- # label = n
- # if Journey::Route === n
- # label = "#{n.verb.source} #{n.path.spec}"
- # end
- # " #{n.object_id} [label=\"#{label}\", shape=box];"
- #}
- #memo_edges = memos.map { |k, memos|
- # (memos || []).map { |v| " #{k} -> #{v.object_id};" }
- #}.flatten.uniq
- <<-eodot
- digraph nfa {
- rankdir=LR;
- node [shape = doublecircle];
- #{accepting_states.join ' '};
- node [shape = circle];
- #{edges.join "\n"}
- }
- eodot
- end
- end
- end
- end
- end
- require 'strscan'
- module ActionDispatch
- module Journey # :nodoc:
- module NFA # :nodoc:
- class MatchData # :nodoc:
- attr_reader :memos
- def initialize(memos)
- @memos = memos
- end
- end
- class Simulator # :nodoc:
- attr_reader :tt
- def initialize(transition_table)
- @tt = transition_table
- end
- def simulate(string)
- input = StringScanner.new(string)
- state = tt.eclosure(0)
- until input.eos?
- sym = input.scan(%r([/.?]|[^/.?]+))
- # FIXME: tt.eclosure is not needed for the GTG
- state = tt.eclosure(tt.move(state, sym))
- end
- acceptance_states = state.find_all { |s|
- tt.accepting?(tt.eclosure(s).sort.last)
- }
- return if acceptance_states.empty?
- memos = acceptance_states.map { |x| tt.memo(x) }.flatten.compact
- MatchData.new(memos)
- end
- alias :=~ :simulate
- alias :match :simulate
- end
- end
- end
- end
- require 'action_dispatch/journey/nfa/dot'
- module ActionDispatch
- module Journey # :nodoc:
- module NFA # :nodoc:
- class TransitionTable # :nodoc:
- include Journey::NFA::Dot
- attr_accessor :accepting
- attr_reader :memos
- def initialize
- @table = Hash.new { |h,f| h[f] = {} }
- @memos = {}
- @accepting = nil
- @inverted = nil
- end
- def accepting?(state)
- accepting == state
- end
- def accepting_states
- [accepting]
- end
- def add_memo(idx, memo)
- @memos[idx] = memo
- end
- def memo(idx)
- @memos[idx]
- end
- def []=(i, f, s)
- @table[f][i] = s
- end
- def merge(left, right)
- @memos[right] = @memos.delete(left)
- @table[right] = @table.delete(left)
- end
- def states
- (@table.keys + @table.values.map(&:keys).flatten).uniq
- end
- # Returns a generalized transition graph with reduced states. The states
- # are reduced like a DFA, but the table must be simulated like an NFA.
- #
- # Edges of the GTG are regular expressions.
- def generalized_table
- gt = GTG::TransitionTable.new
- marked = {}
- state_id = Hash.new { |h,k| h[k] = h.length }
- alphabet = self.alphabet
- stack = [eclosure(0)]
- until stack.empty?
- state = stack.pop
- next if marked[state] || state.empty?
- marked[state] = true
- alphabet.each do |alpha|
- next_state = eclosure(following_states(state, alpha))
- next if next_state.empty?
- gt[state_id[state], state_id[next_state]] = alpha
- stack << next_state
- end
- end
- final_groups = state_id.keys.find_all { |s|
- s.sort.last == accepting
- }
- final_groups.each do |states|
- id = state_id[states]
- gt.add_accepting(id)
- save = states.find { |s|
- @memos.key?(s) && eclosure(s).sort.last == accepting
- }
- gt.add_memo(id, memo(save))
- end
- gt
- end
- # Returns set of NFA states to which there is a transition on ast symbol
- # +a+ from some state +s+ in +t+.
- def following_states(t, a)
- Array(t).map { |s| inverted[s][a] }.flatten.uniq
- end
- # Returns set of NFA states to which there is a transition on ast symbol
- # +a+ from some state +s+ in +t+.
- def move(t, a)
- Array(t).map { |s|
- inverted[s].keys.compact.find_all { |sym|
- sym === a
- }.map { |sym| inverted[s][sym] }
- }.flatten.uniq
- end
- def alphabet
- inverted.values.map(&:keys).flatten.compact.uniq.sort_by { |x| x.to_s }
- end
- # Returns a set of NFA states reachable from some NFA state +s+ in set
- # +t+ on nil-transitions alone.
- def eclosure(t)
- stack = Array(t)
- seen = {}
- children = []
- until stack.empty?
- s = stack.pop
- next if seen[s]
- seen[s] = true
- children << s
- stack.concat(inverted[s][nil])
- end
- children.uniq
- end
- def transitions
- @table.map { |to, hash|
- hash.map { |from, sym| [from, sym, to] }
- }.flatten(1)
- end
- private
- def inverted
- return @inverted if @inverted
- @inverted = Hash.new { |h, from|
- h[from] = Hash.new { |j, s| j[s] = [] }
- }
- @table.each { |to, hash|
- hash.each { |from, sym|
- if sym
- sym = Nodes::Symbol === sym ? sym.regexp : sym.left
- end
- @inverted[from][sym] << to
- }
- }
- @inverted
- end
- end
- end
- end
- end
- require 'action_dispatch/journey/visitors'
- module ActionDispatch
- module Journey # :nodoc:
- module Nodes # :nodoc:
- class Node # :nodoc:
- include Enumerable
- attr_accessor :left, :memo
- def initialize(left)
- @left = left
- @memo = nil
- end
- def each(&block)
- Visitors::Each.new(block).accept(self)
- end
- def to_s
- Visitors::String.new.accept(self)
- end
- def to_dot
- Visitors::Dot.new.accept(self)
- end
- def to_sym
- name.to_sym
- end
- def name
- left.tr '*:', ''
- end
- def type
- raise NotImplementedError
- end
- def symbol?; false; end
- def literal?; false; end
- end
- class Terminal < Node # :nodoc:
- alias :symbol :left
- end
- class Literal < Terminal # :nodoc:
- def literal?; true; end
- def type; :LITERAL; end
- end
- class Dummy < Literal # :nodoc:
- def initialize(x = Object.new)
- super
- end
- def literal?; false; end
- end
- %w{ Symbol Slash Dot }.each do |t|
- class_eval <<-eoruby, __FILE__, __LINE__ + 1
- class #{t} < Terminal;
- def type; :#{t.upcase}; end
- end
- eoruby
- end
- class Symbol < Terminal # :nodoc:
- attr_accessor :regexp
- alias :symbol :regexp
- DEFAULT_EXP = /[^\.\/\?]+/
- def initialize(left)
- super
- @regexp = DEFAULT_EXP
- end
- def default_regexp?
- regexp == DEFAULT_EXP
- end
- def symbol?; true; end
- end
- class Unary < Node # :nodoc:
- def children; [left] end
- end
- class Group < Unary # :nodoc:
- def type; :GROUP; end
- end
- class Star < Unary # :nodoc:
- def type; :STAR; end
- end
- class Binary < Node # :nodoc:
- attr_accessor :right
- def initialize(left, right)
- super(left)
- @right = right
- end
- def children; [left, right] end
- end
- class Cat < Binary # :nodoc:
- def type; :CAT; end
- end
- class Or < Node # :nodoc:
- attr_reader :children
- def initialize(children)
- @children = children
- end
- def type; :OR; end
- end
- end
- end
- end
- #
- # DO NOT MODIFY!!!!
- # This file is automatically generated by Racc 1.4.9
- # from Racc grammer file "".
- #
- require 'racc/parser.rb'
- require 'action_dispatch/journey/parser_extras'
- module ActionDispatch
- module Journey # :nodoc:
- class Parser < Racc::Parser # :nodoc:
- ##### State transition tables begin ###
- racc_action_table = [
- 17, 21, 13, 15, 14, 7, nil, 16, 8, 19,
- 13, 15, 14, 7, 23, 16, 8, 19, 13, 15,
- 14, 7, nil, 16, 8, 13, 15, 14, 7, nil,
- 16, 8, 13, 15, 14, 7, nil, 16, 8 ]
- racc_action_check = [
- 1, 17, 1, 1, 1, 1, nil, 1, 1, 1,
- 20, 20, 20, 20, 20, 20, 20, 20, 7, 7,
- 7, 7, nil, 7, 7, 19, 19, 19, 19, nil,
- 19, 19, 0, 0, 0, 0, nil, 0, 0 ]
- racc_action_pointer = [
- 30, 0, nil, nil, nil, nil, nil, 16, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, 1, nil, 23,
- 8, nil, nil, nil ]
- racc_action_default = [
- -18, -18, -2, -3, -4, -5, -6, -18, -9, -10,
- -11, -12, -13, -14, -15, -16, -17, -18, -1, -18,
- -18, 24, -8, -7 ]
- racc_goto_table = [
- 18, 1, nil, nil, nil, nil, nil, nil, 20, nil,
- nil, nil, nil, nil, nil, nil, nil, nil, 22, 18 ]
- racc_goto_check = [
- 2, 1, nil, nil, nil, nil, nil, nil, 1, nil,
- nil, nil, nil, nil, nil, nil, nil, nil, 2, 2 ]
- racc_goto_pointer = [
- nil, 1, -1, nil, nil, nil, nil, nil, nil, nil,
- nil ]
- racc_goto_default = [
- nil, nil, 2, 3, 4, 5, 6, 9, 10, 11,
- 12 ]
- racc_reduce_table = [
- 0, 0, :racc_error,
- 2, 11, :_reduce_1,
- 1, 11, :_reduce_2,
- 1, 11, :_reduce_none,
- 1, 12, :_reduce_none,
- 1, 12, :_reduce_none,
- 1, 12, :_reduce_none,
- 3, 15, :_reduce_7,
- 3, 13, :_reduce_8,
- 1, 16, :_reduce_9,
- 1, 14, :_reduce_none,
- 1, 14, :_reduce_none,
- 1, 14, :_reduce_none,
- 1, 14, :_reduce_none,
- 1, 19, :_reduce_14,
- 1, 17, :_reduce_15,
- 1, 18, :_reduce_16,
- 1, 20, :_reduce_17 ]
- racc_reduce_n = 18
- racc_shift_n = 24
- racc_token_table = {
- false => 0,
- :error => 1,
- :SLASH => 2,
- :LITERAL => 3,
- :SYMBOL => 4,
- :LPAREN => 5,
- :RPAREN => 6,
- :DOT => 7,
- :STAR => 8,
- :OR => 9 }
- racc_nt_base = 10
- racc_use_result_var = true
- Racc_arg = [
- racc_action_table,
- racc_action_check,
- racc_action_default,
- racc_action_pointer,
- racc_goto_table,
- racc_goto_check,
- racc_goto_default,
- racc_goto_pointer,
- racc_nt_base,
- racc_reduce_table,
- racc_token_table,
- racc_shift_n,
- racc_reduce_n,
- racc_use_result_var ]
- Racc_token_to_s_table = [
- "$end",
- "error",
- "SLASH",
- "LITERAL",
- "SYMBOL",
- "LPAREN",
- "RPAREN",
- "DOT",
- "STAR",
- "OR",
- "$start",
- "expressions",
- "expression",
- "or",
- "terminal",
- "group",
- "star",
- "symbol",
- "literal",
- "slash",
- "dot" ]
- Racc_debug_parser = false
- ##### State transition tables end #####
- # reduce 0 omitted
- def _reduce_1(val, _values, result)
- result = Cat.new(val.first, val.last)
- result
- end
- def _reduce_2(val, _values, result)
- result = val.first
- result
- end
- # reduce 3 omitted
- # reduce 4 omitted
- # reduce 5 omitted
- # reduce 6 omitted
- def _reduce_7(val, _values, result)
- result = Group.new(val[1])
- result
- end
- def _reduce_8(val, _values, result)
- result = Or.new([val.first, val.last])
- result
- end
- def _reduce_9(val, _values, result)
- result = Star.new(Symbol.new(val.last))
- result
- end
- # reduce 10 omitted
- # reduce 11 omitted
- # reduce 12 omitted
- # reduce 13 omitted
- def _reduce_14(val, _values, result)
- result = Slash.new('/')
- result
- end
- def _reduce_15(val, _values, result)
- result = Symbol.new(val.first)
- result
- end
- def _reduce_16(val, _values, result)
- result = Literal.new(val.first)
- result
- end
- def _reduce_17(val, _values, result)
- result = Dot.new(val.first)
- result
- end
- def _reduce_none(val, _values, result)
- val[0]
- end
- end # class Parser
- end # module Journey
- end # module ActionDispatch
- require 'action_dispatch/journey/scanner'
- require 'action_dispatch/journey/nodes/node'
- module ActionDispatch
- module Journey # :nodoc:
- class Parser < Racc::Parser # :nodoc:
- include Journey::Nodes
- def initialize
- @scanner = Scanner.new
- end
- def parse(string)
- @scanner.scan_setup(string)
- do_parse
- end
- def next_token
- @scanner.next_token
- end
- end
- end
- end
- module ActionDispatch
- module Journey # :nodoc:
- module Path # :nodoc:
- class Pattern # :nodoc:
- attr_reader :spec, :requirements, :anchored
- def initialize(strexp)
- parser = Journey::Parser.new
- @anchored = true
- case strexp
- when String
- @spec = parser.parse(strexp)
- @requirements = {}
- @separators = "/.?"
- when Router::Strexp
- @spec = parser.parse(strexp.path)
- @requirements = strexp.requirements
- @separators = strexp.separators.join
- @anchored = strexp.anchor
- else
- raise "wtf bro: #{strexp}"
- end
- @names = nil
- @optional_names = nil
- @required_names = nil
- @re = nil
- @offsets = nil
- end
- def ast
- @spec.grep(Nodes::Symbol).each do |node|
- re = @requirements[node.to_sym]
- node.regexp = re if re
- end
- @spec.grep(Nodes::Star).each do |node|
- node = node.left
- node.regexp = @requirements[node.to_sym] || /(.+)/
- end
- @spec
- end
- def names
- @names ||= spec.grep(Nodes::Symbol).map { |n| n.name }
- end
- def required_names
- @required_names ||= names - optional_names
- end
- def optional_names
- @optional_names ||= spec.grep(Nodes::Group).map { |group|
- group.grep(Nodes::Symbol)
- }.flatten.map { |n| n.name }.uniq
- end
- class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
- attr_reader :offsets
- def initialize(matchers)
- @matchers = matchers
- @capture_count = [0]
- end
- def visit(node)
- super
- @capture_count
- end
- def visit_SYMBOL(node)
- node = node.to_sym
- if @matchers.key?(node)
- re = /#{@matchers[node]}|/
- @capture_count.push((re.match('').length - 1) + (@capture_count.last || 0))
- else
- @capture_count << (@capture_count.last || 0)
- end
- end
- end
- class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc:
- def initialize(separator, matchers)
- @separator = separator
- @matchers = matchers
- @separator_re = "([^#{separator}]+)"
- super()
- end
- def accept(node)
- %r{\A#{visit node}\Z}
- end
- def visit_CAT(node)
- [visit(node.left), visit(node.right)].join
- end
- def visit_SYMBOL(node)
- node = node.to_sym
- return @separator_re unless @matchers.key?(node)
- re = @matchers[node]
- "(#{re})"
- end
- def visit_GROUP(node)
- "(?:#{visit node.left})?"
- end
- def visit_LITERAL(node)
- Regexp.escape(node.left)
- end
- alias :visit_DOT :visit_LITERAL
- def visit_SLASH(node)
- node.left
- end
- def visit_STAR(node)
- re = @matchers[node.left.to_sym] || '.+'
- "(#{re})"
- end
- end
- class UnanchoredRegexp < AnchoredRegexp # :nodoc:
- def accept(node)
- %r{\A#{visit node}}
- end
- end
- class MatchData # :nodoc:
- attr_reader :names
- def initialize(names, offsets, match)
- @names = names
- @offsets = offsets
- @match = match
- end
- def captures
- (length - 1).times.map { |i| self[i + 1] }
- end
- def [](x)
- idx = @offsets[x - 1] + x
- @match[idx]
- end
- def length
- @offsets.length
- end
- def post_match
- @match.post_match
- end
- def to_s
- @match.to_s
- end
- end
- def match(other)
- return unless match = to_regexp.match(other)
- MatchData.new(names, offsets, match)
- end
- alias :=~ :match
- def source
- to_regexp.source
- end
- def to_regexp
- @re ||= regexp_visitor.new(@separators, @requirements).accept spec
- end
- private
- def regexp_visitor
- @anchored ? AnchoredRegexp : UnanchoredRegexp
- end
- def offsets
- return @offsets if @offsets
- viz = RegexpOffsets.new(@requirements)
- @offsets = viz.accept(spec)
- end
- end
- end
- end
- end
- module ActionDispatch
- module Journey # :nodoc:
- class Route # :nodoc:
- attr_reader :app, :path, :defaults, :name
- attr_reader :constraints
- alias :conditions :constraints
- attr_accessor :precedence
- ##
- # +path+ is a path constraint.
- # +constraints+ is a hash of constraints to be applied to this route.
- def initialize(name, app, path, constraints, defaults = {})
- @name = name
- @app = app
- @path = path
- @constraints = constraints
- @defaults = defaults
- @required_defaults = nil
- @required_parts = nil
- @parts = nil
- @decorated_ast = nil
- @precedence = 0
- end
- def ast
- @decorated_ast ||= begin
- decorated_ast = path.ast
- decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self }
- decorated_ast
- end
- end
- def requirements # :nodoc:
- # needed for rails `rake routes`
- path.requirements.merge(@defaults).delete_if { |_,v|
- /.+?/ == v
- }
- end
- def segments
- path.names
- end
- def required_keys
- required_parts + required_defaults.keys
- end
- def score(constraints)
- required_keys = path.required_names
- supplied_keys = constraints.map { |k,v| v && k.to_s }.compact
- return -1 unless (required_keys - supplied_keys).empty?
- score = (supplied_keys & path.names).length
- score + (required_defaults.length * 2)
- end
- def parts
- @parts ||= segments.map { |n| n.to_sym }
- end
- alias :segment_keys :parts
- def format(path_options)
- path_options.delete_if do |key, value|
- value.to_s == defaults[key].to_s && !required_parts.include?(key)
- end
- Visitors::Formatter.new(path_options).accept(path.spec)
- end
- def optional_parts
- path.optional_names.map { |n| n.to_sym }
- end
- def required_parts
- @required_parts ||= path.required_names.map { |n| n.to_sym }
- end
- def required_default?(key)
- (constraints[:required_defaults] || []).include?(key)
- end
- def required_defaults
- @required_defaults ||= @defaults.dup.delete_if do |k,_|
- parts.include?(k) || !required_default?(k)
- end
- end
- def matches?(request)
- constraints.all? do |method, value|
- next true unless request.respond_to?(method)
- case value
- when Regexp, String
- value === request.send(method).to_s
- when Array
- value.include?(request.send(method))
- else
- value === request.send(method)
- end
- end
- end
- def ip
- constraints[:ip] || //
- end
- def verb
- constraints[:request_method] || //
- end
- end
- end
- end
- module ActionDispatch
- module Journey # :nodoc:
- class Router # :nodoc:
- class Strexp # :nodoc:
- class << self
- alias :compile :new
- end
- attr_reader :path, :requirements, :separators, :anchor
- def initialize(path, requirements, separators, anchor = true)
- @path = path
- @requirements = requirements
- @separators = separators
- @anchor = anchor
- end
- def names
- @path.scan(/:\w+/).map { |s| s.tr(':', '') }
- end
- end
- end
- end
- end
- require 'uri'
- 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.
- #
- # normalize_path("/foo") # => "/foo"
- # normalize_path("/foo/") # => "/foo"
- # normalize_path("foo") # => "/foo"
- # normalize_path("") # => "/"
- def self.normalize_path(path)
- path = "/#{path}"
- path.squeeze!('/')
- path.sub!(%r{/+\Z}, '')
- path = '/' if path == ''
- path
- end
- # URI path and fragment escaping
- # http://tools.ietf.org/html/rfc3986
- module UriEscape # :nodoc:
- # Symbol captures can generate multiple path segments, so include /.
- reserved_segment = '/'
- reserved_fragment = '/?'
- reserved_pchar = ':@&=+$,;%'
- safe_pchar = "#{URI::REGEXP::PATTERN::UNRESERVED}#{reserved_pchar}"
- safe_segment = "#{safe_pchar}#{reserved_segment}"
- safe_fragment = "#{safe_pchar}#{reserved_fragment}"
- UNSAFE_SEGMENT = Regexp.new("[^#{safe_segment}]", false).freeze
- UNSAFE_FRAGMENT = Regexp.new("[^#{safe_fragment}]", false).freeze
- end
- Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI
- def self.escape_path(path)
- Parser.escape(path.to_s, UriEscape::UNSAFE_SEGMENT)
- end
- def self.escape_fragment(fragment)
- Parser.escape(fragment.to_s, UriEscape::UNSAFE_FRAGMENT)
- end
- def self.unescape_uri(uri)
- Parser.unescape(uri)
- end
- end
- end
- end
- end
- require 'action_dispatch/journey/router/utils'
- require 'action_dispatch/journey/router/strexp'
- require 'action_dispatch/journey/routes'
- require 'action_dispatch/journey/formatter'
- before = $-w
- $-w = false
- require 'action_dispatch/journey/parser'
- $-w = before
- require 'action_dispatch/journey/route'
- require 'action_dispatch/journey/path/pattern'
- module ActionDispatch
- module Journey # :nodoc:
- class Router # :nodoc:
- class RoutingError < ::StandardError # :nodoc:
- end
- # :nodoc:
- VERSION = '2.0.0'
- class NullReq # :nodoc:
- attr_reader :env
- def initialize(env)
- @env = env
- end
- def request_method
- env['REQUEST_METHOD']
- end
- def path_info
- env['PATH_INFO']
- end
- def ip
- env['REMOTE_ADDR']
- end
- def [](k); env[k]; end
- end
- attr_reader :request_class, :formatter
- attr_accessor :routes
- def initialize(routes, options)
- @options = options
- @params_key = options[:parameters_key]
- @request_class = options[:request_class] || NullReq
- @routes = routes
- end
- def call(env)
- env['PATH_INFO'] = Utils.normalize_path(env['PATH_INFO'])
- find_routes(env).each do |match, parameters, route|
- script_name, path_info, set_params = env.values_at('SCRIPT_NAME',
- 'PATH_INFO',
- @params_key)
- unless route.path.anchored
- env['SCRIPT_NAME'] = (script_name.to_s + match.to_s).chomp('/')
- env['PATH_INFO'] = match.post_match
- end
- env[@params_key] = (set_params || {}).merge parameters
- status, headers, body = route.app.call(env)
- if 'pass' == headers['X-Cascade']
- env['SCRIPT_NAME'] = script_name
- env['PATH_INFO'] = path_info
- env[@params_key] = set_params
- next
- end
- return [status, headers, body]
- end
- return [404, {'X-Cascade' => 'pass'}, ['Not Found']]
- end
- def recognize(req)
- find_routes(req.env).each do |match, parameters, route|
- unless route.path.anchored
- req.env['SCRIPT_NAME'] = match.to_s
- req.env['PATH_INFO'] = match.post_match.sub(/^([^\/])/, '/\1')
- end
- yield(route, nil, parameters)
- end
- end
- def visualizer
- tt = GTG::Builder.new(ast).transition_table
- groups = partitioned_routes.first.map(&:ast).group_by { |a| a.to_s }
- asts = groups.values.map { |v| v.first }
- tt.visualizer(asts)
- end
- private
- def partitioned_routes
- routes.partitioned_routes
- end
- def ast
- routes.ast
- end
- def simulator
- routes.simulator
- end
- def custom_routes
- partitioned_routes.last
- end
- def filter_routes(path)
- return [] unless ast
- data = simulator.match(path)
- data ? data.memos : []
- end
- def find_routes env
- req = request_class.new(env)
- routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
- r.path.match(req.path_info)
- }
- routes.concat get_routes_as_head(routes)
- routes.sort_by!(&:precedence).select! { |r| r.matches?(req) }
- routes.map! { |r|
- match_data = r.path.match(req.path_info)
- match_names = match_data.names.map { |n| n.to_sym }
- match_values = match_data.captures.map { |v| v && Utils.unescape_uri(v) }
- info = Hash[match_names.zip(match_values).find_all { |_, y| y }]
- [match_data, r.defaults.merge(info), r]
- }
- end
- def get_routes_as_head(routes)
- precedence = (routes.map(&:precedence).max || 0) + 1
- routes = routes.select { |r|
- r.verb === "GET" && !(r.verb === "HEAD")
- }.map! { |r|
- Route.new(r.name,
- r.app,
- r.path,
- r.conditions.merge(request_method: "HEAD"),
- r.defaults).tap do |route|
- route.precedence = r.precedence + precedence
- end
- }
- routes.flatten!
- routes
- end
- end
- end
- end
- 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.
- class Routes # :nodoc:
- include Enumerable
- attr_reader :routes, :named_routes
- def initialize
- @routes = []
- @named_routes = {}
- @ast = nil
- @partitioned_routes = nil
- @simulator = nil
- end
- def length
- routes.length
- end
- alias :size :length
- def last
- routes.last
- end
- def each(&block)
- routes.each(&block)
- end
- def clear
- routes.clear
- end
- def partitioned_routes
- @partitioned_routes ||= routes.partition do |r|
- r.path.anchored && r.ast.grep(Nodes::Symbol).all?(&:default_regexp?)
- end
- end
- def ast
- @ast ||= begin
- asts = partitioned_routes.first.map(&:ast)
- Nodes::Or.new(asts) unless asts.empty?
- end
- end
- def simulator
- @simulator ||= begin
- gtg = GTG::Builder.new(ast).transition_table
- GTG::Simulator.new(gtg)
- end
- end
- # Add a route to the routing table.
- def add_route(app, path, conditions, defaults, name = nil)
- route = Route.new(name, app, path, conditions, defaults)
- route.precedence = routes.length
- routes << route
- named_routes[name] = route if name && !named_routes[name]
- clear_cache!
- route
- end
- private
- def clear_cache!
- @ast = nil
- @partitioned_routes = nil
- @simulator = nil
- end
- end
- end
- end
- require 'strscan'
- module ActionDispatch
- module Journey # :nodoc:
- class Scanner # :nodoc:
- def initialize
- @ss = nil
- end
- def scan_setup(str)
- @ss = StringScanner.new(str)
- end
- def eos?
- @ss.eos?
- end
- def pos
- @ss.pos
- end
- def pre_match
- @ss.pre_match
- end
- def next_token
- return if @ss.eos?
- until token = scan || @ss.eos?; end
- token
- end
- private
- def scan
- case
- # /
- when text = @ss.scan(/\//)
- [:SLASH, text]
- when text = @ss.scan(/\*\w+/)
- [:STAR, text]
- when text = @ss.scan(/\(/)
- [:LPAREN, text]
- when text = @ss.scan(/\)/)
- [:RPAREN, text]
- when text = @ss.scan(/\|/)
- [:OR, text]
- when text = @ss.scan(/\./)
- [:DOT, text]
- when text = @ss.scan(/:\w+/)
- [:SYMBOL, text]
- when text = @ss.scan(/[\w%\-~]+/)
- [:LITERAL, text]
- # any char
- when text = @ss.scan(/./)
- [:LITERAL, text]
- end
- end
- end
- end
- end
- # encoding: utf-8
- module ActionDispatch
- module Journey # :nodoc:
- module Visitors # :nodoc:
- class Visitor # :nodoc:
- DISPATCH_CACHE = Hash.new { |h,k|
- h[k] = "visit_#{k}"
- }
- def accept(node)
- visit(node)
- end
- private
- def visit node
- send(DISPATCH_CACHE[node.type], node)
- end
- def binary(node)
- visit(node.left)
- visit(node.right)
- end
- def visit_CAT(n); binary(n); end
- def nary(node)
- node.children.each { |c| visit(c) }
- end
- def visit_OR(n); nary(n); end
- def unary(node)
- visit(node.left)
- end
- def visit_GROUP(n); unary(n); end
- def visit_STAR(n); unary(n); end
- def terminal(node); end
- %w{ LITERAL SYMBOL SLASH DOT }.each do |t|
- class_eval %{ def visit_#{t}(n); terminal(n); end }, __FILE__, __LINE__
- end
- end
- # Loop through the requirements AST
- class Each < Visitor # :nodoc:
- attr_reader :block
- def initialize(block)
- @block = block
- end
- def visit(node)
- super
- block.call(node)
- end
- end
- class String < Visitor # :nodoc:
- private
- def binary(node)
- [visit(node.left), visit(node.right)].join
- end
- def nary(node)
- node.children.map { |c| visit(c) }.join '|'
- end
- def terminal(node)
- node.left
- end
- def visit_GROUP(node)
- "(#{visit(node.left)})"
- end
- end
- # Used for formatting urls (url_for)
- class Formatter < Visitor # :nodoc:
- attr_reader :options, :consumed
- def initialize(options)
- @options = options
- @consumed = {}
- end
- private
- def visit_GROUP(node)
- if consumed == options
- nil
- else
- route = visit(node.left)
- route.include?("\0") ? nil : route
- end
- end
- def terminal(node)
- node.left
- end
- def binary(node)
- [visit(node.left), visit(node.right)].join
- end
- def nary(node)
- node.children.map { |c| visit(c) }.join
- end
- def visit_SYMBOL(node)
- key = node.to_sym
- if value = options[key]
- consumed[key] = value
- Router::Utils.escape_path(value)
- else
- "\0"
- end
- end
- end
- class Dot < Visitor # :nodoc:
- def initialize
- @nodes = []
- @edges = []
- end
- def accept(node)
- super
- <<-eodot
- digraph parse_tree {
- size="8,5"
- node [shape = none];
- edge [dir = none];
- #{@nodes.join "\n"}
- #{@edges.join("\n")}
- }
- eodot
- end
- private
- def binary(node)
- node.children.each do |c|
- @edges << "#{node.object_id} -> #{c.object_id};"
- end
- super
- end
- def nary(node)
- node.children.each do |c|
- @edges << "#{node.object_id} -> #{c.object_id};"
- end
- super
- end
- def unary(node)
- @edges << "#{node.object_id} -> #{node.left.object_id};"
- super
- end
- def visit_GROUP(node)
- @nodes << "#{node.object_id} [label=\"()\"];"
- super
- end
- def visit_CAT(node)
- @nodes << "#{node.object_id} [label=\"â—‹\"];"
- super
- end
- def visit_STAR(node)
- @nodes << "#{node.object_id} [label=\"*\"];"
- super
- end
- def visit_OR(node)
- @nodes << "#{node.object_id} [label=\"|\"];"
- super
- end
- def terminal(node)
- value = node.left
- @nodes << "#{node.object_id} [label=\"#{value}\"];"
- end
- end
- end
- end
- end
- require 'action_dispatch/journey/router'
- require 'action_dispatch/journey/gtg/builder'
- require 'action_dispatch/journey/gtg/simulator'
- require 'action_dispatch/journey/nfa/builder'
- require 'action_dispatch/journey/nfa/simulator'
- module ActionDispatch
- # Provide callbacks to be executed before and after the request dispatch.
- class Callbacks
- include ActiveSupport::Callbacks
- define_callbacks :call
- class << self
- delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader"
- end
- def self.before(*args, &block)
- set_callback(:call, :before, *args, &block)
- end
- def self.after(*args, &block)
- set_callback(:call, :after, *args, &block)
- end
- def initialize(app)
- @app = app
- end
- def call(env)
- error = nil
- result = run_callbacks :call do
- begin
- @app.call(env)
- rescue => error
- end
- end
- raise error if error
- result
- end
- end
- end
- require 'active_support/core_ext/hash/keys'
- require 'active_support/core_ext/module/attribute_accessors'
- require 'active_support/key_generator'
- require 'active_support/message_verifier'
- module ActionDispatch
- class Request < Rack::Request
- def cookie_jar
- env['action_dispatch.cookies'] ||= Cookies::CookieJar.build(self)
- end
- end
- # \Cookies are read and written through ActionController#cookies.
- #
- # The cookies being read are the ones received along with the request, the cookies
- # being written will be sent out with the response. Reading a cookie does not get
- # the cookie object itself back, just the value it holds.
- #
- # Examples of writing:
- #
- # # Sets a simple session cookie.
- # # This cookie will be deleted when the user's browser is closed.
- # cookies[:user_name] = "david"
- #
- # # Assign an array of values to a cookie.
- # cookies[:lat_lon] = [47.68, -122.37]
- #
- # # Sets a cookie that expires in 1 hour.
- # cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
- #
- # # Sets a signed cookie, which prevents users from tampering with its value.
- # # The cookie is signed by your app's <tt>config.secret_key_base</tt> value.
- # # It can be read using the signed method <tt>cookies.signed[:key]</tt>
- # cookies.signed[:user_id] = current_user.id
- #
- # # Sets a "permanent" cookie (which expires in 20 years from now).
- # cookies.permanent[:login] = "XJ-122"
- #
- # # You can also chain these methods:
- # cookies.permanent.signed[:login] = "XJ-122"
- #
- # Examples of reading:
- #
- # cookies[:user_name] # => "david"
- # cookies.size # => 2
- # cookies[:lat_lon] # => [47.68, -122.37]
- # cookies.signed[:login] # => "XJ-122"
- #
- # Example for deleting:
- #
- # cookies.delete :user_name
- #
- # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
- #
- # cookies[:key] = {
- # value: 'a yummy cookie',
- # expires: 1.year.from_now,
- # domain: 'domain.com'
- # }
- #
- # cookies.delete(:key, domain: 'domain.com')
- #
- # The option symbols for setting cookies are:
- #
- # * <tt>:value</tt> - The cookie's value or list of values (as an array).
- # * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
- # of the application.
- # * <tt>:domain</tt> - The domain for which this cookie applies so you can
- # restrict to the domain level. If you use a schema like www.example.com
- # and want to share session with user.example.com set <tt>:domain</tt>
- # to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
- # <tt>:all</tt> again when deleting keys.
- #
- # domain: nil # Does not sets cookie domain. (default)
- # domain: :all # Allow the cookie for the top most level
- # domain and subdomains.
- #
- # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
- # * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers.
- # Default is +false+.
- # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
- # only HTTP. Defaults to +false+.
- class Cookies
- HTTP_HEADER = "Set-Cookie".freeze
- GENERATOR_KEY = "action_dispatch.key_generator".freeze
- SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze
- ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
- ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
- TOKEN_KEY = "action_dispatch.secret_token".freeze
- # Cookies can typically store 4096 bytes.
- MAX_COOKIE_SIZE = 4096
- # Raised when storing more than 4K of session data.
- CookieOverflow = Class.new StandardError
- class CookieJar #:nodoc:
- include Enumerable
- # This regular expression is used to split the levels of a domain.
- # The top level domain can be any string without a period or
- # **.**, ***.** style TLDs like co.uk or com.au
- #
- # www.example.co.uk gives:
- # $& => example.co.uk
- #
- # example.com gives:
- # $& => example.com
- #
- # lots.of.subdomains.example.local gives:
- # $& => example.local
- DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
- def self.options_for_env(env) #:nodoc:
- { signed_cookie_salt: env[SIGNED_COOKIE_SALT] || '',
- encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT] || '',
- encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
- token_key: env[TOKEN_KEY] }
- end
- def self.build(request)
- env = request.env
- key_generator = env[GENERATOR_KEY]
- options = options_for_env env
- host = request.host
- secure = request.ssl?
- new(key_generator, host, secure, options).tap do |hash|
- hash.update(request.cookies)
- end
- end
- def initialize(key_generator, host = nil, secure = false, options = {})
- @key_generator = key_generator
- @set_cookies = {}
- @delete_cookies = {}
- @host = host
- @secure = secure
- @options = options
- @cookies = {}
- end
- def each(&block)
- @cookies.each(&block)
- end
- # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
- def [](name)
- @cookies[name.to_s]
- end
- def fetch(name, *args, &block)
- @cookies.fetch(name.to_s, *args, &block)
- end
- def key?(name)
- @cookies.key?(name.to_s)
- end
- alias :has_key? :key?
- def update(other_hash)
- @cookies.update other_hash.stringify_keys
- self
- end
- def handle_options(options) #:nodoc:
- options[:path] ||= "/"
- if options[:domain] == :all
- # if there is a provided tld length then we use it otherwise default domain regexp
- domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
- # if host is not ip and matches domain regexp
- # (ip confirms to domain regexp so we explicitly check for ip)
- options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ domain_regexp)
- ".#{$&}"
- end
- elsif options[:domain].is_a? Array
- # if host matches one of the supplied domains without a dot in front of it
- options[:domain] = options[:domain].find {|domain| @host.include? domain.sub(/^\./, '') }
- end
- end
- # Sets the cookie named +name+. The second argument may be the very cookie
- # value, or a hash of options as documented above.
- def []=(key, options)
- if options.is_a?(Hash)
- options.symbolize_keys!
- value = options[:value]
- else
- value = options
- options = { :value => value }
- end
- handle_options(options)
- if @cookies[key.to_s] != value or options[:expires]
- @cookies[key.to_s] = value
- @set_cookies[key.to_s] = options
- @delete_cookies.delete(key.to_s)
- end
- value
- end
- # Removes the cookie on the client machine by setting the value to an empty string
- # and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in
- # an options hash to delete cookies with extra data such as a <tt>:path</tt>.
- def delete(key, options = {})
- return unless @cookies.has_key? key.to_s
- options.symbolize_keys!
- handle_options(options)
- value = @cookies.delete(key.to_s)
- @delete_cookies[key.to_s] = options
- value
- end
- # Whether the given cookie is to be deleted by this CookieJar.
- # Like <tt>[]=</tt>, you can pass in an options hash to test if a
- # deletion applies to a specific <tt>:path</tt>, <tt>:domain</tt> etc.
- def deleted?(key, options = {})
- options.symbolize_keys!
- handle_options(options)
- @delete_cookies[key.to_s] == options
- end
- # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie
- def clear(options = {})
- @cookies.each_key{ |k| delete(k, options) }
- end
- # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
- #
- # cookies.permanent[:prefers_open_id] = true
- # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
- #
- # This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
- #
- # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
- #
- # cookies.permanent.signed[:remember_me] = current_user.id
- # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
- end
- # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
- # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
- # cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
- # be raised.
- #
- # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
- #
- # Example:
- #
- # cookies.signed[:discount] = 45
- # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
- #
- # cookies.signed[:discount] # => 45
- def signed
- @signed ||= SignedCookieJar.new(self, @key_generator, @options)
- end
- # Only needed for supporting the +UpgradeSignatureToEncryptionCookieStore+, users and plugin authors should not use this
- def signed_using_old_secret #:nodoc:
- @signed_using_old_secret ||= SignedCookieJar.new(self, ActiveSupport::DummyKeyGenerator.new(@options[:token_key]), @options)
- end
- # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
- # If the cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception
- # will be raised.
- #
- # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
- #
- # Example:
- #
- # cookies.encrypted[:discount] = 45
- # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
- #
- # cookies.encrypted[:discount] # => 45
- def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
- end
- def write(headers)
- @set_cookies.each { |k, v| ::Rack::Utils.set_cookie_header!(headers, k, v) if write_cookie?(v) }
- @delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
- end
- def recycle! #:nodoc:
- @set_cookies.clear
- @delete_cookies.clear
- end
- mattr_accessor :always_write_cookie
- self.always_write_cookie = false
- private
- def write_cookie?(cookie)
- @secure || !cookie[:secure] || always_write_cookie
- end
- end
- class PermanentCookieJar #:nodoc:
- def initialize(parent_jar, key_generator, options = {})
- @parent_jar = parent_jar
- @key_generator = key_generator
- @options = options
- end
- def [](key)
- @parent_jar[name.to_s]
- end
- def []=(key, options)
- if options.is_a?(Hash)
- options.symbolize_keys!
- else
- options = { :value => options }
- end
- options[:expires] = 20.years.from_now
- @parent_jar[key] = options
- end
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
- end
- def signed
- @signed ||= SignedCookieJar.new(self, @key_generator, @options)
- end
- def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
- end
- def method_missing(method, *arguments, &block)
- ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " +
- "You probably want to try this method over the parent CookieJar."
- end
- end
- class SignedCookieJar #:nodoc:
- def initialize(parent_jar, key_generator, options = {})
- @parent_jar = parent_jar
- @options = options
- secret = key_generator.generate_key(@options[:signed_cookie_salt])
- @verifier = ActiveSupport::MessageVerifier.new(secret)
- end
- def [](name)
- if signed_message = @parent_jar[name]
- @verifier.verify(signed_message)
- end
- rescue ActiveSupport::MessageVerifier::InvalidSignature
- nil
- end
- def []=(key, options)
- if options.is_a?(Hash)
- options.symbolize_keys!
- options[:value] = @verifier.generate(options[:value])
- else
- options = { :value => @verifier.generate(options) }
- end
- raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
- @parent_jar[key] = options
- end
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
- end
- def signed
- @signed ||= SignedCookieJar.new(self, @key_generator, @options)
- end
- def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
- end
- def method_missing(method, *arguments, &block)
- ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " +
- "You probably want to try this method over the parent CookieJar."
- end
- end
- class EncryptedCookieJar #:nodoc:
- def initialize(parent_jar, key_generator, options = {})
- if ActiveSupport::DummyKeyGenerator === key_generator
- raise "Encrypted Cookies must be used in conjunction with config.secret_key_base." +
- "Set config.secret_key_base in config/initializers/secret_token.rb"
- end
- @parent_jar = parent_jar
- @options = options
- secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
- sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
- @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
- end
- def [](key)
- if encrypted_message = @parent_jar[key]
- @encryptor.decrypt_and_verify(encrypted_message)
- end
- rescue ActiveSupport::MessageVerifier::InvalidSignature,
- ActiveSupport::MessageVerifier::InvalidMessage
- nil
- end
- def []=(key, options)
- if options.is_a?(Hash)
- options.symbolize_keys!
- else
- options = { :value => options }
- end
- options[:value] = @encryptor.encrypt_and_sign(options[:value])
- raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
- @parent_jar[key] = options
- end
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
- end
- def signed
- @signed ||= SignedCookieJar.new(self, @key_generator, @options)
- end
- def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
- end
- def method_missing(method, *arguments, &block)
- ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " +
- "You probably want to try this method over the parent CookieJar."
- end
- end
- def initialize(app)
- @app = app
- end
- def call(env)
- status, headers, body = @app.call(env)
- if cookie_jar = env['action_dispatch.cookies']
- cookie_jar.write(headers)
- if headers[HTTP_HEADER].respond_to?(:join)
- headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n")
- end
- end
- [status, headers, body]
- end
- end
- end
- require 'action_dispatch/http/request'
- require 'action_dispatch/middleware/exception_wrapper'
- require 'action_dispatch/routing/inspector'
- module ActionDispatch
- # This middleware is responsible for logging exceptions and
- # showing a debugging page in case the request is local.
- class DebugExceptions
- RESCUES_TEMPLATE_PATH = File.expand_path('../templates', __FILE__)
- def initialize(app, routes_app = nil)
- @app = app
- @routes_app = routes_app
- end
- def call(env)
- _, headers, body = response = @app.call(env)
- if headers['X-Cascade'] == 'pass'
- body.close if body.respond_to?(:close)
- raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
- end
- response
- rescue Exception => exception
- raise exception if env['action_dispatch.show_exceptions'] == false
- render_exception(env, exception)
- end
- private
- def render_exception(env, exception)
- wrapper = ExceptionWrapper.new(env, exception)
- log_error(env, wrapper)
- if env['action_dispatch.show_detailed_exceptions']
- template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
- :request => Request.new(env),
- :exception => wrapper.exception,
- :application_trace => wrapper.application_trace,
- :framework_trace => wrapper.framework_trace,
- :full_trace => wrapper.full_trace,
- :routes_inspector => routes_inspector(exception),
- :source_extract => wrapper.source_extract,
- :line_number => wrapper.line_number,
- :file => wrapper.file
- )
- file = "rescues/#{wrapper.rescue_template}"
- body = template.render(:template => file, :layout => 'rescues/layout')
- render(wrapper.status_code, body)
- else
- raise exception
- end
- end
- def render(status, body)
- [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
- end
- def log_error(env, wrapper)
- logger = logger(env)
- return unless logger
- exception = wrapper.exception
- trace = wrapper.application_trace
- trace = wrapper.framework_trace if trace.empty?
- ActiveSupport::Deprecation.silence do
- message = "\n#{exception.class} (#{exception.message}):\n"
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
- message << " " << trace.join("\n ")
- logger.fatal("#{message}\n\n")
- end
- end
- def logger(env)
- env['action_dispatch.logger'] || stderr_logger
- end
- def stderr_logger
- @stderr_logger ||= ActiveSupport::Logger.new($stderr)
- end
- def routes_inspector(exception)
- if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error))
- ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
- end
- end
- end
- end
- require 'action_controller/metal/exceptions'
- require 'active_support/core_ext/class/attribute_accessors'
- module ActionDispatch
- class ExceptionWrapper
- cattr_accessor :rescue_responses
- @@rescue_responses = Hash.new(:internal_server_error)
- @@rescue_responses.merge!(
- 'ActionController::RoutingError' => :not_found,
- 'AbstractController::ActionNotFound' => :not_found,
- 'ActionController::MethodNotAllowed' => :method_not_allowed,
- 'ActionController::NotImplemented' => :not_implemented,
- 'ActionController::UnknownFormat' => :not_acceptable,
- 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
- 'ActionController::BadRequest' => :bad_request,
- 'ActionController::ParameterMissing' => :bad_request
- )
- cattr_accessor :rescue_templates
- @@rescue_templates = Hash.new('diagnostics')
- @@rescue_templates.merge!(
- 'ActionView::MissingTemplate' => 'missing_template',
- 'ActionController::RoutingError' => 'routing_error',
- 'AbstractController::ActionNotFound' => 'unknown_action',
- 'ActionView::Template::Error' => 'template_error'
- )
- attr_reader :env, :exception, :line_number, :file
- def initialize(env, exception)
- @env = env
- @exception = original_exception(exception)
- end
- def rescue_template
- @@rescue_templates[@exception.class.name]
- end
- def status_code
- self.class.status_code_for_exception(@exception.class.name)
- end
- def application_trace
- clean_backtrace(:silent)
- end
- def framework_trace
- clean_backtrace(:noise)
- end
- def full_trace
- clean_backtrace(:all)
- end
- def self.status_code_for_exception(class_name)
- Rack::Utils.status_code(@@rescue_responses[class_name])
- end
- def source_extract
- if application_trace && trace = application_trace.first
- file, line, _ = trace.split(":")
- @file = file
- @line_number = line.to_i
- source_fragment(@file, @line_number)
- end
- end
- private
- def original_exception(exception)
- if registered_original_exception?(exception)
- exception.original_exception
- else
- exception
- end
- end
- def registered_original_exception?(exception)
- exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name)
- end
- def clean_backtrace(*args)
- if backtrace_cleaner
- backtrace_cleaner.clean(@exception.backtrace, *args)
- else
- @exception.backtrace
- end
- end
- def backtrace_cleaner
- @backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner']
- end
- def source_fragment(path, line)
- return unless Rails.respond_to?(:root) && Rails.root
- full_path = Rails.root.join(path)
- if File.exists?(full_path)
- File.open(full_path, "r") do |file|
- start = [line - 3, 0].max
- lines = file.each_line.drop(start).take(6)
- Hash[*(start+1..(lines.count+start)).zip(lines).flatten]
- end
- end
- end
- end
- end
- module ActionDispatch
- class Request < Rack::Request
- # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
- # read a notice you put there or <tt>flash["notice"] = "hello"</tt>
- # to put a new one.
- def flash
- @env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"])
- end
- end
- # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
- # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
- # action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can
- # then expose the flash to its template. Actually, that exposure is automatically done.
- #
- # class PostsController < ActionController::Base
- # def create
- # # save post
- # flash[:notice] = "Post successfully created"
- # redirect_to @post
- # end
- #
- # def show
- # # doesn't need to assign the flash notice to the template, that's done automatically
- # end
- # end
- #
- # show.html.erb
- # <% if flash[:notice] %>
- # <div class="notice"><%= flash[:notice] %></div>
- # <% end %>
- #
- # Since the +notice+ and +alert+ keys are a common idiom, convenience accessors are available:
- #
- # flash.alert = "You must be logged in"
- # flash.notice = "Post successfully created"
- #
- # This example just places a string in the flash, but you can put any object in there. And of course, you can put as
- # many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
- #
- # See docs on the FlashHash class for more details about the flash.
- class Flash
- KEY = 'action_dispatch.request.flash_hash'.freeze
- class FlashNow #:nodoc:
- attr_accessor :flash
- def initialize(flash)
- @flash = flash
- end
- def []=(k, v)
- @flash[k] = v
- @flash.discard(k)
- v
- end
- def [](k)
- @flash[k]
- end
- # Convenience accessor for <tt>flash.now[:alert]=</tt>.
- def alert=(message)
- self[:alert] = message
- end
- # Convenience accessor for <tt>flash.now[:notice]=</tt>.
- def notice=(message)
- self[:notice] = message
- end
- end
- class FlashHash
- include Enumerable
- def self.from_session_value(value)
- flash = case value
- when FlashHash # Rails 3.1, 3.2
- new(value.instance_variable_get(:@flashes), value.instance_variable_get(:@used))
- when Hash # Rails 4.0
- new(value['flashes'], value['discard'])
- else
- new
- end
- flash.tap(&:sweep)
- end
- def to_session_value
- return nil if empty?
- {'discard' => @discard.to_a, 'flashes' => @flashes}
- end
- def initialize(flashes = {}, discard = []) #:nodoc:
- @discard = Set.new(discard)
- @flashes = flashes
- @now = nil
- end
- def initialize_copy(other)
- if other.now_is_loaded?
- @now = other.now.dup
- @now.flash = self
- end
- super
- end
- def []=(k, v)
- @discard.delete k
- @flashes[k] = v
- end
- def [](k)
- @flashes[k]
- end
- def update(h) #:nodoc:
- @discard.subtract h.keys
- @flashes.update h
- self
- end
- def keys
- @flashes.keys
- end
- def key?(name)
- @flashes.key? name
- end
- def delete(key)
- @discard.delete key
- @flashes.delete key
- self
- end
- def to_hash
- @flashes.dup
- end
- def empty?
- @flashes.empty?
- end
- def clear
- @discard.clear
- @flashes.clear
- end
- def each(&block)
- @flashes.each(&block)
- end
- alias :merge! :update
- def replace(h) #:nodoc:
- @discard.clear
- @flashes.replace h
- self
- end
- # Sets a flash that will not be available to the next action, only to the current.
- #
- # flash.now[:message] = "Hello current action"
- #
- # This method enables you to use the flash as a central messaging system in your app.
- # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
- # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
- # vanish when the current action is done.
- #
- # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
- #
- # Also, brings two convenience accessors:
- #
- # flash.now.alert = "Beware now!"
- # # Equivalent to flash.now[:alert] = "Beware now!"
- #
- # flash.now.notice = "Good luck now!"
- # # Equivalent to flash.now[:notice] = "Good luck now!"
- def now
- @now ||= FlashNow.new(self)
- end
- # Keeps either the entire current flash or a specific flash entry available for the next action:
- #
- # flash.keep # keeps the entire flash
- # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
- def keep(k = nil)
- @discard.subtract Array(k || keys)
- k ? self[k] : self
- end
- # Marks the entire flash or a single flash entry to be discarded by the end of the current action:
- #
- # flash.discard # discard the entire flash at the end of the current action
- # flash.discard(:warning) # discard only the "warning" entry at the end of the current action
- def discard(k = nil)
- @discard.merge Array(k || keys)
- k ? self[k] : self
- end
- # Mark for removal entries that were kept, and delete unkept ones.
- #
- # This method is called automatically by filters, so you generally don't need to care about it.
- def sweep #:nodoc:
- @discard.each { |k| @flashes.delete k }
- @discard.replace @flashes.keys
- end
- # Convenience accessor for <tt>flash[:alert]</tt>.
- def alert
- self[:alert]
- end
- # Convenience accessor for <tt>flash[:alert]=</tt>.
- def alert=(message)
- self[:alert] = message
- end
- # Convenience accessor for <tt>flash[:notice]</tt>.
- def notice
- self[:notice]
- end
- # Convenience accessor for <tt>flash[:notice]=</tt>.
- def notice=(message)
- self[:notice] = message
- end
- protected
- def now_is_loaded?
- @now
- end
- end
- def initialize(app)
- @app = app
- end
- def call(env)
- @app.call(env)
- ensure
- session = Request::Session.find(env) || {}
- flash_hash = env[KEY]
- if flash_hash
- if !flash_hash.empty? || session.key?('flash')
- session["flash"] = flash_hash.to_session_value
- new_hash = flash_hash.dup
- else
- new_hash = flash_hash
- end
- env[KEY] = new_hash
- end
- if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)
- session.key?('flash') && session['flash'].nil?
- session.delete('flash')
- end
- end
- end
- end
- require 'active_support/core_ext/hash/conversions'
- require 'action_dispatch/http/request'
- require 'active_support/core_ext/hash/indifferent_access'
- module ActionDispatch
- class ParamsParser
- class ParseError < StandardError
- attr_reader :original_exception
- def initialize(message, original_exception)
- super(message)
- @original_exception = original_exception
- end
- end
- DEFAULT_PARSERS = {
- Mime::XML => :xml_simple,
- Mime::JSON => :json
- }
- def initialize(app, parsers = {})
- @app, @parsers = app, DEFAULT_PARSERS.merge(parsers)
- end
- def call(env)
- if params = parse_formatted_parameters(env)
- env["action_dispatch.request.request_parameters"] = params
- end
- @app.call(env)
- end
- private
- def parse_formatted_parameters(env)
- request = Request.new(env)
- return false if request.content_length.zero?
- mime_type = content_type_from_legacy_post_data_format_header(env) ||
- request.content_mime_type
- strategy = @parsers[mime_type]
- return false unless strategy
- case strategy
- when Proc
- strategy.call(request.raw_post)
- when :xml_simple, :xml_node
- data = request.deep_munge(Hash.from_xml(request.body.read) || {})
- data.with_indifferent_access
- when :json
- data = ActiveSupport::JSON.decode(request.body)
- data = {:_json => data} unless data.is_a?(Hash)
- request.deep_munge(data).with_indifferent_access
- else
- false
- end
- rescue Exception => e # YAML, XML or Ruby code block errors
- logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
- raise ParseError.new(e.message, e)
- end
- def content_type_from_legacy_post_data_format_header(env)
- if x_post_format = env['HTTP_X_POST_DATA_FORMAT']
- case x_post_format.to_s.downcase
- when 'yaml' then return Mime::YAML
- when 'xml' then return Mime::XML
- end
- end
- nil
- end
- def logger(env)
- env['action_dispatch.logger'] || ActiveSupport::Logger.new($stderr)
- end
- end
- end
- module ActionDispatch
- class PublicExceptions
- attr_accessor :public_path
- def initialize(public_path)
- @public_path = public_path
- end
- def call(env)
- exception = env["action_dispatch.exception"]
- status = env["PATH_INFO"][1..-1]
- request = ActionDispatch::Request.new(env)
- content_type = request.formats.first
- body = { :status => status, :error => exception.message }
- render(status, content_type, body)
- end
- private
- def render(status, content_type, body)
- format = content_type && "to_#{content_type.to_sym}"
- if format && body.respond_to?(format)
- render_format(status, content_type, body.public_send(format))
- else
- render_html(status)
- end
- end
- def render_format(status, content_type, body)
- [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
- 'Content-Length' => body.bytesize.to_s}, [body]]
- end
- def render_html(status)
- found = false
- path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
- path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
- if found || File.exist?(path)
- render_format(status, 'text/html', File.read(path))
- else
- [404, { "X-Cascade" => "pass" }, []]
- end
- end
- end
- end
- module ActionDispatch
- # ActionDispatch::Reloader provides prepare and cleanup callbacks,
- # intended to assist with code reloading during development.
- #
- # Prepare callbacks are run before each request, and cleanup callbacks
- # after each request. In this respect they are analogs of ActionDispatch::Callback's
- # before and after callbacks. However, cleanup callbacks are not called until the
- # request is fully complete -- that is, after #close has been called on
- # the response body. This is important for streaming responses such as the
- # following:
- #
- # self.response_body = lambda { |response, output|
- # # code here which refers to application models
- # }
- #
- # Cleanup callbacks will not be called until after the response_body lambda
- # is evaluated, ensuring that it can refer to application models and other
- # classes before they are unloaded.
- #
- # By default, ActionDispatch::Reloader is included in the middleware stack
- # only in the development environment; specifically, when +config.cache_classes+
- # is false. Callbacks may be registered even when it is not included in the
- # middleware stack, but are executed only when <tt>ActionDispatch::Reloader.prepare!</tt>
- # or <tt>ActionDispatch::Reloader.cleanup!</tt> are called manually.
- #
- class Reloader
- include ActiveSupport::Callbacks
- define_callbacks :prepare, :scope => :name
- define_callbacks :cleanup, :scope => :name
- # Add a prepare callback. Prepare callbacks are run before each request, prior
- # to ActionDispatch::Callback's before callbacks.
- def self.to_prepare(*args, &block)
- set_callback(:prepare, *args, &block)
- end
- # Add a cleanup callback. Cleanup callbacks are run after each request is
- # complete (after #close is called on the response body).
- def self.to_cleanup(*args, &block)
- set_callback(:cleanup, *args, &block)
- end
- # Execute all prepare callbacks.
- def self.prepare!
- new(nil).prepare!
- end
- # Execute all cleanup callbacks.
- def self.cleanup!
- new(nil).cleanup!
- end
- def initialize(app, condition=nil)
- @app = app
- @condition = condition || lambda { true }
- @validated = true
- end
- def call(env)
- @validated = @condition.call
- prepare!
- response = @app.call(env)
- response[2] = ::Rack::BodyProxy.new(response[2]) { cleanup! }
- response
- rescue Exception
- cleanup!
- raise
- end
- def prepare! #:nodoc:
- run_callbacks :prepare if validated?
- end
- def cleanup! #:nodoc:
- run_callbacks :cleanup if validated?
- ensure
- @validated = true
- end
- private
- def validated? #:nodoc:
- @validated
- end
- end
- end
- module ActionDispatch
- # This middleware calculates the IP address of the remote client that is
- # making the request. It does this by checking various headers that could
- # contain the address, and then picking the last-set address that is not
- # on the list of trusted IPs. This follows the precedent set by e.g.
- # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453],
- # with {reasoning explained at length}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection]
- # by @gingerlime. A more detailed explanation of the algorithm is given
- # at GetIp#calculate_ip.
- #
- # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
- # requires. Some Rack servers simply drop preceeding headers, and only report
- # the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
- # If you are behind multiple proxy servers (like Nginx to HAProxy to Unicorn)
- # then you should test your Rack server to make sure your data is good.
- #
- # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING.
- # This middleware assumes that there is at least one proxy sitting around
- # and setting headers with the client's remote IP address. If you don't use
- # a proxy, because you are hosted on e.g. Heroku without SSL, any client can
- # claim to have any IP address by setting the X-Forwarded-For header. If you
- # care about that, then you need to explicitly drop or ignore those headers
- # sometime before this middleware runs.
- class RemoteIp
- class IpSpoofAttackError < StandardError; end
- # The default trusted IPs list simply includes IP addresses that are
- # guaranteed by the IP specification to be private addresses. Those will
- # not be the ultimate client IP in production, and so are discarded. See
- # http://en.wikipedia.org/wiki/Private_network for details.
- TRUSTED_PROXIES = %r{
- ^127\.0\.0\.1$ | # localhost IPv4
- ^::1$ | # localhost IPv6
- ^fc00: | # private IPv6 range fc00
- ^10\. | # private IPv4 range 10.x.x.x
- ^172\.(1[6-9]|2[0-9]|3[0-1])\.| # private IPv4 range 172.16.0.0 .. 172.31.255.255
- ^192\.168\. # private IPv4 range 192.168.x.x
- }x
- attr_reader :check_ip, :proxies
- # Create a new +RemoteIp+ middleware instance.
- #
- # The +check_ip_spoofing+ option is on by default. When on, an exception
- # is raised if it looks like the client is trying to lie about its own IP
- # address. It makes sense to turn off this check on sites aimed at non-IP
- # clients (like WAP devices), or behind proxies that set headers in an
- # incorrect or confusing way (like AWS ELB).
- #
- # The +custom_trusted+ argument can take a regex, which will be used
- # instead of +TRUSTED_PROXIES+, or a string, which will be used in addition
- # to +TRUSTED_PROXIES+. Any proxy setup will put the value you want in the
- # middle (or at the beginning) of the X-Forwarded-For list, with your proxy
- # servers after it. If your proxies aren't removed, pass them in via the
- # +custom_trusted+ parameter. That way, the middleware will ignore those
- # IP addresses, and return the one that you want.
- def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
- @app = app
- @check_ip = check_ip_spoofing
- @proxies = case custom_proxies
- when Regexp
- custom_proxies
- when nil
- TRUSTED_PROXIES
- else
- Regexp.union(TRUSTED_PROXIES, custom_proxies)
- end
- end
- # Since the IP address may not be needed, we store the object here
- # without calculating the IP to keep from slowing down the majority of
- # requests. For those requests that do need to know the IP, the
- # GetIp#calculate_ip method will calculate the memoized client IP address.
- def call(env)
- env["action_dispatch.remote_ip"] = GetIp.new(env, self)
- @app.call(env)
- end
- # The GetIp class exists as a way to defer processing of the request data
- # into an actual IP address. If the ActionDispatch::Request#remote_ip method
- # is called, this class will calculate the value and then memoize it.
- class GetIp
- # This constant contains a regular expression that validates every known
- # form of IP v4 and v6 address, with or without abbreviations, adapted
- # from {this gist}[https://gist.github.com/1289635].
- VALID_IP = %r{
- (^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})){3}$) | # ip v4
- (^(
- (([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}) | # ip v6 not abbreviated
- (([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4}) | # ip v6 with double colon in the end
- (([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4}) | # - ip addresses v6
- (([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4}) | # - with
- (([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4}) | # - double colon
- (([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4}) | # - in the middle
- (([0-9A-Fa-f]{1,4}:){6} ((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3} (\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
- (([0-9A-Fa-f]{1,4}:){1,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
- (([0-9A-Fa-f]{1,4}:){1}:([0-9A-Fa-f]{1,4}:){0,4}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
- (([0-9A-Fa-f]{1,4}:){0,2}:([0-9A-Fa-f]{1,4}:){0,3}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
- (([0-9A-Fa-f]{1,4}:){0,3}:([0-9A-Fa-f]{1,4}:){0,2}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
- (([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
- (::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
- ([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4
- (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the begining
- (([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending
- )$)
- }x
- def initialize(env, middleware)
- @env = env
- @check_ip = middleware.check_ip
- @proxies = middleware.proxies
- end
- # Sort through the various IP address headers, looking for the IP most
- # likely to be the address of the actual remote client making this
- # request.
- #
- # REMOTE_ADDR will be correct if the request is made directly against the
- # Ruby process, on e.g. Heroku. When the request is proxied by another
- # server like HAProxy or Nginx, the IP address that made the original
- # request will be put in an X-Forwarded-For header. If there are multiple
- # proxies, that header may contain a list of IPs. Other proxy services
- # set the Client-Ip header instead, so we check that too.
- #
- # As discussed in {this post about Rails IP Spoofing}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
- # while the first IP in the list is likely to be the "originating" IP,
- # it could also have been set by the client maliciously.
- #
- # In order to find the first address that is (probably) accurate, we
- # take the list of IPs, remove known and trusted proxies, and then take
- # the last address left, which was presumably set by one of those proxies.
- def calculate_ip
- # Set by the Rack web server, this is a single value.
- remote_addr = ips_from('REMOTE_ADDR').last
- # Could be a CSV list and/or repeated headers that were concatenated.
- client_ips = ips_from('HTTP_CLIENT_IP').reverse
- forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR').reverse
- # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
- # If they are both set, it means that this request passed through two
- # proxies with incompatible IP header conventions, and there is no way
- # for us to determine which header is the right one after the fact.
- # Since we have no idea, we give up and explode.
- should_check_ip = @check_ip && client_ips.last
- if should_check_ip && !forwarded_ips.include?(client_ips.last)
- # We don't know which came from the proxy, and which from the user
- raise IpSpoofAttackError, "IP spoofing attack?! " +
- "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} " +
- "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
- end
- # We assume these things about the IP headers:
- #
- # - X-Forwarded-For will be a list of IPs, one per proxy, or blank
- # - Client-Ip is propagated from the outermost proxy, or is blank
- # - REMOTE_ADDR will be the IP that made the request to Rack
- ips = [forwarded_ips, client_ips, remote_addr].flatten.compact
- # If every single IP option is in the trusted list, just return REMOTE_ADDR
- filter_proxies(ips).first || remote_addr
- end
- # Memoizes the value returned by #calculate_ip and returns it for
- # ActionDispatch::Request to use.
- def to_s
- @ip ||= calculate_ip
- end
- protected
- def ips_from(header)
- # Split the comma-separated list into an array of strings
- ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
- # Only return IPs that are valid according to the regex
- ips.select{ |ip| ip =~ VALID_IP }
- end
- def filter_proxies(ips)
- ips.reject { |ip| ip =~ @proxies }
- end
- end
- end
- end
- require 'securerandom'
- require 'active_support/core_ext/string/access'
- module ActionDispatch
- # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
- # ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header.
- #
- # The unique request id is either based off the X-Request-Id header in the request, which would typically be generated
- # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
- # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
- #
- # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
- # from multiple pieces of the stack.
- class RequestId
- def initialize(app)
- @app = app
- end
- def call(env)
- env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id
- @app.call(env).tap { |status, headers, body| headers["X-Request-Id"] = env["action_dispatch.request_id"] }
- end
- private
- def external_request_id(env)
- if request_id = env["HTTP_X_REQUEST_ID"].presence
- request_id.gsub(/[^\w\-]/, "").first(255)
- end
- end
- def internal_request_id
- SecureRandom.uuid
- end
- end
- end
- require 'rack/utils'
- require 'rack/request'
- require 'rack/session/abstract/id'
- require 'action_dispatch/middleware/cookies'
- require 'action_dispatch/request/session'
- module ActionDispatch
- module Session
- class SessionRestoreError < StandardError #:nodoc:
- attr_reader :original_exception
- def initialize(const_error)
- @original_exception = const_error
- super("Session contains objects whose class definition isn't available.\n" +
- "Remember to require the classes for all objects kept in the session.\n" +
- "(Original exception: #{const_error.message} [#{const_error.class}])\n")
- end
- end
- module Compatibility
- def initialize(app, options = {})
- options[:key] ||= '_session_id'
- super
- end
- def generate_sid
- sid = SecureRandom.hex(16)
- sid.encode!(Encoding::UTF_8)
- sid
- end
- protected
- def initialize_sid
- @default_options.delete(:sidbits)
- @default_options.delete(:secure_random)
- end
- end
- module StaleSessionCheck
- def load_session(env)
- stale_session_check! { super }
- end
- def extract_session_id(env)
- stale_session_check! { super }
- end
- def stale_session_check!
- yield
- rescue ArgumentError => argument_error
- if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
- begin
- # Note that the regexp does not allow $1 to end with a ':'
- $1.constantize
- rescue LoadError, NameError => e
- raise ActionDispatch::Session::SessionRestoreError, e, e.backtrace
- end
- retry
- else
- raise
- end
- end
- end
- module SessionObject # :nodoc:
- def prepare_session(env)
- Request::Session.create(self, env, @default_options)
- end
- def loaded_session?(session)
- !session.is_a?(Request::Session) || session.loaded?
- end
- end
- class AbstractStore < Rack::Session::Abstract::ID
- include Compatibility
- include StaleSessionCheck
- include SessionObject
- private
- def set_cookie(env, session_id, cookie)
- request = ActionDispatch::Request.new(env)
- request.cookie_jar[key] = cookie
- end
- end
- end
- end
- require 'action_dispatch/middleware/session/abstract_store'
- module ActionDispatch
- module Session
- # Session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful
- # if you don't store critical data in your sessions and you don't need them to live for extended periods
- # of time.
- class CacheStore < AbstractStore
- # Create a new store. The cache to use can be passed in the <tt>:cache</tt> option. If it is
- # not specified, <tt>Rails.cache</tt> will be used.
- def initialize(app, options = {})
- @cache = options[:cache] || Rails.cache
- options[:expire_after] ||= @cache.options[:expires_in]
- super
- end
- # Get a session from the cache.
- def get_session(env, sid)
- sid ||= generate_sid
- session = @cache.read(cache_key(sid))
- session ||= {}
- [sid, session]
- end
- # Set a session in the cache.
- def set_session(env, sid, session, options)
- key = cache_key(sid)
- if session
- @cache.write(key, session, :expires_in => options[:expire_after])
- else
- @cache.delete(key)
- end
- sid
- end
- # Remove a session from the cache.
- def destroy_session(env, sid, options)
- @cache.delete(cache_key(sid))
- generate_sid
- end
- private
- # Turn the session id into a cache key.
- def cache_key(sid)
- "_session_id:#{sid}"
- end
- end
- end
- end
- require 'active_support/core_ext/hash/keys'
- require 'action_dispatch/middleware/session/abstract_store'
- require 'rack/session/cookie'
- module ActionDispatch
- module Session
- # This cookie-based session store is the Rails default. Sessions typically
- # contain at most a user_id and flash message; both fit within the 4K cookie
- # size limit. Cookie-based sessions are dramatically faster than the
- # alternatives.
- #
- # If you have more than 4K of session data or don't want your data to be
- # visible to the user, pick another session store.
- #
- # CookieOverflow is raised if you attempt to store more than 4K of data.
- #
- # A message digest is included with the cookie to ensure data integrity:
- # a user cannot alter his +user_id+ without knowing the secret key
- # included in the hash. New apps are generated with a pregenerated secret
- # in config/environment.rb. Set your own for old apps you're upgrading.
- #
- # Session options:
- #
- # * <tt>:secret</tt>: An application-wide key string. It's important that
- # the secret is not vulnerable to a dictionary attack. Therefore, you
- # should choose a secret consisting of random numbers and letters and
- # more than 30 characters.
- #
- # secret: '449fe2e7daee471bffae2fd8dc02313d'
- #
- # * <tt>:digest</tt>: The message digest algorithm used to verify session
- # integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
- # such as 'MD5', 'RIPEMD160', 'SHA256', etc.
- #
- # To generate a secret key for an existing application, run
- # "rake secret" and set the key in config/initializers/secret_token.rb.
- #
- # Note that changing digest or secret invalidates all existing sessions!
- class CookieStore < Rack::Session::Abstract::ID
- include Compatibility
- include StaleSessionCheck
- include SessionObject
- def initialize(app, options={})
- super(app, options.merge!(:cookie_only => true))
- end
- def destroy_session(env, session_id, options)
- new_sid = generate_sid unless options[:drop]
- # Reset hash and Assign the new session id
- env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {}
- new_sid
- end
- def load_session(env)
- stale_session_check! do
- data = unpacked_cookie_data(env)
- data = persistent_session_id!(data)
- [data["session_id"], data]
- end
- end
- private
- def extract_session_id(env)
- stale_session_check! do
- unpacked_cookie_data(env)["session_id"]
- end
- end
- def unpacked_cookie_data(env)
- env["action_dispatch.request.unsigned_session_cookie"] ||= begin
- stale_session_check! do
- if data = get_cookie(env)
- data.stringify_keys!
- end
- data || {}
- end
- end
- end
- def persistent_session_id!(data, sid=nil)
- data ||= {}
- data["session_id"] ||= sid || generate_sid
- data
- end
- def set_session(env, sid, session_data, options)
- session_data["session_id"] = sid
- session_data
- end
- def set_cookie(env, session_id, cookie)
- cookie_jar(env)[@key] = cookie
- end
- def get_cookie(env)
- cookie_jar(env)[@key]
- end
- def cookie_jar(env)
- request = ActionDispatch::Request.new(env)
- request.cookie_jar.signed
- end
- end
- class EncryptedCookieStore < CookieStore
- private
- def cookie_jar(env)
- request = ActionDispatch::Request.new(env)
- request.cookie_jar.encrypted
- end
- end
- # This cookie store helps you upgrading apps that use +CookieStore+ to the new default +EncryptedCookieStore+
- # To use this CookieStore set
- #
- # Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'
- #
- # in your config/initializers/session_store.rb
- #
- # You will also need to add
- #
- # Myapp::Application.config.secret_key_base = 'some secret'
- #
- # in your config/initializers/secret_token.rb, but do not remove +Myapp::Application.config.secret_token = 'some secret'+
- class UpgradeSignatureToEncryptionCookieStore < EncryptedCookieStore
- private
- def get_cookie(env)
- signed_using_old_secret_cookie_jar(env)[@key] || cookie_jar(env)[@key]
- end
- def signed_using_old_secret_cookie_jar(env)
- request = ActionDispatch::Request.new(env)
- request.cookie_jar.signed_using_old_secret
- end
- end
- end
- end
- require 'action_dispatch/middleware/session/abstract_store'
- begin
- require 'rack/session/dalli'
- rescue LoadError => e
- $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
- raise e
- end
- module ActionDispatch
- module Session
- class MemCacheStore < Rack::Session::Dalli
- include Compatibility
- include StaleSessionCheck
- include SessionObject
- def initialize(app, options = {})
- options[:expire_after] ||= options[:expires]
- super
- end
- end
- end
- end
- require 'action_dispatch/http/request'
- require 'action_dispatch/middleware/exception_wrapper'
- module ActionDispatch
- # This middleware rescues any exception returned by the application
- # and calls an exceptions app that will wrap it in a format for the end user.
- #
- # The exceptions app should be passed as parameter on initialization
- # of ShowExceptions. Every time there is an exception, ShowExceptions will
- # store the exception in env["action_dispatch.exception"], rewrite the
- # PATH_INFO to the exception status code and call the rack app.
- #
- # If the application returns a "X-Cascade" pass response, this middleware
- # will send an empty response as result with the correct status code.
- # If any exception happens inside the exceptions app, this middleware
- # catches the exceptions and returns a FAILSAFE_RESPONSE.
- class ShowExceptions
- FAILSAFE_RESPONSE = [500, { 'Content-Type' => 'text/plain' },
- ["500 Internal Server Error\n" \
- "If you are the administrator of this website, then please read this web " \
- "application's log file and/or the web server's log file to find out what " \
- "went wrong."]]
- def initialize(app, exceptions_app)
- @app = app
- @exceptions_app = exceptions_app
- end
- def call(env)
- @app.call(env)
- rescue Exception => exception
- raise exception if env['action_dispatch.show_exceptions'] == false
- render_exception(env, exception)
- end
- private
- def render_exception(env, exception)
- wrapper = ExceptionWrapper.new(env, exception)
- status = wrapper.status_code
- env["action_dispatch.exception"] = wrapper.exception
- env["PATH_INFO"] = "/#{status}"
- response = @exceptions_app.call(env)
- response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response
- rescue Exception => failsafe_error
- $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
- FAILSAFE_RESPONSE
- end
- def pass_response(status)
- [status, {"Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0"}, []]
- end
- end
- end
- module ActionDispatch
- class SSL
- YEAR = 31536000
- def self.default_hsts_options
- { :expires => YEAR, :subdomains => false }
- end
- def initialize(app, options = {})
- @app = app
- @hsts = options.fetch(:hsts, {})
- @hsts = {} if @hsts == true
- @hsts = self.class.default_hsts_options.merge(@hsts) if @hsts
- @host = options[:host]
- @port = options[:port]
- end
- def call(env)
- request = Request.new(env)
- if request.ssl?
- status, headers, body = @app.call(env)
- headers = hsts_headers.merge(headers)
- flag_cookies_as_secure!(headers)
- [status, headers, body]
- else
- redirect_to_https(request)
- end
- end
- private
- def redirect_to_https(request)
- url = URI(request.url)
- url.scheme = "https"
- url.host = @host if @host
- url.port = @port if @port
- headers = hsts_headers.merge('Content-Type' => 'text/html',
- 'Location' => url.to_s)
- [301, headers, []]
- end
- # http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02
- def hsts_headers
- if @hsts
- value = "max-age=#{@hsts[:expires].to_i}"
- value += "; includeSubDomains" if @hsts[:subdomains]
- { 'Strict-Transport-Security' => value }
- else
- {}
- end
- end
- def flag_cookies_as_secure!(headers)
- if cookies = headers['Set-Cookie']
- cookies = cookies.split("\n")
- headers['Set-Cookie'] = cookies.map { |cookie|
- if cookie !~ /;\s+secure(;|$)/
- "#{cookie}; secure"
- else
- cookie
- end
- }.join("\n")
- end
- end
- end
- end
- require "active_support/inflector/methods"
- require "active_support/dependencies"
- module ActionDispatch
- class MiddlewareStack
- class Middleware
- attr_reader :args, :block, :name, :classcache
- def initialize(klass_or_name, *args, &block)
- @klass = nil
- if klass_or_name.respond_to?(:name)
- @klass = klass_or_name
- @name = @klass.name
- else
- @name = klass_or_name.to_s
- end
- @classcache = ActiveSupport::Dependencies::Reference
- @args, @block = args, block
- end
- def klass
- @klass || classcache[@name]
- end
- def ==(middleware)
- case middleware
- when Middleware
- klass == middleware.klass
- when Class
- klass == middleware
- else
- normalize(@name) == normalize(middleware)
- end
- end
- def inspect
- klass.to_s
- end
- def build(app)
- klass.new(app, *args, &block)
- end
- private
- def normalize(object)
- object.to_s.strip.sub(/^::/, '')
- end
- end
- include Enumerable
- attr_accessor :middlewares
- def initialize(*args)
- @middlewares = []
- yield(self) if block_given?
- end
- def each
- @middlewares.each { |x| yield x }
- end
- def size
- middlewares.size
- end
- def last
- middlewares.last
- end
- def [](i)
- middlewares[i]
- end
- def unshift(*args, &block)
- middleware = self.class::Middleware.new(*args, &block)
- middlewares.unshift(middleware)
- end
- def initialize_copy(other)
- self.middlewares = other.middlewares.dup
- end
- def insert(index, *args, &block)
- index = assert_index(index, :before)
- middleware = self.class::Middleware.new(*args, &block)
- middlewares.insert(index, middleware)
- end
- alias_method :insert_before, :insert
- def insert_after(index, *args, &block)
- index = assert_index(index, :after)
- insert(index + 1, *args, &block)
- end
- def swap(target, *args, &block)
- index = assert_index(target, :before)
- insert(index, *args, &block)
- middlewares.delete_at(index + 1)
- end
- def delete(target)
- middlewares.delete target
- end
- def use(*args, &block)
- middleware = self.class::Middleware.new(*args, &block)
- middlewares.push(middleware)
- end
- def build(app = nil, &block)
- app ||= block
- raise "MiddlewareStack#build requires an app" unless app
- middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) }
- end
- protected
- def assert_index(index, where)
- i = index.is_a?(Integer) ? index : middlewares.index(index)
- raise "No such middleware to insert #{where}: #{index.inspect}" unless i
- i
- end
- end
- end
- require 'rack/utils'
- require 'active_support/core_ext/uri'
- module ActionDispatch
- class FileHandler
- def initialize(root, cache_control)
- @root = root.chomp('/')
- @compiled_root = /^#{Regexp.escape(root)}/
- headers = cache_control && { 'Cache-Control' => cache_control }
- @file_server = ::Rack::File.new(@root, headers)
- end
- def match?(path)
- path = path.dup
- full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(unescape_path(path)))
- paths = "#{full_path}#{ext}"
- matches = Dir[paths]
- match = matches.detect { |m| File.file?(m) }
- if match
- match.sub!(@compiled_root, '')
- ::Rack::Utils.escape(match)
- end
- end
- def call(env)
- @file_server.call(env)
- end
- def ext
- @ext ||= begin
- ext = ::ActionController::Base.default_static_extension
- "{,#{ext},/index#{ext}}"
- end
- end
- def unescape_path(path)
- URI.parser.unescape(path)
- end
- def escape_glob_chars(path)
- path.force_encoding('binary') if path.respond_to? :force_encoding
- path.gsub(/[*?{}\[\]]/, "\\\\\\&")
- end
- end
- class Static
- def initialize(app, path, cache_control=nil)
- @app = app
- @file_handler = FileHandler.new(path, cache_control)
- end
- def call(env)
- case env['REQUEST_METHOD']
- when 'GET', 'HEAD'
- path = env['PATH_INFO'].chomp('/')
- if match = @file_handler.match?(path)
- env["PATH_INFO"] = match
- return @file_handler.call(env)
- end
- end
- @app.call(env)
- end
- end
- end
- require "action_dispatch"
- module ActionDispatch
- class Railtie < Rails::Railtie # :nodoc:
- config.action_dispatch = ActiveSupport::OrderedOptions.new
- config.action_dispatch.x_sendfile_header = nil
- config.action_dispatch.ip_spoofing_check = true
- config.action_dispatch.show_exceptions = true
- config.action_dispatch.tld_length = 1
- config.action_dispatch.ignore_accept_header = false
- config.action_dispatch.rescue_templates = { }
- config.action_dispatch.rescue_responses = { }
- config.action_dispatch.default_charset = nil
- config.action_dispatch.rack_cache = false
- config.action_dispatch.http_auth_salt = 'http authentication'
- config.action_dispatch.signed_cookie_salt = 'signed cookie'
- config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie'
- config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie'
- config.action_dispatch.default_headers = {
- 'X-Frame-Options' => 'SAMEORIGIN',
- 'X-XSS-Protection' => '1; mode=block',
- 'X-Content-Type-Options' => 'nosniff',
- 'X-UA-Compatible' => 'chrome=1'
- }
- config.eager_load_namespaces << ActionDispatch
- initializer "action_dispatch.configure" do |app|
- ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
- ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
- ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding
- ActionDispatch::Response.default_headers = app.config.action_dispatch.default_headers
- ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
- ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
- config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?
- ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
- ActionDispatch.test_app = app
- end
- end
- end
- require 'rack/session/abstract/id'
- module ActionDispatch
- class Request < Rack::Request
- # Session is responsible for lazily loading the session from store.
- class Session # :nodoc:
- ENV_SESSION_KEY = Rack::Session::Abstract::ENV_SESSION_KEY # :nodoc:
- ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY # :nodoc:
- def self.create(store, env, default_options)
- session_was = find env
- session = Request::Session.new(store, env)
- session.merge! session_was if session_was
- set(env, session)
- Options.set(env, Request::Session::Options.new(store, env, default_options))
- session
- end
- def self.find(env)
- env[ENV_SESSION_KEY]
- end
- def self.set(env, session)
- env[ENV_SESSION_KEY] = session
- end
- class Options #:nodoc:
- def self.set(env, options)
- env[ENV_SESSION_OPTIONS_KEY] = options
- end
- def self.find(env)
- env[ENV_SESSION_OPTIONS_KEY]
- end
- def initialize(by, env, default_options)
- @by = by
- @env = env
- @delegate = default_options.dup
- end
- def [](key)
- if key == :id
- @delegate.fetch(key) {
- @delegate[:id] = @by.send(:extract_session_id, @env)
- }
- else
- @delegate[key]
- end
- end
- def []=(k,v); @delegate[k] = v; end
- def to_hash; @delegate.dup; end
- def values_at(*args); @delegate.values_at(*args); end
- end
- def initialize(by, env)
- @by = by
- @env = env
- @delegate = {}
- @loaded = false
- @exists = nil # we haven't checked yet
- end
- def id
- options[:id]
- end
- def options
- Options.find @env
- end
- def destroy
- clear
- options = self.options || {}
- new_sid = @by.send(:destroy_session, @env, options[:id], options)
- options[:id] = new_sid # Reset session id with a new value or nil
- # Load the new sid to be written with the response
- @loaded = false
- load_for_write!
- end
- def [](key)
- load_for_read!
- @delegate[key.to_s]
- end
- def has_key?(key)
- load_for_read!
- @delegate.key?(key.to_s)
- end
- alias :key? :has_key?
- alias :include? :has_key?
- def keys
- @delegate.keys
- end
- def values
- @delegate.values
- end
- def []=(key, value)
- load_for_write!
- @delegate[key.to_s] = value
- end
- def clear
- load_for_write!
- @delegate.clear
- end
- def to_hash
- load_for_read!
- @delegate.dup.delete_if { |_,v| v.nil? }
- end
- def update(hash)
- load_for_write!
- @delegate.update stringify_keys(hash)
- end
- def delete(key)
- load_for_write!
- @delegate.delete key.to_s
- end
- def inspect
- if loaded?
- super
- else
- "#<#{self.class}:0x#{(object_id << 1).to_s(16)} not yet loaded>"
- end
- end
- def exists?
- return @exists unless @exists.nil?
- @exists = @by.send(:session_exists?, @env)
- end
- def loaded?
- @loaded
- end
- def empty?
- load_for_read!
- @delegate.empty?
- end
- def merge!(other)
- load_for_write!
- @delegate.merge!(other)
- end
- private
- def load_for_read!
- load! if !loaded? && exists?
- end
- def load_for_write!
- load! unless loaded?
- end
- def load!
- id, session = @by.load_session @env
- options[:id] = id
- @delegate.replace(stringify_keys(session))
- @loaded = true
- end
- def stringify_keys(other)
- other.each_with_object({}) { |(key, value), hash|
- hash[key.to_s] = value
- }
- end
- end
- end
- end
- require 'delegate'
- module ActionDispatch
- module Routing
- class RouteWrapper < SimpleDelegator
- def endpoint
- rack_app ? rack_app.inspect : "#{controller}##{action}"
- end
- def constraints
- requirements.except(:controller, :action)
- end
- def rack_app(app = self.app)
- @rack_app ||= begin
- class_name = app.class.name.to_s
- if class_name == "ActionDispatch::Routing::Mapper::Constraints"
- rack_app(app.app)
- elsif ActionDispatch::Routing::Redirect === app || class_name !~ /^ActionDispatch::Routing/
- app
- end
- end
- end
- def verb
- super.source.gsub(/[$^]/, '')
- end
- def path
- super.spec.to_s
- end
- def name
- super.to_s
- end
- def regexp
- __getobj__.path.to_regexp
- end
- def json_regexp
- str = regexp.inspect.
- sub('\\A' , '^').
- sub('\\Z' , '$').
- sub('\\z' , '$').
- sub(/^\// , '').
- sub(/\/[a-z]*$/ , '').
- gsub(/\(\?#.+\)/ , '').
- gsub(/\(\?-\w+:/ , '(').
- gsub(/\s/ , '')
- Regexp.new(str).source
- end
- def reqs
- @reqs ||= begin
- reqs = endpoint
- reqs += " #{constraints.to_s}" unless constraints.empty?
- reqs
- end
- end
- def controller
- requirements[:controller] || ':controller'
- end
- def action
- requirements[:action] || ':action'
- end
- def internal?
- controller =~ %r{\Arails/(info|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}}
- end
- def engine?
- rack_app && rack_app.respond_to?(:routes)
- end
- end
- ##
- # This class is just used for displaying route information when someone
- # executes `rake routes` or looks at the RoutingError page.
- # People should not use this class.
- class RoutesInspector # :nodoc:
- def initialize(routes)
- @engines = {}
- @routes = routes
- end
- def format(formatter, filter = nil)
- routes_to_display = filter_routes(filter)
- routes = collect_routes(routes_to_display)
- formatter.section routes
- @engines.each do |name, engine_routes|
- formatter.section_title "Routes for #{name}"
- formatter.section engine_routes
- end
- formatter.result
- end
- private
- def filter_routes(filter)
- if filter
- @routes.select { |route| route.defaults[:controller] == filter }
- else
- @routes
- end
- end
- def collect_routes(routes)
- routes.collect do |route|
- RouteWrapper.new(route)
- end.reject do |route|
- route.internal?
- end.collect do |route|
- collect_engine_routes(route)
- { name: route.name,
- verb: route.verb,
- path: route.path,
- reqs: route.reqs,
- regexp: route.json_regexp }
- end
- end
- def collect_engine_routes(route)
- name = route.endpoint
- return unless route.engine?
- return if @engines[name]
- routes = route.rack_app.routes
- if routes.is_a?(ActionDispatch::Routing::RouteSet)
- @engines[name] = collect_routes(routes.routes)
- end
- end
- end
- class ConsoleFormatter
- def initialize
- @buffer = []
- end
- def result
- @buffer.join("\n")
- end
- def section_title(title)
- @buffer << "\n#{title}:"
- end
- def section(routes)
- @buffer << draw_section(routes)
- end
- private
- def draw_section(routes)
- name_width = routes.map { |r| r[:name].length }.max
- verb_width = routes.map { |r| r[:verb].length }.max
- path_width = routes.map { |r| r[:path].length }.max
- routes.map do |r|
- "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
- end
- end
- end
- class HtmlTableFormatter
- def initialize(view)
- @view = view
- @buffer = []
- end
- def section_title(title)
- @buffer << %(<tr><th colspan="4">#{title}</th></tr>)
- end
- def section(routes)
- @buffer << @view.render(partial: "routes/route", collection: routes)
- end
- def result
- @view.raw @view.render(layout: "routes/table") {
- @view.raw @buffer.join("\n")
- }
- end
- end
- end
- end
- require 'active_support/core_ext/hash/except'
- require 'active_support/core_ext/hash/reverse_merge'
- require 'active_support/core_ext/hash/slice'
- require 'active_support/core_ext/enumerable'
- require 'active_support/core_ext/array/extract_options'
- require 'active_support/inflector'
- require 'action_dispatch/routing/redirection'
- module ActionDispatch
- module Routing
- class Mapper
- URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
- class Constraints #:nodoc:
- def self.new(app, constraints, request = Rack::Request)
- if constraints.any?
- super(app, constraints, request)
- else
- app
- end
- end
- attr_reader :app, :constraints
- def initialize(app, constraints, request)
- @app, @constraints, @request = app, constraints, request
- end
- def matches?(env)
- req = @request.new(env)
- @constraints.all? do |constraint|
- (constraint.respond_to?(:matches?) && constraint.matches?(req)) ||
- (constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req)))
- end
- ensure
- req.reset_parameters
- end
- def call(env)
- matches?(env) ? @app.call(env) : [ 404, {'X-Cascade' => 'pass'}, [] ]
- end
- private
- def constraint_args(constraint, request)
- constraint.arity == 1 ? [request] : [request.symbolized_path_parameters, request]
- end
- end
- class Mapping #:nodoc:
- IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix, :format]
- ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
- SHORTHAND_REGEX = %r{/[\w/]+$}
- WILDCARD_PATH = %r{\*([^/\)]+)\)?$}
- attr_reader :scope, :path, :options, :requirements, :conditions, :defaults
- def initialize(set, scope, path, options)
- @set, @scope, @path, @options = set, scope, path, options
- @requirements, @conditions, @defaults = {}, {}, {}
- normalize_path!
- normalize_options!
- normalize_requirements!
- normalize_conditions!
- normalize_defaults!
- end
- def to_route
- [ app, conditions, requirements, defaults, options[:as], options[:anchor] ]
- end
- private
- def normalize_path!
- raise ArgumentError, "path is required" if @path.blank?
- @path = Mapper.normalize_path(@path)
- if required_format?
- @path = "#{@path}.:format"
- elsif optional_format?
- @path = "#{@path}(.:format)"
- end
- end
- def required_format?
- options[:format] == true
- end
- def optional_format?
- options[:format] != false && !path.include?(':format') && !path.end_with?('/')
- end
- def normalize_options!
- @options.reverse_merge!(scope[:options]) if scope[:options]
- path_without_format = path.sub(/\(\.:format\)$/, '')
- # Add a constraint for wildcard route to make it non-greedy and match the
- # optional format part of the route by default
- if path_without_format.match(WILDCARD_PATH) && @options[:format] != false
- @options[$1.to_sym] ||= /.+?/
- end
- if path_without_format.match(':controller')
- raise ArgumentError, ":controller segment is not allowed within a namespace block" if scope[:module]
- # Add a default constraint for :controller path segments that matches namespaced
- # controllers with default routes like :controller/:action/:id(.:format), e.g:
- # GET /admin/products/show/1
- # => { controller: 'admin/products', action: 'show', id: '1' }
- @options[:controller] ||= /.+?/
- end
- if using_match_shorthand?(path_without_format, @options)
- to_shorthand = @options[:to].blank?
- @options[:to] ||= path_without_format.gsub(/\(.*\)/, "")[1..-1].sub(%r{/([^/]*)$}, '#\1')
- end
- @options.merge!(default_controller_and_action(to_shorthand))
- end
- # match "account/overview"
- def using_match_shorthand?(path, options)
- path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX
- end
- def normalize_format!
- if options[:format] == true
- options[:format] = /.+/
- elsif options[:format] == false
- options.delete(:format)
- end
- end
- def normalize_requirements!
- constraints.each do |key, requirement|
- next unless segment_keys.include?(key) || key == :controller
- if requirement.source =~ ANCHOR_CHARACTERS_REGEX
- raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
- end
- if requirement.multiline?
- raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
- end
- @requirements[key] = requirement
- end
- if options[:format] == true
- @requirements[:format] = /.+/
- elsif Regexp === options[:format]
- @requirements[:format] = options[:format]
- elsif String === options[:format]
- @requirements[:format] = Regexp.compile(options[:format])
- end
- end
- def normalize_defaults!
- @defaults.merge!(scope[:defaults]) if scope[:defaults]
- @defaults.merge!(options[:defaults]) if options[:defaults]
- options.each do |key, default|
- next if Regexp === default || IGNORE_OPTIONS.include?(key)
- @defaults[key] = default
- end
- if options[:constraints].is_a?(Hash)
- options[:constraints].each do |key, default|
- next unless URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
- @defaults[key] ||= default
- end
- end
- if Regexp === options[:format]
- @defaults[:format] = nil
- elsif String === options[:format]
- @defaults[:format] = options[:format]
- end
- end
- def normalize_conditions!
- @conditions.merge!(:path_info => path)
- constraints.each do |key, condition|
- next if segment_keys.include?(key) || key == :controller
- @conditions[key] = condition
- end
- @conditions[:required_defaults] = []
- options.each do |key, required_default|
- next if segment_keys.include?(key) || IGNORE_OPTIONS.include?(key)
- next if Regexp === required_default
- @conditions[:required_defaults] << key
- end
- via_all = options.delete(:via) if options[:via] == :all
- if !via_all && options[:via].blank?
- msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
- "If you want to expose your action to GET, use `get` in the router:\n\n" \
- " Instead of: match \"controller#action\"\n" \
- " Do: get \"controller#action\""
- raise msg
- end
- if via = options[:via]
- list = Array(via).map { |m| m.to_s.dasherize.upcase }
- @conditions.merge!(:request_method => list)
- end
- end
- def app
- Constraints.new(endpoint, blocks, @set.request_class)
- end
- def default_controller_and_action(to_shorthand=nil)
- if to.respond_to?(:call)
- { }
- else
- if to.is_a?(String)
- controller, action = to.split('#')
- elsif to.is_a?(Symbol)
- action = to.to_s
- end
- controller ||= default_controller
- action ||= default_action
- unless controller.is_a?(Regexp) || to_shorthand
- controller = [@scope[:module], controller].compact.join("/").presence
- end
- if controller.is_a?(String) && controller =~ %r{\A/}
- raise ArgumentError, "controller name should not start with a slash"
- end
- controller = controller.to_s unless controller.is_a?(Regexp)
- action = action.to_s unless action.is_a?(Regexp)
- if controller.blank? && segment_keys.exclude?(:controller)
- raise ArgumentError, "missing :controller"
- end
- if action.blank? && segment_keys.exclude?(:action)
- raise ArgumentError, "missing :action"
- end
- if controller.is_a?(String) && controller !~ /\A[a-z_0-9\/]*\z/
- message = "'#{controller}' is not a supported controller name. This can lead to potential routing problems."
- message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
- raise ArgumentError, message
- end
- hash = {}
- hash[:controller] = controller unless controller.blank?
- hash[:action] = action unless action.blank?
- hash
- end
- end
- def blocks
- if options[:constraints].present? && !options[:constraints].is_a?(Hash)
- [options[:constraints]]
- else
- scope[:blocks] || []
- end
- end
- def constraints
- @constraints ||= {}.tap do |constraints|
- constraints.merge!(scope[:constraints]) if scope[:constraints]
- options.except(*IGNORE_OPTIONS).each do |key, option|
- constraints[key] = option if Regexp === option
- end
- constraints.merge!(options[:constraints]) if options[:constraints].is_a?(Hash)
- end
- end
- def segment_keys
- @segment_keys ||= path_pattern.names.map{ |s| s.to_sym }
- end
- def path_pattern
- Journey::Path::Pattern.new(strexp)
- end
- def strexp
- Journey::Router::Strexp.compile(path, requirements, SEPARATORS)
- end
- def endpoint
- to.respond_to?(:call) ? to : dispatcher
- end
- def dispatcher
- Routing::RouteSet::Dispatcher.new(:defaults => defaults)
- end
- def to
- options[:to]
- end
- def default_controller
- options[:controller] || scope[:controller]
- end
- def default_action
- options[:action] || scope[:action]
- end
- end
- # Invokes Rack::Mount::Utils.normalize path and ensure that
- # (:locale) becomes (/:locale) instead of /(:locale). Except
- # for root cases, where the latter is the correct one.
- def self.normalize_path(path)
- path = Journey::Router::Utils.normalize_path(path)
- path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/\(+[^)]+\)$}
- path
- end
- def self.normalize_name(name)
- normalize_path(name)[1..-1].tr("/", "_")
- end
- module Base
- # You can specify what Rails should route "/" to with the root method:
- #
- # root to: 'pages#main'
- #
- # For options, see +match+, as +root+ uses it internally.
- #
- # You can also pass a string which will expand
- #
- # root 'pages#main'
- #
- # You should put the root route at the top of <tt>config/routes.rb</tt>,
- # because this means it will be matched first. As this is the most popular route
- # of most Rails applications, this is beneficial.
- def root(options = {})
- options = { :to => options } if options.is_a?(String)
- match '/', { :as => :root, :via => :get }.merge!(options)
- end
- # Matches a url pattern to one or more routes. Any symbols in a pattern
- # are interpreted as url query parameters and thus available as +params+
- # in an action:
- #
- # # sets :controller, :action and :id in params
- # match ':controller/:action/:id'
- #
- # Two of these symbols are special, +:controller+ maps to the controller
- # and +:action+ to the controller's action. A pattern can also map
- # wildcard segments (globs) to params:
- #
- # match 'songs/*category/:title', to: 'songs#show'
- #
- # # 'songs/rock/classic/stairway-to-heaven' sets
- # # params[:category] = 'rock/classic'
- # # params[:title] = 'stairway-to-heaven'
- #
- # When a pattern points to an internal route, the route's +:action+ and
- # +:controller+ should be set in options or hash shorthand. Examples:
- #
- # match 'photos/:id' => 'photos#show'
- # match 'photos/:id', to: 'photos#show'
- # match 'photos/:id', controller: 'photos', action: 'show'
- #
- # A pattern can also point to a +Rack+ endpoint i.e. anything that
- # responds to +call+:
- #
- # match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }
- # match 'photos/:id', to: PhotoRackApp
- # # Yes, controller actions are just rack endpoints
- # match 'photos/:id', to: PhotosController.action(:show)
- #
- # Because request various HTTP verbs with a single action has security
- # implications, is recommendable use HttpHelpers[rdoc-ref:HttpHelpers]
- # instead +match+
- #
- # === Options
- #
- # Any options not seen here are passed on as params with the url.
- #
- # [:controller]
- # The route's controller.
- #
- # [:action]
- # The route's action.
- #
- # [:path]
- # The path prefix for the routes.
- #
- # [:module]
- # The namespace for :controller.
- #
- # match 'path', to: 'c#a', module: 'sekret', controller: 'posts'
- # #=> Sekret::PostsController
- #
- # See <tt>Scoping#namespace</tt> for its scope equivalent.
- #
- # [:as]
- # The name used to generate routing helpers.
- #
- # [:via]
- # Allowed HTTP verb(s) for route.
- #
- # match 'path', to: 'c#a', via: :get
- # match 'path', to: 'c#a', via: [:get, :post]
- # match 'path', to: 'c#a', via: :all
- #
- # [:to]
- # Points to a +Rack+ endpoint. Can be an object that responds to
- # +call+ or a string representing a controller's action.
- #
- # match 'path', to: 'controller#action'
- # match 'path', to: lambda { |env| [200, {}, ["Success!"]] }
- # match 'path', to: RackApp
- #
- # [:on]
- # Shorthand for wrapping routes in a specific RESTful context. Valid
- # values are +:member+, +:collection+, and +:new+. Only use within
- # <tt>resource(s)</tt> block. For example:
- #
- # resource :bar do
- # match 'foo', to: 'c#a', on: :member, via: [:get, :post]
- # end
- #
- # Is equivalent to:
- #
- # resource :bar do
- # member do
- # match 'foo', to: 'c#a', via: [:get, :post]
- # end
- # end
- #
- # [:constraints]
- # Constrains parameters with a hash of regular expressions or an
- # object that responds to <tt>matches?</tt>
- #
- # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }
- #
- # class Blacklist
- # def matches?(request) request.remote_ip == '1.2.3.4' end
- # end
- # match 'path', to: 'c#a', constraints: Blacklist.new
- #
- # See <tt>Scoping#constraints</tt> for more examples with its scope
- # equivalent.
- #
- # [:defaults]
- # Sets defaults for parameters
- #
- # # Sets params[:format] to 'jpg' by default
- # match 'path', to: 'c#a', defaults: { format: 'jpg' }
- #
- # See <tt>Scoping#defaults</tt> for its scope equivalent.
- #
- # [:anchor]
- # Boolean to anchor a <tt>match</tt> pattern. Default is true. When set to
- # false, the pattern matches any request prefixed with the given path.
- #
- # # Matches any request starting with 'path'
- # match 'path', to: 'c#a', anchor: false
- #
- # [:format]
- # Allows you to specify the default value for optional +format+
- # segment or disable it by supplying +false+.
- def match(path, options=nil)
- end
- # Mount a Rack-based application to be used within the application.
- #
- # mount SomeRackApp, at: "some_route"
- #
- # Alternatively:
- #
- # mount(SomeRackApp => "some_route")
- #
- # For options, see +match+, as +mount+ uses it internally.
- #
- # All mounted applications come with routing helpers to access them.
- # These are named after the class specified, so for the above example
- # the helper is either +some_rack_app_path+ or +some_rack_app_url+.
- # To customize this helper's name, use the +:as+ option:
- #
- # mount(SomeRackApp => "some_route", as: "exciting")
- #
- # This will generate the +exciting_path+ and +exciting_url+ helpers
- # which can be used to navigate to this mounted app.
- def mount(app, options = nil)
- if options
- path = options.delete(:at)
- else
- unless Hash === app
- raise ArgumentError, "must be called with mount point"
- end
- options = app
- app, path = options.find { |k, v| k.respond_to?(:call) }
- options.delete(app) if app
- end
- raise "A rack application must be specified" unless path
- options[:as] ||= app_name(app)
- options[:via] ||= :all
- match(path, options.merge(:to => app, :anchor => false, :format => false))
- define_generate_prefix(app, options[:as])
- self
- end
- def default_url_options=(options)
- @set.default_url_options = options
- end
- alias_method :default_url_options, :default_url_options=
- def with_default_scope(scope, &block)
- scope(scope) do
- instance_exec(&block)
- end
- end
- private
- def app_name(app)
- return unless app.respond_to?(:routes)
- if app.respond_to?(:railtie_name)
- app.railtie_name
- else
- class_name = app.class.is_a?(Class) ? app.name : app.class.name
- ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
- end
- end
- def define_generate_prefix(app, name)
- return unless app.respond_to?(:routes) && app.routes.respond_to?(:define_mounted_helper)
- _route = @set.named_routes.routes[name.to_sym]
- _routes = @set
- app.routes.define_mounted_helper(name)
- app.routes.singleton_class.class_eval do
- define_method :mounted? do
- true
- end
- define_method :_generate_prefix do |options|
- prefix_options = options.slice(*_rou